Zanurkuj w Pythonie/Wprowadzenie do BaseHTMLProcessor.py
Wprowadzenie do BaseHTMLProcessor.py
edytujSGMLParser
nie tworzy niczego samodzielnie. On po prostu parsuje, parsuje i parsuje i wywołuje metodę dla każdej interesującej rzeczy jaką znajdzie, ale te metody nie wykonują niczego. SGMLParser
jest konsumentem HTML-a: bierze HTML-a i rozkłada go na małe, strukturalne części. Jak już widzieliśmy w poprzednim podrozdziale, możemy dziedziczyć po klasie SGMLParser
, aby zdefiniować klasy, które przechwycą poszczególne znaczniki i jakoś to pożytecznie wykorzystają, np. stworzą listę odnośników na danej stronie internetowej. Teraz pójdziemy krok dalej i zdefiniujemy klasę, która przechwyci wszystko, co zgłosi SGMLParser
i zrekonstruuje kompletny dokument HTML. Używając terminologii technicznej nasza klasa będzie producentem HTML-a.
BaseHTMLProcessor
dziedziczy po SGMLParser
i dostarcza 8 istotnych metod obsługi: unknown_starttag
, unknown_endtag
, handle_charref
, handle_entityref
, handle_comment
, handle_pi
, handle_decl
i handle_data
.
class BaseHTMLProcessor(SGMLParser):
def reset(self): #(1)
self.pieces = []
SGMLParser.reset(self)
def unknown_starttag(self, tag, attrs): #(2)
strattrs = "".join([' %s="%s"' % (key, value) for key, value in attrs])
self.pieces.append("<%(tag)s%(strattrs)s>" % locals())
def unknown_endtag(self, tag): #(3)
self.pieces.append("</%(tag)s>" % locals())
def handle_charref(self, ref): #(4)
self.pieces.append("&#%(ref)s;" % locals())
def handle_entityref(self, ref): #(5)
self.pieces.append("&%(ref)s" % locals())
if htmlentitydefs.entitydefs.has_key(ref):
self.pieces.append(";")
def handle_data(self, text): #(6)
self.pieces.append(text)
def handle_comment(self, text): #(7)
self.pieces.append("<!--%(text)s-->" % locals())
def handle_pi(self, text): #(8)
self.pieces.append("<?%(text)s>" % locals())
def handle_decl(self, text):
self.pieces.append("<!%(text)s>" % locals())
reset
, wołany przezSGMLParser.__init__
, inicjalizujeself.pieces
jako pustą listę przed wywołaniem metody klasy przodka.self.pieces
jest atrybutem, który będzie przechowywał części konstruowanego dokumentu HTML. Każda metoda będzie rekonstruować HTML parsowany przezSGMLParser
i każda z tych metod będzie dodawać jakiś tekst doself.pieces
. Zauważmy, żeself.pieces
jest listą. Moglibyśmy ulec pokusie, aby zdefiniować ten atrybut jako obiekt łańcucha znaków i po prostu dołączać do niego kolejne kawałki tekstu. To także by działało, ale Python jest dużo bardziej wydajny pracując z listami[1].- Ponieważ
BaseHTMLProcessor
nie definiuje żadnej metody dla poszczególnych znaczników (jak np. metodastart_a
wURLLister
),SGMLParser
będzie wywoływał dla każdego początkowego znacznika metodęunknown_starttag
. Ta metoda przyjmuje na wejściu znacznik (argumenttag
) i listę par postaci nazwa atrybutu/wartość atrybutu (argumentattrs
), a następnie rekonstruuje oryginalnego HTML-a i dodaje doself.pieces
. Napis formatujący jest tutaj nieco dziwny; rozwikłamy to później w tym rozdziale (a także tą dziwnie wyglądającą funkcjęlocals
). - Rekonstrukcja znaczników końcowych jest dużo prostsza; po prostu pobieramy nazwę znacznika i opakowujemy nawiasami ostrymi </...>.
- Gdy
SGMLParser
napotka odwołanie znakowe wywołuje metodęhandle_charref
i przekazuje jej samą wartość odwołania. Jeśli dokument HTML zawiera ,ref
przyjmie wartość160
. Rekonstrukcja oryginalnego kompletnego odwołania znakowego wymaga po prostu dodania znaków &#...;. - Odwołanie do encji jest podobne do odwołania znakowego, ale nie zawiera znaku kratki (#). Rekonstrukcja oryginalnego odwołania do encji wymaga dodania znaków &;...;. (Właściwie, jak wskazał na to pewien czytelnik, jest to nieco bardziej skomplikowane. Tylko niektóre standardowe encje HTML-a kończą się znakiem średnika; inne podobnie wyglądające encje już nie. Na szczęście dla nas zbiór standardowych encji HTML-a zdefiniowany jest w Pythonie w słowniku w module o nazwie
htmlentitydefs
. Stąd ta dodatkowa instrukcjaif
.) - Bloki tekstu są po prostu dołączane do
self.pieces
bez żadnych zmian, w postaci dosłownej. - Komentarze HTML-a opakowywane są znakami <!--...-->.
- Instrukcje przetwarzania wstawiane są pomiędzy znakami <?...>.
Ważne
Specyfikacja HTML-a wymaga, aby wszystkie nie-HTML-owe elementy (jak np. JavaScript) były zawarte pomiędzy HTML-owymi znakami komentarza, ale nie wszystkie strony internetowe robią to właściwie (a współczesne przeglądarki internetowe są wyrozumiałe w tym względzie). |
BaseHTMLProcessor
i jego metoda output
def output(self): #(1)
u"""Zwraca przetworzony HTML jako pojedynczy łańcuch znaków"""
return "".join(self.pieces)
- To jest jedyna metoda, która nie jest wołana przez klasę przodka, czyli klasę
SGMLParser
. Ponieważ pozostałe metody umieszczają swoje zrekonstruowane kawałki HTML-a wself.pieces
, ta funkcja jest potrzebna, aby połączyć wszystkie te kawałki w jeden napis. Gdyż jak już wspomniano wcześniej Python jest świetny w obsłudze list i z reguły mierny w obsłudze napisów, kompletny napis wyjściowy tworzony jest tylko wtedy, gdy ktoś o to wyraźnie poprosi.
Materiały dodatkowe
edytuj- W3C omawia odwołania znakowe i encje.
- Python Library Reference potwierdza Twoje podejrzenia, iż moduł
htmlentitydefs
jest dokładnie tym na co wygląda.
Przypisy
- ↑
Powodem dla którego Python jest lepszy w pracy z listami niż napisami, jest fakt iż listy są modyfikowalne (mutable), a napisy są niemodyfikowalne (immutable). Co oznacza, że zwiększeniem listy jest dodanie do niej po prostu nowego elementu i zaktualizowanie indeksu. Natomiast ponieważ napis nie może być zmieniony po utworzeniu, z reguły kod
s = s + nowy
utworzy całkowicie nowy napis powstały z połączenia oryginalnego napisus
i napisunowy
, a oryginalny napis zostanie zniszczony. To wymaga wielu kosztownych operacji zarządzania pamięcią, a wielkość zaangażowanego wysiłku rośnie wraz z długością napisu, a więc wykonywanie kodus = s + nowy
w pętli jest zabójcze. W terminologii technicznej dodanie n elementów do listy oznacza złożoność O(n), podczas gdy dodanie n elementów do napisu złożoność O(n2). Z drugiej strony Python korzysta z prostej optymalizacji, polegającej na tym, że jeśli dany łańcuch znaków posiada tylko jedno odwołanie, to nie tworzy nowego łańcucha, tylko rozszerza stary i akurat w tym przypadku byłoby nieco szybciej na łańcuchach znaków (wówczas złożoność byłaby O(n)).