(c) Robert Tinney |
Dzisiaj postanowiłem pokazać jak w prosty sposób odczytać komentarze z bloga na portalu dobreprogramy.pl, który nie posiada usługi odczytu wiadomości za pomocą XML/JSON przez co trzeba parsować HTML. Dla niecierpliwych na końcu tekstu jest krótki film podsumowujący temat tego wpisu.
Odczyt zawartości strony internetowej robi się dość banalnie. Wystarczy w Workspace Pharo wpisać :
'http://www.google.com' asUrl retrieveContents contents.
Potem umieścić kursor klawiatury za kropką (bądź zaznaczyć myszką całą linijkę kodu) i nacisnąć [Ctrl+p] ("Print it"). Powinna pojawić się zawartość z podanego adresu. Tekst, który się pojawia jest już zaznaczony, więc można nacisnąć klawisz Del by go usunąć.
Jako, że tekst strony już mamy, to potrzebny będzie parser HTML. Będzie potrzebny, bo wbudowany parser XML tonie w zupie tagów luźno zapodanego HTMLa, gdzie zdarzają się niedomknięte tagi, ew. tagi zapisane niezgodnie z dokumentacją i wtrącenia w postaci JavaScriptu czy CSS i inne śmieci.
Dodatkowe biblioteki Smalltalka są przechowywane w repozytoriach. Z takiego dziś skorzystam. Jest to repozytorium Squeaka (inna odmiana Smalltalka zgodna z Pharo).
Aby zainstalować bibliotekę trzeba wejść do przeglądarki Monticello (dostępne z menu myszki). Następnie trzeba kliknąć w "+Package" nadać dowolną nazwę, np. "Soup". Potem klik w "+Repository" i wybieram opcję HTTP. Modyfikuję adres, który się pojawi na szablonie na:
location: 'http://www.squeaksource.com/Soup'
Klikam OK, potem w "Open". Załaduje się lista wersji pakietów. Trzeba teraz wybrać ConfigurationOfSoup - najnowszą wersję , u mnie jest 37 i kliknąć Load. ConfigurationOfSoup to pakiet obiektów, które pobiorą najnowszą wersję zupki. Aby się dowiedzieć jak go uruchomić trzeba otworzyć System Browser i znaleźć ConfigurationOfSoup. Można to zrobić poprzez menu z Find, górną belkę, lub przewinąć listę na sam koniec. Najnowsze pakiety są widoczne zawsze na końcu. W drugim okienku, które jest zwane klasowym lub instancji trzeba kliknąć jeszcze raz nazwę ConfigurationOfSoup, a potem pytajnik. Pod pytajnikiem zwykle jest dokumentacja obiektu.
Widać tam dwie linijki. Jako, że kod Smalltaka można wykonać z każdego okienka, zaznaczam linijkę z wersją #stable i wykonuję "Do it". skrypt pobierze i skompiluje resztę biblioteki.
Teraz można pobrać zupę do obiektu już za pomocą samej biblioteki (oczywiście wszystko to wpisuję w Workspace):
zupa := Soup fromUrl: 'http://*dobreprog***.pl/aeroflyluby/Domowy-sposob-na-Diode,36474.html'.
Tym razem użyłem prawdziwego adresu z bloga. Mam więc zupę z tagów i w tym momencie zaczyna się zabawa. Trzeba dowiedzieć się pomiędzy jakimi tagami trzymany jest blok komentarzy. Potem znaleźć pojedyncze wpisy, a następnie rozbić na:
- adres logo: #img
- nazwę komentującego: #nick
- treść komentarza: #text
Zrobiłem małe rozeznanie i znalazłem id tagu, w którym te komentarze siedzą:
ctl00_phContentLeft_panUpdateComment
Niestety Soup nie ma możliwości wyciągania tagów po identyfikatorze. Trzeba sobie napisać swoją funkcję. Najlepiej to zrobić zrzynając już z istniejącej:
findTagByClass: aString
^ self findTagByClass: aString ifAbsent: [nil]
Oczywiście tworzymy ją w instancji klasy SoupTag w pakiecie Soup-Core. Widać w kodzie, że funkcja ta wywołuje jeszcze inną, z parametrem ifAbsent, która to jest główną (rozszerzoną funkcją) do wyciągania tagu. Jej nazwę też trzeba podmienić. W sumie tworzymy dwie funkcje:
findTagByID: aString
^ self findTagByID: aString ifAbsent: [nil]
i
findTagByID: aString ifAbsent: aBlock
self findTag: [:aTag | (aTag attributeAt: 'id') = aString]
ifPresent: [:aTag | ^ aTag].
^ aBlock value
Mając te dwie funkcje mogę już wyciągnąć tag, w którym siedzą komentarze:
root:= aSoup findTagByID: 'ctl00_phContentLeft_panUpdateComment'.
Można użyć "Print it", które wyświetli treść tagu, lub dać "Do it" i podejrzeć obiekt root przez zaznaczenie go i użycie "Inspect it".
Teraz czas wyciągnąć pojedyncze pola komentarzy. Tutaj będzie trochę trudniej, bo id komentarzy zmienia się. To jest, id komentarzy jest w postaci: komentarz_124234. Niestety, Soup nie umożliwia wyciągania tagów z użyciem wyrażeń regularnych. Trzeba będzie sobie poradzić. I znów trzeba zmodyfikować nasze nowe funkcje:
findAllTagsByIDregX:
aString
^ Array streamContents: [ :aStream |
self findAllTagsByIDregX: aString
stream: aStream ]
i
^ Array streamContents: [ :aStream |
self findAllTagsByIDregX: aString
stream: aStream ]
i
findAllTagsByIDregX: aString stream: aStream
self
childTagsDo:
[ :aTag
| ((aTag
attributeAt: 'id')
isNil ) ifFalse:
[((aTag
id) matchesRegex: aString)
ifTrue: [aStream
nextPut: aTag
]].
aTag
findAllTagsByID: aString
stream: aStream
]
oraz:
findAllTagsByID: aString
^ Array streamContents: [ :aStream |
self findAllTagsByID: aString stream: aStream ]i
findAllTagsByID: aString stream: aStream
self childTagsDo:
[ :aTag | (aTag attributeAt: 'id') = aString
ifTrue: [aStream nextPut: aTag ].
aTag findAllTagsByID: aString stream: aStream ]
Mając te funkcje teraz mogę wyciągnąć komentarze do tablicy:
komentIDregX := 'komentarz_[0-9]+'.
komentSoups := root findAllTagsByIDregX: komentIDregX.
Super. ;) Teraz utworzę obiekty na tagi komentarzy, nicków i logo komentujących:
komentClass
:= 'text-h75
tresc'.
imgClass := 'border small float-left'.
nickClass := 'text-h65 font-heading display-inl_blk nick'.
nixesTags := root findAllTagsByClass: nickClass.
imgsTags := root findAllTagsByClass: imgClass.
komentTags := root findAllTagsByClass: komentClass.
imgClass := 'border small float-left'.
nickClass := 'text-h65 font-heading display-inl_blk nick'.
nixesTags := root findAllTagsByClass: nickClass.
imgsTags := root findAllTagsByClass: imgClass.
komentTags := root findAllTagsByClass: komentClass.
nixes
:= OrderedCollection
new.imgs
:= OrderedCollection
new.
koments := OrderedCollection new.nixesTags do: [:tag | nixes add: (tag text) ].
imgsTags do: [:tag | imgs add: (tag src) ].
komentTags do: [:tag | koments add: (tag text) ].
koments := OrderedCollection new.nixesTags do: [:tag | nixes add: (tag text) ].
imgsTags do: [:tag | imgs add: (tag src) ].
komentTags do: [:tag | koments add: (tag text) ].
Oto rezultat:
Jak ktoś chce sprawdzić czy adresy obrazków są dobre, to może wyświetlić np. pierwszy poleceniem:
(ImageMorph fromStream: ((imgs at: 1) asUrl retrieveContents contentStream)) openInWorld.
Aby pozbyć się obrazka trzeba wcisnąć Shift i kliknąć na obrazku środkowym przyciskiem myszki.
Oczywiście poprzedni kod można zapisać krócej używając słownika i listy. Słownik (Dictionary) to odpowiednik mapy w Javie. Ostateczna postać:
zupa := Soup fromUrl: 'http://www.d***y.pl/aeroflyluby/Domowy-sposob-na-Diode,36474.html'.
komentIDregX := 'komentarz_[0-9]+'.
komentClass := 'text-h75 tresc'.
imgClass := 'border small float-left'.
nickClass := 'text-h65 font-heading display-inl_blk nick'.
komentSoups := root findAllTagsByIDregX: komentIDregX.
komenty := OrderedCollection new.
komentSoups do: [:ks |
koment := Dictionary new.
koment at: #nick put: (ks findTagByClass: nickClass) text.
koment at: #img put: (ks findTagByClass: imgClass) src.
koment at: #text put: (ks findTagByClass: komentClass) text.
komenty add: koment.
].
(ImageMorph fromStream: (((komenty at: 1) at: #img) asUrl retrieveContents contentStream)) openInWorld.
Podsumowanie na video w jeszcze innym stylu:
Na filmie można zauważyć, że nie tworzę funkcji przed wykonaniem kodu. Środowisko Smalltalka informuje mnie, że takowej nie ma i pozwala utworzyć brakującą. Po utworzeniu funkcji można kliknąć "Proceed" i środowisko zachowa się jakby nic nie było. W ten sposób także bardzo łatwo i szybko pisze się testy w konwencji TDD - test first. To nie żadna nowość. Smalltalk był pierwszym szeroko dostępnym środowiskiem, które było wyposażone w bibliotekę do testów jednostkowych xUnit (jUnit - wersja na Javę). To, czym dziś ludzie się zachwycają i biorą za nowość, było używane grubo ponad 30 lat temu.
W kolejnej części o tym jak to wszystko wyświetlić na stronie WWW.
Brak komentarzy:
Prześlij komentarz