JSoup mini-tutorial – parsowanie HTML w środowisku java

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:

  1. Tworzymy projekt javovy
  2. Z wyżej wymienionej strony pobieramy plik jsoup-X.X.X.jar
  3. 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
  4. Dodajemy jar do projektu w Eclipse.
    1. Z menu kontekstowego wybieramy Project -> Properties
    2. W lewym menu wybieramy Java Build Path
    3. Przechodzimy do zakładki Libraries
    4. Naciskamy Add External JARs i znajdujemy nasz plik na dysku.
    5. Naciskamy OK

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 do attr("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 elementu
  • text() – zwraca wewnętrzny tekst, czyli html() pozbawiony znaczników. Będzie zawierał tekst swój, jak i elementów dzieci.
  • parent() – zwraca element rodzica
  • tag() – zwraca obiekt klasy Tag, reprezentujący dany element. Tag reprezentuje typ elementu – a, div, p, …
  • tagName() – Zwraca nazwę tagu, czyli analogicznie do tag().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.

Dodaj komentarz

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