Przejdź do treści

Webscrapping w R

W dzisiejszym wpisie nieco bardziej techniczne sprawy, ale bardzo cenne dla osób, które przy pomocy R chcą pobierać dane ze stron WWW.

Przedstawię trzy narzędzia, które w tym pomogą. Dwa pomogą na pewno, trzecie to bardziej ciekawostka do robienia screen shotów.

Jak dobrać się do tabelki na stronie?

Często jakieś dane, które nas interesują są opublikowane na stronach WWW. Można je przepisać ręcznie, można je wciągnąć do Excela (jest odpowiedni, w miarę intuicyjny importer tabelek dostępny pod guzikiem Z sieci Web w menu Dane). Ale nas interesuje oczywiście w R (i Python, ale to nie w tym odcinku).

W dzisiejszych zajęciach przydadzą nam się biblioteki:

Za przykład niech posłuży nam strona o polskich województwach z Wikipedii. Przerwij na chwilę czytanie tego tekstu i przejdź na tę stronę (kliknij środkowym klawiszem myszki czy tam rolką w link). Na stronie mamy kilka tabelek, pobierzemy sobie jedną (pierwszą).

Teraz potrzebujemy się dobrać do tabelek. W tym miejscu przyda się wiedza na temat struktury dokumentu HTML (kilka podstawowych tagów, drzewo tagów i zagnieżdżenia jednych tagów w drugich) oraz CSS (klasy, i identyfikatory elementów, czyli class i id). Taką wiedzę można znaleźć w innych miejscach – poszukajcie.

Korzystając z pakietu rvest mamy dwa sposoby wyboru elementów. Jeden to ścieżka do elementu w postaci XPath drugi to wędrówka po drzewie HTML z podaniem odpowiednich klas.

Zacznijmy od tego pierwszego.

Tabele znajdują się na stronie w ścieżce:

  • div o id bodyContent
  • w którym jest div o id mw-content-text
  • wewnątrz którego mamy div o klasie mw-parser-output
  • i tutaj już są tabele o klasach wikitable

Zapisując to w XPath mamy:

Co w wyniku daje nam node’y:

Tak samo możemy dotrzeć korzystając z HTMLa i CSSów:

Wynik jest identyczny:

Weźmy teraz pierwszą tabelę (jej zawartość):

i zobaczmy zawartość jej kilku kolumn (1, 2, 3 i 9):

TERYT województwo miasto wojewódzkie tablica rejestracyjna
2 dolnośląskie Wrocław D
4 kujawsko-pomorskie Bydgoszcz ¹Toruń ² C
6 lubelskie Lublin L
8 lubuskie Gorzów Wielkopolski ¹Zielona Góra ² F
10 łódzkie Łódź E
12 małopolskie Kraków K
14 mazowieckie Warszawa W
16 opolskie Opole O
18 podkarpackie Rzeszów R
20 podlaskie Białystok B
22 pomorskie Gdańsk G
24 śląskie Katowice S
26 świętokrzyskie Kielce T
28 warmińsko-mazurskie Olsztyn N
30 wielkopolskie Poznań P
32 zachodniopomorskie Szczecin Z

Prawda, że proste?

Strony i treść podstron

Zajmijmy się zatem czymś trudniejszym. Wyobraźmy sobie, że mamy stronę z listą linków do innych podstron. Ta lista jest stronicowana, a nas interesują informacje zawarte na tych właściwych stronach. Czyli: mamy stronicowaną listę artykułów, a interesuje nas jakaś zawartość artykułu. Zamiast artykułów może być na przykład lista ogłoszeń, a interesować mogą nas jakieś parametry ogłoszenia.

Pozostańmy przy przykładzie z artykułami, na warsztat weźmiemy te z Wiadomości portalu Gazeta.pl. Wchodzimy zatem na stronę główną Wiadomości i jedziemy na sam dół, a następnie klikamy w którąś kolejną stronę. W efekcie widzimy, że link dla strony drugiej wygląda tak: http://wiadomosci.gazeta.pl/wiadomosci/0,114871.html?str=2_19834953, a dla trzeciej http://wiadomosci.gazeta.pl/wiadomosci/0,114871.html?str=3_19834953.

