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
libi 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 JARsi 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.