Zanurkuj w Pythonie/Przetwarzanie HTML-a - wszystko razem

Wszystko razem

edytuj

Nadszedł czas, aby połączyć w całość wiedzę, którą zdobyliśmy do tej pory.

Przykład. Funkcja translate, część 1
 def translate(url, dialectName="chef"):  #(1)
     import urllib                        #(2)
     sock = urllib.urlopen(url)           #(3)
     htmlSource = sock.read()           
     sock.close()
  1. Funkcja translate przyjmuje opcjonalny argument dialectName, który jest łańcuchem znaków określającym używany dialekt. Zaraz zobaczymy, jak to jest wykorzystywane.
  2. Moment, tam jest ważna instrukcja w tej funkcji! Jest to w pełni dozwolone w Pythonie działanie. Instrukcję import używaliśmy zwykle na samym początku programu, aby zaimportowany moduł był dostępny w dowolnym miejscu. Ale możemy także importować moduły w samej funkcji, przez co będą one dostępne tylko z jej poziomu. Jeżeli jakiegoś modułu potrzebujemy użyć tylko w jednej funkcji, jest to najlepszy sposób aby zachować modularność twojego programu. (Docenisz to, gdy okaże się, że twój weekendowy hack wyrósł na ważące 800 linii dzieło sztuki, a ty właśnie zdecydujesz się podzielić to na mniejsze części).
  3. Tutaj otwieramy połączenie i do zmiennej htmlSource pobieramy źródło HTML spod wskazanego adresu URL.
Przykład. Funkcja translate, część 2: coraz ciekawiej
     parserName = "%sDialectizer" % dialectName.capitalize() #(1)
     parserClass = globals()[parserName]                     #(2)
     parser = parserClass()                                  #(3)
  1. capitalize jest metodą łańcucha znaków, z którą się jeszcze nie spotkaliśmy; zmienia ona pierwszy znak na wielką literę, a wszystkie pozostałe znaki na małe litery. W połączeniu z prostym formatowaniem napisu, nazwa dialektu zamieniana jest na nazwę odpowiadającej mu klasy. Jeżeli dialectName ma wartość 'chef', parserName przyjmie wartość 'ChefDialectizer'.
  2. W tym miejscu mamy nazwę klasy (w zmiennej parserName) oraz dostęp do globalnej przestrzeni nazw, poprzez słownik globals(). Łącząc obie informacje dostajemy referencje do klasy o określonej nazwie. (Pamiętajmy, że klasy są obiektami i mogą być przypisane do zmiennej, jak każdy inny obiekt). Jeżeli parserName ma wartość 'ChefDialectizer', parserClass będzie klasą ChefDialectizer.
  3. Ostatecznie, mając obiekt klasy (parserClass) chcemy zainicjować tę klasę. Wiemy już jak zrobić –- po prostu wywołujemy klasę w taki sposób, jakby była to funkcja. Fakt, że klasa jest przechowywana w lokalnej zmiennej nie robi żadnej różnicy, po prostu wywołujemy lokalną zmienną jak funkcję, i na wyjściu wyskakuje instancja klasy. Jeżeli parserClass jest klasą ChefDialectizer, parser będzie instancją klasy ChefDialectizer.

Zastanawiasz się, ponieważ istnieją tylko 3 klasy Dialectizer, dlaczego by nie użyć po prostu instrukcji case? (W porządku, w Pythonie nie ma instrukcji case, ale zawsze można użyć serii instrukcji if). Z jednego powodu: elastyczności programu. Funkcja translate nie ma pojęcia, jak wiele zdefiniowaliśmy podklas Dialectizer-a. Wyobraźmy sobie, że definiujemy jutro nową klasę FooDialectizer -– funkcja translate zadziała bez przeróbek.

Nawet lepiej – wyobraźmy sobie, że umieszczasz klasę FooDialectizer w osobnym module i importujesz ją poprzez from module import. Jak wcześniej mogliśmy się przekonać, taka operacja dołączy to do globals(), więc funkcja translate nadal będzie działać prawidłowo bez konieczności dokonywania modyfikacji, nawet wtedy, gdy FooDialectizer znajdzie się w oddzielnym pliku.

Teraz wyobraźmy sobie, że nazwa dialektu pochodzi skądś spoza programu, może z bazy danych lub z wartości wprowadzonej przez użytkownika. Możemy użyć jakąkolwiek ilość pythonowych skryptów po stronie serwera, aby dynamicznie generować strony internetowe; taka funkcja mogłaby przekazać URL i nazwę dialektu (oba w postaci łańcucha znaków) w zapytania żądania strony internetowej, i zwrócić "przetłumaczoną" stronę.

Na koniec wyobraźmy sobie framework Dialectizer z wbudowaną obsługą plug-inów. Możemy umieścić każdą podklasę Dialectizer-a w osobnym pliku pozostawiając jedynie w pliku dialect.py funkcję translate. Jeżeli zachowasz stały schemat nazewnictwa klas, funkcja translate może dynamicznie importować potrzebną klasę z odpowiedniego pliku, jedynie na podstawie podanej nazwy dialektu. (Dynamicznego importowanie omówimy to w dalszej części tego podręcznika). Aby dodać nowy dialekt, wystarczy, że utworzymy odpowiednio nazwany plik (np. foodialect.py zawierający klasę FooDialectizer) w katalogu z plug-inami. Wywołując funkcję translate z nazwą dialektu 'foo', odnajdzie ona moduł foodialect.py i automatycznie zaimportuje klasę FooDialectizer.


Przykład. Funkcja translate, część 3
     parser.feed(htmlSource) #(1)
     parser.close()          #(2)
     return parser.output()  #(3)
  1. Po tym całym wyobrażaniu sobie, co robiło się już nudne, mamy funkcję feed, która przeprowadza całą transformację. Ponieważ całe źródło HTML-a mamy w jednym łańcuchu znaków, więc funkcję feed wywołujemy tylko raz. Oczywiście możemy wywoływać ją dowolną ilość razy, a parser za każdym razem przeprowadzi transformację. Na przykład, jeżeli obawiamy się o zużycie pamięci (albo wiemy, że będziemy parsowali naprawdę wielkie strony HTML), możemy umieścić tą funkcję w pętli, w której będziemy odczytywał tylko kawałek HTML-a i karmił nim parser. Efekty będą takie same.
  2. Ponieważ funkcja feed wykorzystuje wewnętrzny bufor, powinniśmy zawsze po zakończeniu operacji wywołać funkcję close() parsera (nawet jeżeli przesłaliśmy parserowi całość za jednym razem). W przeciwnym wypadku możemy stwierdzić, że w otrzymanym wyniku brakuje kilku ostatnich bajtów.
  3. Pamiętajmy, że funkcja output, którą zdefiniowaliśmy samodzielnie w klasie BaseHTMLProcessor, łączy wszystkie zbuforowane kawałki i zwraca całość w postaci pojedynczego łańcucha znaków.

I właśnie w taki sposób, "przetłumaczyliśmy" stronę internetową, podając jedynie jej adres URL i nazwę dialektu.