Parametr str odpowiada za numer strony. Sprawdzamy jeszcze co się stanie dla str=1. Dzieje się to co powinno – mamy pierwszą stronę. Wykorzystamy to i pobierzemy linki do artykułów z pierwszych 5 stron.

W efekcie zebraliśmy 115 linków. Ale uwaga – nie wszystkie linki prowadzą do artykułów (artykuły to strony typ numer 7, czyli urle zaczynające się od http://wiadomosci.gazeta.pl/wiadomosci/7,) Zostawmy więc tylko te urle, które prowadzą do artykułów:

Zostało 111 sztuk. Dlaczego zrobiliśmy takie przesianie? Ano dlatego, że szablon strony innej niż siódemka wymagałby innej obsługi zbierania danych ze strony z treścią. Aby nie komplikować procesu po prostu się ograniczymy.

Za chwilę zbierzemy treści artykułów, ale a co jeśli się nie uda pobrać strony? Skrypt nam się wywali. Aby do tego nie dopuścić skorzystamy ze świetnej funkcji safely() z pakietu purrr mapując funkcję read_html(). Pozwoli nam to sprawdzić czy pobranie strony się udało czy nie (bo na Czerskiej założyli nam bana, albo po prostu padła sieć ;)

Teraz dla każdego z artykułów pobierzemy autora, datę publikacji i lead. Ponieważ jest to czynność mocno powtarzalna – zrobimy do tego stosowną funkcję.

Na szczęście szablony stron Gazeta.pl są bardzo czytelnie przygotowane, każdy właściwie element ma swoją klasę i wystarczyło skorzystać z odpowiednich selektorów. Wykorzystałem XPath, ale równie dobrze działa wybieranie elementów po CSSie. Zanim napisałem powyższy kod otworzyłem stronę artykułu w Chrome, kliknąłem w “Zbadaj” i wio, element po elemencie, sprawdzałem jaką ma klasę, w jakim znaczniku HTML jest zapisany. I czy jest tekstem wewnątrz znacznika, czy też jego atrybutem. Tego inaczej nie da się zrobić – potrzebna jest praca z przeglądarką.

Mając gotowe narzędzie dla wszystkich zgromadzonych urli wywołujemy funkcję i zbijamy dane w jedną długą tabelę (co trochę trwa):

Zebraliśmy 107 wierszy w tabeli. Czyli nie udało się dla wszystkich zgromadzonych z indeksu linków. Ale to nic… jak mawia moje młodsze dziecko – nie o to w tym chodzi.

Co z tymi danymi można zrobić? A na przykład sprawdzić jaki autor publikuje najczęściej:

Albo coś więcej, ale o tym już pisałem ponad pół roku temu! Tam też znajdziecie (w pierwszej części tekstu) informacje jak artykuły zostały pobrane.

Strony dynamiczne i formularze

Wszystko ładnie pięknie działa w przypadku stron statycznych, które nie doczytują sobie żadnych danych w trakcie. A co jeśli te mechanizmy nie zadziałają? Pozostaje nam udawanie klikania po stronie, tak jakbyśmy to sami robili. Z pomocą przychodzi Selenium którego warto się nauczyć. W R mamy bibliotekę

Idea Selenium to uruchomienie rzeczywistej przeglądarki na jakiejś wirtualnej maszynie i udawanie prawdziwego zachowania użytkownika. Wirtualną maszynę zapewni nam Docker. Kiedyś może więcej o tym napiszę, na dzisiaj wystarczy instalacja (tutaj opisano jak to zrobi na Ubuntu).

Po zainstalowaniu Dockera instalujemy obraz z przeglądarką – w tym przypadku z Chrome. W konsoli wpisujemy po prostu:

Następnie uruchamiamy maszynę z przeglądarką, możemy to zrobić z poziomu R:

Zapisujemy sobie tutaj process id uruchomionej maszyny – po zakończeniu zabawy maszynę wyłączymy. Cyferki 4445 i 4444 mogą się różnic w Twojej konfiguracji, to kwestia otwartych portów na konkretnej maszynie, w konkretnej sieci.

W kolejnym kroku przygotowujemy driver do przeglądarki, czyli odpalamy maszynę Dockera:

chwilę czekamy, żeby Docker wystartował, na przykład:

i uruchamiamy przeglądarkę:

Powyżej widzimy informacje o przeglądarce. Ale interesuje nas jednak wejście na konkretną stronę – na przykład Google:

Zobaczmy cośmy narobili:

Mamy określony tytuł strony, mamy jej adres.

Teraz wyszukajmy coś w tym otwartym Google wypełniając pole formularza. Poszukamy ciągu R Cran:

Co się wydarzyło, czy zadziałało i gdzieś przeszliśmy?

Jak widać tytuł i adres strony się zmieniły! Świetnie.

Kliknijmy teraz w jakiś link w wynikach wyszukiwania:

powinniśmy przejść na inna stronę – sprawdzamy tak samo jak poprzednio gdzie jesteśmy:

Zobaczmy co widać w naszej wirtualnej przeglądarce:

Oto co mamy:

Kliknij, żeby powiększyć

A może potrzebne źródło strony? Proszę bardzo:

Formularze możemy również wypełniać z poziomu pakietu rvest:

Logowanie do Facebooka

A jak zalogować się na jakaś stronę? Też najwygodniej Selenium i Dockerem. Zaloguję się do Facebooka:

Kliknij, żeby powiększyć



Kliknij, żeby powiększyć

To zaciemnienie wynika z samego Facebooka – loguję się na konto z innego niż zazwyczaj adresu, gdzieś tam w rogu na dole pokazuje się komunikat o tym informujący.

Po zakończonej pracy zamykamy przeglądarkę:

i zatrzymujemy Dockera:

Screen shot ze strony

Wyżej pobraliśmy screen shot z odwiedzanej w Selenium strony. To samo możemy zrobić prościej, bez Dockera. Posłuży do tego pakiet webshot:

Zanim jednak z niego zaczniemy korzystać musimy zainstalować wirtualną przeglądarkę (ale to nie to samo co Selenium):

Teraz możemy pobrać po prostu screen całej strony (całej, czyli do jej końca a nie tylko tego co widać na ekranie):

Kliknij, żeby powiększyć

Albo tylko jakiś fragment określony CSSem:

Kliknij, żeby powiększyć

Gdzieś widziałem kod funkcji robiącej screen shota z podanego tweetu (wg jego ID) – to bardzo fajna sprawa. I na przykład jak @dziennikarz publikuje najbardziej popularne tweety dziennikarzy to warto je mieć czasem w formie obrazka… Śpieszmy się zapisywać tweety, tak szybko są czasem kasowane ;)

