Wstęp
Biblioteka JSoup pozwala w sposób szybki i przyjemny przeparsować dokument HTML i wyciągnąć z niego interesujące nas dane. Używając gotowego parsera (zamiast na przykład wyrażeń regularnych) oszczędzimy sporo czasu i nerwów, a przede wszystkim umożliwimy samym sobie stworzenie czytelniejszego rozwiązania.
JSoup umożliwia zarówno klasyczne przechodzenie po drzewie (children <> parent), jak i wyszukiwanie węzłów po:
- html’owym id
- dowolnych atrybutach
- html’owym class
- html’owym tagu (czyli nazwie znacznika)
- selektorze css (tak, jak w arkuszu styli / jQuery)
Instalacja
Instalacja dla Maven i Gradle opisana jest na stronie: https://jsoup.org/download
Dla osób korzystających z gołego eclipse’a, chcących szybko uruchomić prosty projekt bez stawiania całego systemu zależności instalacja przebiega nastepująco:
- Tworzymy projekt javovy
- Z wyżej wymienionej strony pobieramy plik jsoup-X.X.X.jar
- Plik zapisujemy na dysku naszego komputera. Jeśli będzie wykorzystany wyłącznie do tego jednego projektu, możemy stworzyć w nim folder
lib
i tam umieścić nasz jar. Jeśli mamy gdzieś dedykowany folder na jar’y – umieszczamy jsoup w nim - Dodajemy jar do projektu w Eclipse.
- Z menu kontekstowego wybieramy
Project -> Properties
- W lewym menu wybieramy
Java Build Path
- Przechodzimy do zakładki
Libraries
- Naciskamy
Add External JARs
i znajdujemy nasz plik na dysku. - Naciskamy
OK
- Z menu kontekstowego wybieramy
Używanie
Dokumentacja
Pełna dokumentacja wszystkich klas znajduje się na stronie: https://jsoup.org/apidocs/
Ładowanie Dokumentu
Strona HTML w JSoup reprezentowana jest przez klasę Document (org.jsoup.nodes.Document).
Z sieci
W większości przypadku stronę HTML będziemy chcieli załadować bezpośrednio z internetu. Jeśli nie chcemy wyspecyfikować żadnych ustawień połączenia (a skorzystać z domyślnych) dokument stworzymy tak:
String Url = "http://www.wp.pl/"; int connectionTimeoutMs = 10000; //10s Document document; try { document = Jsoup.parse(new URL(Url), connectionTimeoutMs); } catch(IOException e) { e.printStackTrace(); return; }
Oprócz powyższego, najprostszego podejścia, możemy stworzyć dokument wykorzystując pośredni obiekt Connection. Pozwala on ustawić między innymi:
- Ciasteczka
- Referer
- Proxy
- User-Agent
- Dodatkowe nagłówki zapytania
- Metodę zapytania (GET / POST <w tym dane>)
Aby wykonać połączenie z timeout’em wynoszącym 10 sekund, z user-agentem firefox’a (pobranym z http://www.useragentstring.com/Firefox40.1_id_19879.php), korzystając z GET wykonujemy:
String Url = "http://www.wp.pl/"; int connectionTimeoutMs = 10000; //10s Document document; try { document = Jsoup.connect(Url).timeout(connectionTimeoutMs).userAgent("Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0) Gecko/20100101 Firefox/40.1").get(); } catch(IOException e) { e.printStackTrace(); return; }
Z pliku
Aby wczytać HTML z pliku możemy wykorzystać tę są metodę co z sieci, odpowiednio modyfikując Uri.
Ze string’a
Wejściowy HTML może także zostać przekazany jako napis. W tym celu możemy posłużyć się metodą parse
, podając jako argument String
String html = "<html>...</html>"; Document document = Jsoup.parse(html);
Znajdowanie węzłów, pobieranie danych
Każdy znacznik HTML reprezentowany jest przez klasę Element
. Twórcy pakietu wprowadzili dodatkowo klasę Elements
, która jest kolekcją elementów. Metody wyszukiwania, które w wyniku dają 1 element (getElementById())
, zwracają Element, zaś te, które mogą spasować wiele elementów – kolekcję elementów.
Zarówno Element (oraz Document, jako klasa pochodna) implementują zestaw dość oczywisto nazwanych metod do znajdowania węzłów i pobierania danych.
Warto uważać, bo metody szukające potomków szukają ich zazwyczaj wgłąb na dowolną głębokość. Oznacza to, że szukając tagu <li> w liście <ul> znajdziemy także tagi <li> w zagnieżdżonych listach.
Poniższy fragment kodu przedstawia użycie każdej ‚popularnej’ metody. Wszystkie razem zostały wykorzystane do odnalezienia tytułu popularnego nagłówka na stronie http://www.gazeta.pl/0,0.html
//1. Szukamy bloków z klasą mt_pict Elements mtPictElements = document.getElementsByClass("mt_pict"); //2. Spodziewamy się jednego takiego bloku, nie wiemy jak zareagować, jeśli będzie ich więcej assert(mtPictElements.size() == 1); //3. Wewnątrz szukamy p class="lead", a w środku <a>. Teskt tego <a> to interesujący nas tekst Elements leadElements = mtPictElements.first().getElementsByClass("lead"); //4. Znów spodziewamy się jednego takiego bloku assert(leadElements.size() == 1); //5. Szukamy elementu <a> z którego wyjmiemy tekst Elements aElements = leadElements.first().getElementsByTag("a"); //6. assert(aElements.size() == 1); //7. Pobieramy właściwy tekst String title = aElements.first().text(); System.out.println("Tytuł na stronie głównej: " + title);
Ta metoda jest jednak dość niewygodna. Do znalezienia jednego bloku potrzebowaliśmy kilkunastu linii kodu. Z pomocą przychodzi najużyteczniejsza technika z pakietu, czyli wyszukiwanie na podstawie znacznika CSS. Analogiczne rozwiązanie:
Elements aElements = document.select(".mt_pict .lead a"); assert(aElements.size() == 1); String title = aElements.first().text(); System.out.println("Tytuł na stronie głównej: " + title);
W powyższym przykładzie pojawiło się jeszcze odwołanie do nieporuszonej wcześniej metody – text()
.
Warto więc zwrócić uwagę na tę oraz kilka dodatkowych metod klasy Element:
attr(String attributeKey)
– pozwala uzyskać atrybut (taki jak id, czy class, dowolny inny). Jeśli atrybut nie istnieje dla danego węzła – zostanie zwrócony pusty napis, nie null.id()
– skrót doattr("id")
html()
– pozwala uzyskać kod HTML wewnątrz elementu. Zwraca wyłącznie kod wewnątrz, czyli bez znacznika elementu którego pobieramy.outerHtml()
– pozwala uzyskać kod HTML elementu. Zwraca zarówno kod wewnątrz elementu, jak i kod samego elementutext()
– zwraca wewnętrzny tekst, czylihtml()
pozbawiony znaczników. Będzie zawierał tekst swój, jak i elementów dzieci.parent()
– zwraca element rodzicatag()
– zwraca obiekt klasy Tag, reprezentujący dany element. Tag reprezentuje typ elementu – a, div, p, …tagName()
– Zwraca nazwę tagu, czyli analogicznie dotag().getName()
Limity i ograniczenia
JSoup nie jest silnikiem HTML / JavaScript. Nie jest więc w stanie wykonać kodu JS umieszczonego wewnątrz stron. Nie ma więc możliwości sensownego przeparsowania stron SPA (aczkolwiek z tych dane zazwyczaj pobiera się bezpośrednio przez API wystawione przez twórców aplikacji!).
JSoup poradzi więc sobie świetnie ze stronami generowanymi na serwerze, gorzej ze stronami generowanymi u klienta.