В этом руководстве мы рассмотрим следующее:
Что такое Jsoup?
Jsoup представляет собой HTML-парсер на Java. Другими словами, Jsoup — Java-библиотека, позволяющая парсить любой HTML-документ. С помощью Jsoup можно выполнить веб-скрапинг локального HTML-файла или загрузить HTML-документ по URL.
Jsoup также предлагает обширный набор методов для работы с DOM. В частности, можно использовать CSS-селекторы и Jquery-подобные методы для выделения нужных HTML-элементов и извлечения из них данных. Это делает Jsoup эффективной Java-библиотекой для веб-скрапинга как для новичков, так и для профессионалов.
Отметим, что Jsoup — не единственная библиотека для выполнения веб-скрапинга на Java. HtmlUnit — еще одна популярная Java-библиотека для парсинга. Ознакомьтесь с нашим руководством по HtmlUnit, посвященным веб-скрапингу на Java.
Предустановки
Прежде чем написать первую строчку кода, необходимо выполнить перечисленные ниже условия:
- Java >= 8: подойдет любая версия Java, начиная с 8-й. Рекомендуем загрузить и установить версию Java LTS (Long Term Support). В частности, данный учебник основан на Java 17. На момент написания статьи Java 17 является последней LTS-версией Java.
- Maven или Gradle: вы можете выбрать любой инструмент автоматизации сборки Java-проектов, который вам больше нравится. В частности, Maven или Gradle понадобятся для управления зависимостями.
- Продвинутая IDE с поддержкой Java: подойдет любая IDE, поддерживающая Java с Maven или Gradle. В данном руководстве используется IntelliJ IDEA, которая, вероятно, является лучшей из существующих Java IDE.
По указанным выше ссылкам можно выполнить все необходимые предустановки. По порядку загрузите Java, Maven или Gradle, а также IDE для Java. Следуйте официальным руководствам по установке, чтобы избежать распространенных проблем и неполадок.
Теперь проверим успешность выполненных предустановок.
Убедитесь, что Java настроена правильно
Откройте терминал. Убедитесь в том, что вы установили и правильно добавили Java в PATH. Это можно сделать помощью следующей команды:
java -version
В результате выполнения этой команды должно быть выведено что-то вроде этого:
java version "17.0.5" 2022-10-18 LTS
Java(TM) SE Runtime Environment (build 17.0.5+9-LTS-191)
Java HotSpot(TM) 64-Bit Server VM (build 17.0.5+9-LTS-191, mixed mode, sharing)
Убедитесь, что установлен Maven или Gradle
Если вы выбрали Maven, выполните в терминале следующую команду:
mvn -v
Вы должны получить информацию о версии Maven, которую настроили, следующим образом:
Apache Maven 3.8.6 (84538c9988a25aec085021c365c560670ad80f63)
Maven home: C:\Maven\apache-maven-3.8.6
Java version: 17.0.5, vendor: Oracle Corporation, runtime: C:\Program Files\Java\jdk-17.0.5
Default locale: en_US, platform encoding: Cp1252
OS name: "windows 11", version: "10.0", arch: "amd64", family: "windows"
Если же вы выбрали Gradle, то выполните в терминале эту команду:
gradle -v
Аналогичным образом необходимо получить информацию о версии установленного Gradle, как показано ниже:
------------------------------------------------------------
Gradle 7.5.1
------------------------------------------------------------
Build time: 2022-08-05 21:17:56 UTC
Revision: d1daa0cbf1a0103000b71484e1dbfe096e095918
Kotlin: 1.6.21
Groovy: 3.0.10
Ant: Apache Ant(TM) version 1.10.11 compiled on July 10 2021
JVM: 17.0.5 (Oracle Corporation 17.0.5+9-LTS-191)
OS: Windows 11 10.0 amd64
Теперь вы готовы к тому, чтобы узнать, как выполнять веб-скрапинг с помощью Jsoup на языке Java!
Как создать веб-скрапер с помощью Jsoup
Отсюда вы узнаете, как создать скрипт для веб-скрапинга при помощи Jsoup. Он сможет автоматически извлекать данные с любого сайта. Возьмем для примера Quotes to Scrape — так называемую, песочницу для веб-скрапинга.
Вот как выглядит Quotes to Scrape:
Как видно, целевой сайт содержит постраничный список цитат. Задача веб-скрапера Jsoup — пройтись по каждой странице, извлечь их и вернуть эти данные в формате CSV.
Итак, следуйте этому пошаговому руководству по Jsoup и узнайте, как создать простой веб-парсер!
Шаг 1: Создание Java-проекта
Здесь мы рассмотрим, как инициализировать Java-проект в IntelliJ IDEA 2022.2.3. Заметим, что подойдет и любая другая IDE. В IntelliJ IDEA для настройки Java-проекта требуется всего несколько щелчков мышью. Запустите IntelliJ IDEA и дождитесь ее загрузки. Затем в верхнем меню выберите пункт File > New > Project....
Теперь инициализируйте свой Java-проект во всплывающем окне New Project следующим образом:
Укажите название и местоположение проекта, выберите Java в качестве языка программирования и выберите Maven или Gradle в зависимости от установленного инструмента сборки. Нажмите кнопку Create и подождите, пока IntelliJ IDEA инициализирует ваш Java-проект. Теперь вы должны увидеть следующий пустой Java-проект:
Настало время установить Jsoup и приступить к сбору веб-данных!
Шаг 2: Установка Jsoup
Если вы являетесь пользователем Maven, добавьте приведенные ниже строки в тег dependencies
вашего файла pom.xml:
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.15.3</version>
</dependency>
Теперь ваш файл Maven pom.xml
должен выглядеть следующим образом:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.brightdata</groupId>
<artifactId>web-scraper-jsoup</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.15.3</version>
</dependency>
</dependencies>
</project>
Если же вы пользуетесь Gradle, добавьте эту строку в объект dependencies
вашего файла build.gradle
:
implementation "org.jsoup:jsoup:1.15.3"
Вы только что добавили Jsoup
в зависимости своего проекта. Теперь настало время установить его. В IntelliJ IDEA нажмите на кнопку перезагрузки Gradle/Maven, расположенную ниже:
В результате будет установлена зависимость Jsoup
. Дождитесь окончания процесса установки. Теперь у вас есть доступ ко всем возможностям Jsoup. Убедиться в том, что Jsoup был установлен правильно, можно, добавив эту строку импорта в верхнюю часть файла Main.java
:
import org.jsoup.*;
Если IntelliJ IDEA не сообщает об ошибке, это значит, что теперь вы можете использовать Jsoup в своем веб-парсере на Java.
Теперь давайте создадим веб-скрапер с помощью Jsoup!
Шаг 3: Подключение к целевой странице
С помощью Jsoup можно подключиться к целевому сайту с помощью всего одной строки кода:
// downloading the target website with an HTTP GET request
Document doc = Jsoup.connect("https://quotes.toscrape.com/").get();
Подключиться к сайту можно благодаря методу Jsoup connect()
. Он работает следующим образом: Jsoup выполняет HTTP GET-запрос к URL, указанному в качестве параметра, получает HTML-документ, возвращенный целевым сервером, и сохраняет его в объекте Jsoup-класса Document
под названием doc
.
Следует помнить, что при неудачном подключении функции connect()
Jsoup выдаст сообщение об ошибке IOException. Это может произойти по нескольким причинам. Однако следует знать, что многие сайты блокируют запросы, не содержащие корректного заголовка User-Agent
. Если вы не знакомы с этим понятием, то заголовок User-Agent
представляет собой строковое значение, идентифицирующее приложение и версию ОС, от которой исходит запрос. Подробнее о User-Agent
для веб-скрапинга.
В Jsoup
можно указать заголовок User-Agent
следующим образом:
Document doc = Jsoup
.connect("https://quotes.toscrape.com/")
.userAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36")
.get();
В частности, метод Jsoup userAgent()
позволяет установить заголовок User-Agent
. Заметим, что через метод header()
можно задать значение любого другого HTTP-заголовка.
Теперь ваш класс Main.java
должен выглядеть следующим образом:
package com.brightdata;
import org.jsoup.*;
import org.jsoup.nodes.*;
import java.io.IOException;
public class Main {
public static void main(String[] args) throws IOException {
// downloading the target website with an HTTP GET request
Document doc = Jsoup
.connect("https://quotes.toscrape.com/")
.userAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36")
.get();
}
}
Приступим к анализу целевого сайта, чтобы научиться извлекать из него данные.
Шаг 4: Анализ HTML-страницы
Если вы хотите извлечь данные из HTML-документа, сначала необходимо проанализировать HTML-код страницы. Сперва нужно определить HTML-элементы, содержащие данные, которые требуют извлечения. Затем понадобится найти способ выделения этих HTML-элементов.
Все это можно сделать с помощью инструментов разработчика вашего браузера. В Google Chrome или любом другом браузере на базе Chromium щелкните правой кнопкой мыши на HTML-элементе, содержащем интересующие вас данные. Затем выберите пункт Inspect.
Вот что вы должны теперь увидеть:
Покопавшись в HTML-коде, можно увидеть, что каждая цитата обернута в HTML-элемент <div>
. При более подробном осмотре можно увидеть, что этот элемент содержит:
- HTML-элемент
<span>
содержит текст цитаты - HTML-элемент
<small>
содержит имя автора - Элемент
<div>
со списком HTML-элементов<a>
содержит теги, связанные с цитатой.
Теперь посмотрите на CSS-классы, используемые этими HTML-элементами. Благодаря им можно определить CSS-селекторы, необходимые для извлечения этих HTML-элементов из DOM. В частности, можно получить все данные, связанные с цитатой, применив CSS-селекторы для .quote
, приведенные ниже:
.text
.author
.tags .tag
Теперь давайте узнаем, как это можно сделать в Jsoup.
Шаг 5: Выбор HTML-элементов с помощью Jsoup
Класс Jsoup Document
предлагает несколько способов выбора HTML-элементов из DOM. Рассмотрим наиболее важные из них.
Jsoup позволяет извлекать HTML-элементы на основе их тегов:
// selecting all <div> HTML elements
Elements divs = doc.getElementsByTag("div");
Это вернет список HTML-элементов <div>
, содержащихся в DOM.
Аналогичным образом можно выбирать элементы HTML по классам:
// getting the ".quote" HTML element
Elements quotes = doc.getElementsByClass("quote");
Если необходимо получить отдельный HTML-элемент по его атрибуту id
, можно использовать:
// getting the "#quote-1" HTML element
Element div = doc.getElementById("quote-1");
Можно также выбирать элементы HTML по атрибуту:
// selecting all HTML elements that have the "value" attribute
Elements htmlElements = doc.getElementsByAttribute("value");
Или содержащие определенный фрагмент текста:
// selecting all HTML elements that contain the word "for"
Elements htmlElements = doc.getElementsContainingText("for");
Это лишь несколько примеров. Следует помнить, что Jsoup предлагает более 20 различных подходов к выбору HTML-элементов на странице. Ознакомьтесь с ними.
Как уже было сказано, CSS-селекторы являются эффективным способом выбора HTML-элементов. Вы можете применить CSS-селектор для получения элементов в Jsoup с помощью метода select()
:
// selecting all quote HTML elements
Elements quoteElements = doc.getElementsByClass(".quote");
Так как Elements
расширяет ArrayList
, вы можете выполнять итерации по нему, чтобы получить каждый Element
. Обратите внимание, что все методы выбора HTML можно применить и к одному Element
. При этом логика выбора будет ограничена дочерними элементами выбранного HTML-элемента.
Таким образом, вы можете выбрать нужные HTML-элементы в каждом .quote
, как показано ниже:
for (Element quoteElement: quoteElements) {
Element text = quoteElement.select(".text").first();
Element author = quoteElement.select(".author").first();
Elements tags = quoteElement.select(".tag");
}
Теперь научимся извлекать данные из этих HTML-элементов.
Шаг 6. Извлечение данных со страницы с помощью Jsoup
Для начала необходимо создать Java-класс, в котором будут храниться полученные данные. Создайте файл Quote.java
в пакете main и инициализируйте его следующим образом:
package com.brightdata;
package com.brightdata;
public class Quote {
private String text;
private String author;
private String tags;
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public String getTags() {
return tags;
}
public void setTags(String tags) {
this.tags = tags;
}
}
Теперь расширим фрагмент, представленный в конце предыдущего раздела. Извлечем нужные данные из выбранных HTML-элементов и сохраним их в объектах quote
следующим образом:
// initializing the list of Quote data objects
// that will contain the scraped data
List<Quote> quotes = new ArrayList<>();
// retrieving the list of product HTML elements
// selecting all quote HTML elements
Elements quoteElements = doc.select(".quote");
// iterating over the quoteElements list of HTML quotes
for (Element quoteElement : quoteElements) {
// initializing a quote data object
Quote quote = new Quote();
// extracting the text of the quote and removing the
// special characters
String text = quoteElement.select(".text").first().text()
.replace("“", "")
.replace("”", "");
String author = quoteElement.select(".author").first().text();
// initializing the list of tags
List<String> tags = new ArrayList<>();
// iterating over the list of tags
for (Element tag : quoteElement.select(".tag")) {
// adding the tag string to the list of tags
tags.add(tag.text());
}
// storing the scraped data in the Quote object
quote.setText(text);
quote.setAuthor(author);
quote.setTags(String.join(", ", tags)); // merging the tags into a "A, B, ..., Z" string
// adding the Quote object to the list of the scraped quotes
quotes.add(quote);
}
Поскольку каждый объект quote может содержать больше одного тега, можно хранить их все в Java List
. Затем с помощью метода String.join()
можно сократить список строк до всего одной. Наконец, эту строку можно сохранить в объекте quote
.
В конце цикла for
коллекция quotes
будет содержать все данные из объектов quote, извлеченные с главной страницы целевого сайта. Однако целевой сайт состоит из множества страниц!
Давайте узнаем, как использовать Jsoup для парсинга целого сайта.
Шаг 7: Как парсить сайт с помощью Jsoup
Если внимательно посмотреть на главную страницу сайта Quotes to Scrape, можно заметить кнопку «Далее». Осмотрите этот HTML-элемент с помощью инструментов разработчика браузера. Щелкните по нему правой кнопкой мыши и выберите пункт Inspect.
Здесь можно заметить, что кнопка «Далее» является HTML-элементом <li>
. Он в свою очередь содержит HTML-элемент <a>
, в котором хранится относительный URL следующей страницы. Обратите внимание, что кнопку «Далее» можно найти на всех страницах целевого сайта, кроме последней. Такого подхода придерживается большинство страничных сайтов.
Извлекая ссылку, хранящуюся в этом HTML-элементе <a>
, можно получить следующую страницу для сканирования. Таким образом, если вы хотите отсканировать весь сайт, следуйте приведенной ниже логике:
- Поиск HTML-элемента
.next
- если он присутствует, извлеките относительный URL, содержащийся в его дочернем
<a>
, и переходите к пункту 2. - если нет, то это последняя страница, и на ней можно остановиться
- если он присутствует, извлеките относительный URL, содержащийся в его дочернем
- Добавьте относительный URL, извлекаемый из HTML-элемента
<a>
, к базовому URL сайта - Используйте полный URL-адрес для подключения к новой странице
- Парсите данные с новой страницы
- Переходите к пункту 1.
Именно в этом и заключается суть веб-парсинга. С помощью Jsoup можно обойти многостраничный сайт следующим образом:
// the URL of the target website's home page
String baseUrl = "https://quotes.toscrape.com";
// initializing the list of Quote data objects
// that will contain the scraped data
List<Quote> quotes = new ArrayList<>();
// retrieving the home page...
// looking for the "Next →" HTML element
Elements nextElements = doc.select(".next");
// if there is a next page to scrape
while (!nextElements.isEmpty()) {
// getting the "Next →" HTML element
Element nextElement = nextElements.first();
// extracting the relative URL of the next page
String relativeUrl = nextElement.getElementsByTag("a").first().attr("href");
// building the complete URL of the next page
String completeUrl = baseUrl + relativeUrl;
// connecting to the next page
doc = Jsoup
.connect(completeUrl)
.userAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36")
.get();
// scraping logic...
// looking for the "Next →" HTML element in the new page
nextElements = doc.select(".next");
}
Как видите, логику обхода, описанную выше, можно реализовать с помощью простого цикла while
. Он занимает всего несколько строк кода. В частности, необходимо воспользоваться подходом do ... while
.
Теперь вы умеете парсить многостраничный сайт. Осталось только научиться преобразовывать собранные данные в более удобный формат.
Шаг 8: Экспорт полученных данных в CSV
Преобразовать полученные данные в CSV-файл можно следующим образом:
// initializing the output CSV file
File csvFile = new File("output.csv");
// using the try-with-resources to handle the
// release of the unused resources when the writing process ends
try (PrintWriter printWriter = new PrintWriter(csvFile)) {
// iterating over all quotes
for (Quote quote : quotes) {
// converting the quote data into a
// list of strings
List<String> row = new ArrayList<>();
// wrapping each field with between quotes
// to make the CSV file more consistent
row.add("\"" + quote.getText() + "\"");
row.add("\"" +quote.getAuthor() + "\"");
row.add("\"" +quote.getTags() + "\"");
// printing a CSV line
printWriter.println(String.join(",", row));
}
}
Этот фрагмент преобразует quote-формат данных в CSV и сохраняет их в файле output.csv
. Как видите, для этого не требуется какой-либо дополнительной зависимости. Достаточно инициализировать CSV-файл с помощью File
. Затем можно заполнить его с помощью PrintWriter
, распечатав каждый quote в виде строки в формате CSV в файле output.csv
.
Обратите внимание, что PrintWriter
всегда следует закрывать, когда он больше не нужен. В частности, приведенная выше конструкция try-with-resources
гарантирует, что экземпляр PrintWriter будет закрыт в конце блока try
.
Вы начали с навигации по сайту и теперь можете извлечь его данные, чтобы сохранить их в удобном CSV-файле. Настало время взглянуть на полноценный веб-парсер Jsoup.
Собираем все воедино
Вот как выглядит готовый код веб-парсера Jsoup на языке Java:
package com.brightdata;
import org.jsoup.*;
import org.jsoup.nodes.*;
import org.jsoup.select.Elements;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) throws IOException {
// the URL of the target website's home page
String baseUrl = "https://quotes.toscrape.com";
// initializing the list of Quote data objects
// that will contain the scraped data
List<Quote> quotes = new ArrayList<>();
// downloading the target website with an HTTP GET request
Document doc = Jsoup
.connect(baseUrl)
.userAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36")
.get();
// looking for the "Next →" HTML element
Elements nextElements = doc.select(".next");
// if there is a next page to scrape
while (!nextElements.isEmpty()) {
// getting the "Next →" HTML element
Element nextElement = nextElements.first();
// extracting the relative URL of the next page
String relativeUrl = nextElement.getElementsByTag("a").first().attr("href");
// building the complete URL of the next page
String completeUrl = baseUrl + relativeUrl;
// connecting to the next page
doc = Jsoup
.connect(completeUrl)
.userAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36")
.get();
// retrieving the list of product HTML elements
// selecting all quote HTML elements
Elements quoteElements = doc.select(".quote");
// iterating over the quoteElements list of HTML quotes
for (Element quoteElement : quoteElements) {
// initializing a quote data object
Quote quote = new Quote();
// extracting the text of the quote and removing the
// special characters
String text = quoteElement.select(".text").first().text();
String author = quoteElement.select(".author").first().text();
// initializing the list of tags
List<String> tags = new ArrayList<>();
// iterating over the list of tags
for (Element tag : quoteElement.select(".tag")) {
// adding the tag string to the list of tags
tags.add(tag.text());
}
// storing the scraped data in the Quote object
quote.setText(text);
quote.setAuthor(author);
quote.setTags(String.join(", ", tags)); // merging the tags into a "A; B; ...; Z" string
// adding the Quote object to the list of the scraped quotes
quotes.add(quote);
}
// looking for the "Next →" HTML element in the new page
nextElements = doc.select(".next");
}
// initializing the output CSV file
File csvFile = new File("output.csv");
// using the try-with-resources to handle the
// release of the unused resources when the writing process ends
try (PrintWriter printWriter = new PrintWriter(csvFile, StandardCharsets.UTF_8)) {
// to handle BOM
printWriter.write('\ufeff');
// iterating over all quotes
for (Quote quote : quotes) {
// converting the quote data into a
// list of strings
List<String> row = new ArrayList<>();
// wrapping each field with between quotes
// to make the CSV file more consistent
row.add("\"" + quote.getText() + "\"");
row.add("\"" +quote.getAuthor() + "\"");
row.add("\"" +quote.getTags() + "\"");
// printing a CSV line
printWriter.println(String.join(",", row));
}
}
}
}
Как здесь показано, веб-парсер можно реализовать с помощью Java менее чем за 100 строк кода. Благодаря Jsoup можно подключиться к сайту, полностью обойти его и автоматически извлечь все необходимые данные. Затем выполняется запись полученной информации в CSV-файл. Именно для этого и предназначен веб-парсер Jsoup.
В IntelliJ IDEA запустите скрипт Jsoup для веб-скрапинга, нажав на кнопку ниже:
IntelliJ IDEA скомпилирует файл Main.java
и выполнит класс Main. По окончании процесса парсинга в корневом каталоге проекта появится файл output.csv
. Откройте его. Он должен содержать следующие данные:
Теперь у вас есть CSV-файл, содержащий все 100 цитат из Quotes to Scrape! Это означает, что вы только что узнали, как создать веб-парсер с помощью Jsoup!
Заключение
В этом уроке вы узнали, что нужно для создания веб-парсера, что такое Jsoup и как его использовать для сбора данных из интернета. На реальном примере было подробно рассмотрено, как использовать Jsoup для создания приложения для веб-скрапинга. Как вы узнали, создание парсера с помощью Jsoup на Java подразумевает использование относительно небольшого количества кода.
Однако на практике веб-скрапинг не так уж и прост. Это связано с целым рядом проблем, с которыми приходится сталкиваться парсерам. Не стоит забывать, что технологии защиты от ботов и веб-скрапинга сегодня популярны как никогда. Все, что вам нужно, это мощный и многофункциональный инструмент для веб-скрапинга, который предлагает компания Bright Data. Не хотите иметь дело с парсингом? Взгляните на наши готовые наборы данных.
Если вы хотите узнать больше о том, как обходить блокировки, можете выбрать прокси, исходя из своих потребностей среди многочисленных прокси-сервисов, доступных в Bright Data.