Pamiętaj, żeby wpaść na fanpage’a Dane i Analizy, zalajkować go i udzielać się w komentarzach ;) A jak chcesz – rzuć piątaka na serwer, korzystając z linku obok.

4 komentarze do “Webscrapping w R”

  1. Jak zwykle przystępny artykuł. To kiedy ten Python?
    Do „wybierania” elementów ze strony polecam dodatek do Chrome’a „SelectorGadget” — można sprawdzić klasę/id elementu i od razu przerobić dane informacje na xpatha. Z takich ciekawostek warto jeszcze się zapoznać z httrem, połączenie rvest + httr sporo czasu mi oszczędziło w pracy już na porównywaniu danych z różnych zabezpieczonych projektów i jakoś wolę to od RSelenium. Mniej setupu :)

    1. Tak, httr też przydatny szczególnie w „rozmowach” z api.

      A Python może w następnym odcinku? Bo pomysł na wpis jest dość banalny, więc można przy okazji nauczyć się Pythona (to o mnie).

      1. Banalne wpisy też się przydają. Jakoś próbuję się przekonać do Pandasów i Seaborna, ale w porównaniu do R jakieś to wszystkie takie toporne. Kwestia przyzwyczajenia zapewne.

  2. Świetny artykuł. Dziękuję.
    Pozwolę sobie polecać wszystkim zainteresowanym R lekturę. Bo jest to chyba jeden z wiodących blogów R „data journalism” w PL

    A że robię prasówkę z konkretnego działu Rynek Zdrowia, zaraz wykorzystam. btw też marzy mi się nauka Pythona, ale najpierw chciałbym poznać dobrze R… i chyba poczekam aż będzie można używać Pythona zamiennie w R notebooks w RStudio.

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *