Как написать парсер на Java для сбора данных с сайта

Парсер на Java скачивает страницу, достает из нее поля и сохраняет результат. Для статичного HTML берите Jsoup. Для страниц, которые рисует JavaScript, нужны HtmlUnit или Playwright. Если сайт отдает данные JSON-ом, хватит HttpClient и Jackson. Все примеры работают на учебных сайтах: подключение библиотеки, разбор страницы, обход страниц, сохранение в CSV и JSON, XML и правила, которые не дают нарушить закон.

Чем парсить сайты на Java: Jsoup, HtmlUnit, Selenium и Playwright

Четыре библиотеки закрывают почти любую задачу. Они различаются главным: исполняют ли JavaScript и нужен ли браузер. Каждая карточка начинается с этого вопроса.

Jsoup

Это основной html parser java для статичных страниц. Jsoup скачивает HTML, строит дерево и ищет элементы по CSS-селекторам. JavaScript он не исполняет, поэтому остается самой легкой из четырех библиотек. Для большинства сайтов с обычной версткой достаточно его одного.

Что умеетТолько HTML, без JavaScript
Скорость и памятьСамый быстрый старт, памяти нужно минимум
Нужен браузерНет
Координаты Mavenorg.jsoup:jsoup:1.23.2
Когда братьНужные данные есть в HTML страницы

HtmlUnit

HtmlUnit – браузер без окна, написанный на Java. Он исполняет JavaScript своим движком и ищет элементы через XPath. Ставить Chrome не нужно. Страницы с тяжелыми современными скриптами он иногда не осиливает.

Что умеетHTML и JavaScript своим движком
Скорость и памятьМедленнее Jsoup, но легче настоящего браузера
Нужен браузерНет
Координаты Mavenorg.htmlunit:htmlunit:5.6.0
Когда братьНужен JavaScript, но логика страницы простая

Selenium

Selenium управляет настоящим браузером: открывает страницы, жмет кнопки, заполняет формы. Он нужен, когда без действий пользователя данные не появятся. Платой за это становятся память и скорость: на каждый запуск поднимается целый браузер.

Что умеетHTML, JavaScript и действия пользователя в настоящем браузере
Скорость и памятьМедленный старт, памяти нужно много
Нужен браузерДа, и драйвер к нему
Координаты Mavenorg.seleniumhq.selenium:selenium-java:4.51.0
Когда братьНужны клики и формы, либо Selenium уже есть в проекте

Playwright for Java

Playwright for Java тоже запускает настоящий браузер и работает с Chromium, Firefox и WebKit. Ожидания у него встроены в сам API: метод ждет элемент и продолжает работу. В Selenium ожидания тоже есть, но их задают явно, через WebDriverWait. Для нового проекта со сложным JavaScript Playwright обычно удобнее, а в проекте, где тесты уже написаны на Selenium, переходить необязательно.

Что умеетHTML, JavaScript и действия пользователя в настоящем браузере
Скорость и памятьМедленный старт, памяти нужно много
Нужен браузерДа, Playwright скачивает его сам
Координаты Mavencom.microsoft.playwright:playwright:1.64.0
Когда братьНовый проект, сложный JavaScript, ожидание элементов

Как выбрать библиотеку по поддержке JavaScript и расходу памяти

Выбирайте по тому, где лежат нужные данные: в готовом HTML, в скрипте или в ответе API.

БиблиотекаJavaScriptПамятьКогда брать
JsoupНетМинимумДанные есть в HTML страницы
HtmlUnitДаСреднеНужен JavaScript, но без браузера
SeleniumДаМногоНужны клики и формы, в проекте уже есть Selenium
PlaywrightДаМногоСложный JavaScript, новый проект

Как подготовить Maven-проект для парсинга сайтов

Поставьте JDK 25 – это актуальная LTS-версия, сборки есть на Adoptium. Среда разработки любая, например IntelliJ IDEA. Создайте Maven-проект и подключите библиотеки.

Каталог всех библиотек и их версий находится на Maven Central.

Вот pom.xml для всех примеров статьи:

<project xmlns="http://maven.apache.org/POM/4.0.0">
  <modelVersion>4.0.0</modelVersion>
  <groupId>demo</groupId>
  <artifactId>java-parser</artifactId>
  <version>1.0</version>

  <properties>
    <maven.compiler.release>17</maven.compiler.release>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.jsoup</groupId>
      <artifactId>jsoup</artifactId>
      <version>1.23.2</version>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>2.22.4</version>
    </dependency>
    <dependency>
      <groupId>org.htmlunit</groupId>
      <artifactId>htmlunit</artifactId>
      <version>5.6.0</version>
    </dependency>
    <dependency>
      <groupId>com.microsoft.playwright</groupId>
      <artifactId>playwright</artifactId>
      <version>1.64.0</version>
    </dependency>
  </dependencies>
</project>

Уровень компиляции 17 выбран потому, что HtmlUnit 5.6.0 требует Java 17. Исходники кладите в src/main/java. Чтобы собрать проект, выполните mvn compile. Если Maven скачал зависимости и написал BUILD SUCCESS, подготовка закончена.

У Jackson есть и ветка 3.x с другими координатами: tools.jackson.core:jackson-databind, ей нужна Java 17. В статье везде ветка 2.x, ее импорты начинаются с com.fasterxml.

Для Gradle подключайте те же координаты строками implementation.

Как изучить страницу перед парсингом

Прежде чем писать код, посмотрите, откуда страница берет данные. Откройте books.toscrape.com – это сайт, сделанный для тренировки парсинга. Нажмите F12, чтобы открыть DevTools в Chrome.

Вкладка Elements показывает дерево страницы. Наведите курсор на название книги, щелкните правой кнопкой и выберите «Проверить». В дереве подсветится нужный тег. Кликните на нем правой кнопкой и выберите Copy → Copy selector: Chrome скопирует CSS-селектор. Для XPath в том же меню есть Copy → Copy XPath.

Скопированный селектор длинный и привязан к одной карточке. Сократите его до общего вида: у карточки книги это article.product_pod, у цены p.price_color. Убедитесь, что селектор находит все карточки: в Elements нажмите Ctrl+F и вставьте его, справа появится число совпадений.

Вкладка Network показывает все запросы страницы. Обновите страницу клавишей F5 и нажмите фильтр Fetch/XHR. Если в списке есть запрос, чей ответ в Preview выглядит как JSON с вашими данными, парсить HTML не нужно: берите адрес этого запроса.

На сайтах с подгрузкой данных такой запрос появляется после загрузки страницы или после прокрутки и нажатия кнопки «Загрузить еще».

Отдельный пример такого API дает учебный сайт quotes.toscrape.com: он отдает цитаты JSON-ом по адресу https://quotes.toscrape.com/api/quotes?page=1.

Быстрая проверка, есть ли данные в готовом HTML: откройте view-source: перед адресом страницы и поищите там название товара. Если название найдено, Jsoup справится. Если названия нет, это повод продолжить поиск: посмотрите дерево в Elements и запросы во вкладке Network. Данные могут приходить скриптом, а могут лежать в другой форме, например в тексте с другим написанием.

Как написать парсер на Java с Jsoup

Парсер HTML на Java с библиотекой Jsoup состоит из трех действий: скачать страницу, выбрать элементы, прочитать поля. Jsoup строит из страницы дерево элементов, а селекторы достают из него нужное. Для примера возьмем первую страницу каталога книг. Начните с записи, в которой хранится одна книга.

import java.math.BigDecimal;

public record Book(String title, BigDecimal price, String url) {
}

Теперь сам парсер. Метод Jsoup.connect() принимает адрес, а userAgent() и timeout() задают представление клиента и ожидание ответа в миллисекундах. Метод get() возвращает Document.

import java.io.IOException;
import java.math.BigDecimal;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;

public class BookParser {

    static final String USER_AGENT = "Mozilla/5.0 (compatible; LearningParser/1.0)";

    public static void main(String[] args) throws IOException {
        Document doc = Jsoup.connect("https://books.toscrape.com/")
                .userAgent(USER_AGENT)
                .timeout(10_000)
                .get();

        for (Element card : doc.select("article.product_pod")) {
            Book book = parseCard(card);
            if (book != null) {
                System.out.println(book);
            }
        }
    }

    static Book parseCard(Element card) {
        Element link = card.selectFirst("h3 a");
        if (link == null) {
            return null;
        }
        String title = link.attr("title");
        String url = link.absUrl("href");

        Element priceTag = card.selectFirst("p.price_color");
        BigDecimal price = priceTag == null ? null : parsePrice(priceTag.text());

        return new Book(title, price, url);
    }

    static BigDecimal parsePrice(String raw) {
        String number = raw.replaceAll("[^0-9.,]", "").replace(",", ".");
        return number.isEmpty() ? null : new BigDecimal(number);
    }
}

Запустите класс. В консоли появятся строки вида Book[title=A Light in the Attic, price=51.77, url=https://books.toscrape.com/catalogue/a-light-in-the-attic_1000/index.html]. Если список пуст, селектор не совпал с версткой: сверьте его в DevTools.

В коде три места, о которые спотыкаются чаще всего.

  • selectFirst() возвращает null, когда элемента нет. Проверка на null не дает упасть парсеру на одной странной карточке.
  • absUrl("href") превращает относительную ссылку в полную. Запись attr("abs:href") делает то же самое. Обычный attr("href") вернул бы catalogue/a-light-in-the-attic_1000/index.html без адреса сайта.
  • Цена приходит строкой со знаком валюты. parsePrice() оставляет цифры и точку, а BigDecimal хранит деньги без ошибок округления, которые есть у double.

Если вы привыкли к XPath, Jsoup понимает и его: метод selectXpath() возвращает те же Elements.

Пагинация и сохранение результатов в CSV и JSON

Каталог занимает 50 страниц по 20 книг, всего 1000. На каждой есть ссылка «next». Парсер идет по ней, пока ссылка не пропадет. Между запросами он ждет секунду, чтобы не нагружать сервер.

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;

public class BookCrawler {

    static final int MAX_PAGES = 5;

    public static void main(String[] args) throws Exception {
        List<Book> books = new ArrayList<>();
        String url = "https://books.toscrape.com/";
        int page = 0;

        while (url != null && page < MAX_PAGES) {
            Document doc = Jsoup.connect(url)
                    .userAgent(BookParser.USER_AGENT)
                    .timeout(10_000)
                    .get();

            for (Element card : doc.select("article.product_pod")) {
                Book book = BookParser.parseCard(card);
                if (book != null) {
                    books.add(book);
                }
            }

            Element next = doc.selectFirst("li.next a");
            url = next == null ? null : next.absUrl("href");
            page++;
            Thread.sleep(1000);
        }

        writeCsv(books, Path.of("books.csv"));
        writeJson(books, new File("books.json"));
        System.out.println("Сохранено книг: " + books.size());
    }

    static void writeCsv(List<Book> books, Path path) throws IOException {
        List<String> lines = new ArrayList<>();
        lines.add("title,price,url");
        for (Book b : books) {
            String price = b.price() == null ? "" : b.price().toPlainString();
            lines.add(quote(b.title()) + "," + price + "," + quote(b.url()));
        }
        Files.write(path, lines, StandardCharsets.UTF_8);
    }

    static String quote(String value) {
        return "\"" + value.replace("\"", "\"\"") + "\"";
    }

    static void writeJson(List<Book> books, File file) throws IOException {
        new ObjectMapper()
                .enable(SerializationFeature.INDENT_OUTPUT)
                .writeValue(file, books);
    }
}

Константа MAX_PAGES ограничивает тестовый прогон пятью страницами. Для полного обхода поставьте 50. После запуска в папке проекта появятся books.csv со 100 строками данных и books.json с массивом объектов title, price и url.

Откройте CSV в Excel или LibreOffice. Если программа не разделила поля сама, выберите при импорте кодировку UTF-8 и запятую как разделитель: тогда названия, цены и ссылки лягут в три колонки.

Названия книг бывают с запятыми и кавычками, поэтому метод quote() заключает значения в кавычки и удваивает внутренние. Jackson сериализует record без дополнительных настроек, а BigDecimal записывает числом.

Если сайт сломается посреди обхода, вы потеряете накопленное.

Для длинных прогонов записывайте результаты после каждой страницы.

Как разобрать JSON на Java, если сайт отдает данные через API

Парсинг json java проще разбора HTML: ответ уже структурирован. Учебный сайт quotes.toscrape.com отдает цитаты по адресу https://quotes.toscrape.com/api/quotes?page=1. Ответ содержит поля has_next, page и массив quotes. У каждой цитаты есть text, tags и author с полем name.

У настоящих сайтов такой адрес находится во вкладке Network, в фильтре Fetch/XHR. Отправьте запрос через java.net.http.HttpClient, он входит в JDK и ничего не требует. Разберите ответ Jackson-ом.

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;

public class QuotesApi {

    public static void main(String[] args) throws Exception {
        HttpClient client = HttpClient.newHttpClient();
        ObjectMapper mapper = new ObjectMapper();
        int page = 1;
        boolean hasNext = true;

        while (hasNext && page <= 3) {
            HttpRequest request = HttpRequest.newBuilder(
                            URI.create("https://quotes.toscrape.com/api/quotes?page=" + page))
                    .header("User-Agent", "Mozilla/5.0 (compatible; LearningParser/1.0)")
                    .timeout(Duration.ofSeconds(10))
                    .GET()
                    .build();

            HttpResponse<String> response =
                    client.send(request, HttpResponse.BodyHandlers.ofString());

            JsonNode root = mapper.readTree(response.body());
            for (JsonNode quote : root.get("quotes")) {
                String author = quote.get("author").get("name").asText();
                String text = quote.get("text").asText();
                System.out.println(author + ": " + text);
            }

            hasNext = root.get("has_next").asBoolean();
            page++;
            Thread.sleep(1000);
        }
    }
}

Запуск напечатает цитаты со строками вроде «Albert Einstein: …». Метод readTree() возвращает дерево JsonNode, по которому удобно идти через get(). Для ненадежных полей используйте path(): он возвращает пустой узел вместо null.

Если структура ответа стабильна, превратите ее в класс. Лишние поля пропускает аннотация @JsonIgnoreProperties(ignoreUnknown = true).

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import java.util.List;

@JsonIgnoreProperties(ignoreUnknown = true)
public record Quote(String text, Author author, List<String> tags) {

    @JsonIgnoreProperties(ignoreUnknown = true)
    public record Author(String name) {
    }
}

Теперь массив читается одной строкой: mapper.convertValue(root.get("quotes"), new TypeReference<List<Quote>>() {}). Класс TypeReference лежит в пакете com.fasterxml.jackson.core.type.

Динамические страницы: HtmlUnit с JavaScript и Playwright for Java

На странице quotes.toscrape.com/js цитат в исходном HTML нет, их рисует скрипт. Jsoup увидит пустую страницу. Два способа получить данные: движок HtmlUnit или настоящий браузер под управлением Playwright.

HtmlUnit с включенным JavaScript

Класс WebClient открывает страницу и исполняет скрипты. Метод waitForBackgroundJavaScript() ждет, пока фоновые скрипты закончат работу. Пакеты в HtmlUnit начинаются с org.htmlunit, старые com.gargoylesoftware остались в версиях 2.x.

import java.util.List;
import org.htmlunit.WebClient;
import org.htmlunit.html.DomNode;
import org.htmlunit.html.HtmlPage;

public class QuotesHtmlUnit {

    public static void main(String[] args) throws Exception {
        try (WebClient client = new WebClient()) {
            client.getOptions().setCssEnabled(false);
            client.getOptions().setJavaScriptEnabled(true);
            client.getOptions().setThrowExceptionOnScriptError(false);

            HtmlPage page = client.getPage("https://quotes.toscrape.com/js/");
            client.waitForBackgroundJavaScript(5_000);

            List<DomNode> quotes = page.getByXPath("//div[@class='quote']");
            for (DomNode quote : quotes) {
                DomNode text = quote.getFirstByXPath(".//span[@class='text']");
                DomNode author = quote.getFirstByXPath(".//small[@class='author']");
                System.out.println(author.asNormalizedText() + ": " + text.asNormalizedText());
            }
        }
    }
}

Метод asNormalizedText() возвращает видимый текст узла, а asXml() отдает его разметку. Старого asText() в текущем API нет. Если вывод пуст, увеличьте ожидание в waitForBackgroundJavaScript(). Опция setThrowExceptionOnScriptError(false) не дает парсеру упасть на скрипте, который движок HtmlUnit не понял.

HtmlUnit ломается на сайтах с самыми новыми возможностями JavaScript. Тогда он молча возвращает недорисованную страницу.

Playwright for Java

Playwright запускает настоящий Chromium. Перед первым запуском установите браузеры командой mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="install". Она скачает Chromium, Firefox и WebKit.

import com.microsoft.playwright.Browser;
import com.microsoft.playwright.Page;
import com.microsoft.playwright.Playwright;
import java.util.List;

public class QuotesPlaywright {

    public static void main(String[] args) {
        try (Playwright playwright = Playwright.create()) {
            Browser browser = playwright.chromium().launch();
            Page page = browser.newPage();

            page.navigate("https://quotes.toscrape.com/js/");
            page.waitForSelector(".quote");

            List<String> texts = page.locator(".quote .text").allInnerTexts();
            texts.forEach(System.out::println);

            browser.close();
        }
    }
}

Метод waitForSelector() ждет, пока на странице появится цитата, поэтому подбирать паузы не нужно. Метод allInnerTexts() вернет список текстов всех найденных элементов.

В консоли появятся тексты цитат первой страницы.

Playwright ломается там, где не хватает ресурсов: каждый запуск поднимает целый браузер. Если нужные данные приходят JSON-ом, не запускайте браузер совсем: берите API через HttpClient.

Парсеры XML Java: DOM, SAX и StAX на примере sitemap.xml

Файл sitemap.xml содержит список адресов сайта и годится как удобный источник для обхода. Java xml parser пример проще всего показать на нем. В JDK есть три парсера xml java, и разница между ними в памяти и в удобстве.

  • DOM загружает документ в дерево целиком. Работать с ним легко, но большой файл займет много памяти.
  • SAX читает файл потоком и сообщает о событиях: начало тега, текст, конец тега. Памяти он почти не тратит, но код приходится писать через обработчик.
  • StAX тоже потоковый, но читает файл сам программист: он вызывает next(), когда нужно. Код выходит короче, чем у SAX.

Формат sitemap задан протоколом sitemaps.org: корень urlset, внутри элементы url, у каждого обязательный loc с адресом. Создайте в корне проекта файл sitemap.xml с таким содержимым:

<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  <url>
    <loc>https://books.toscrape.com/catalogue/page-1.html</loc>
    <lastmod>2025-01-01</lastmod>
  </url>
  <url>
    <loc>https://books.toscrape.com/catalogue/page-2.html</loc>
    <lastmod>2025-01-01</lastmod>
  </url>
</urlset>

Вот java sax parser пример на DefaultHandler. Обработчик собирает текст внутри тегов loc и складывает адреса в список. Параметр disallow-doctype-decl запрещает объявления DOCTYPE и закрывает атаки через внешние сущности: чужой файл может быть недоверенным.

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.xml.sax.Attributes;
import org.xml.sax.helpers.DefaultHandler;

public class SitemapSax {

    public static void main(String[] args) throws Exception {
        SAXParserFactory factory = SAXParserFactory.newInstance();
        factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
        SAXParser parser = factory.newSAXParser();

        List<String> urls = new ArrayList<>();

        parser.parse(new File("sitemap.xml"), new DefaultHandler() {
            private StringBuilder buffer;

            @Override
            public void startElement(String uri, String localName, String qName,
                                     Attributes attributes) {
                if ("loc".equals(qName)) {
                    buffer = new StringBuilder();
                }
            }

            @Override
            public void characters(char[] ch, int start, int length) {
                if (buffer != null) {
                    buffer.append(ch, start, length);
                }
            }

            @Override
            public void endElement(String uri, String localName, String qName) {
                if ("loc".equals(qName)) {
                    urls.add(buffer.toString().trim());
                    buffer = null;
                }
            }
        });

        urls.forEach(System.out::println);
    }
}

В консоли появятся два адреса из файла.

Буфер нужен потому, что метод characters() может получить текст тега кусками.

Тот же результат на DOM занимает шесть строк:

import java.io.File;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;

public class SitemapDom {

    public static void main(String[] args) throws Exception {
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
        Document doc = factory.newDocumentBuilder().parse(new File("sitemap.xml"));

        NodeList locs = doc.getElementsByTagName("loc");
        for (int i = 0; i < locs.getLength(); i++) {
            System.out.println(locs.item(i).getTextContent().trim());
        }
    }
}

И на StAX:

import java.io.FileInputStream;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamReader;

public class SitemapStax {

    public static void main(String[] args) throws Exception {
        XMLInputFactory factory = XMLInputFactory.newFactory();
        factory.setProperty(XMLInputFactory.SUPPORT_DTD, false);
        try (FileInputStream in = new FileInputStream("sitemap.xml")) {
            XMLStreamReader reader = factory.createXMLStreamReader(in);
            while (reader.hasNext()) {
                if (reader.next() == XMLStreamConstants.START_ELEMENT
                        && "loc".equals(reader.getLocalName())) {
                    System.out.println(reader.getElementText().trim());
                }
            }
            reader.close();
        }
    }
}

Для sitemap на несколько тысяч адресов хватит DOM. Файлы на сотни мегабайт читайте SAX или StAX. Адрес sitemap у реальных сайтов обычно указан в robots.txt строкой Sitemap.

Правила парсинга, нагрузка на сайт и персональные данные

Парсер – автоматический клиент чужого сервера, и вести себя он должен прилично. Правила ниже защищают и сервер, и вас.

Начните с robots.txt. Файл лежит в корне сайта, например site.ru/robots.txt, и перечисляет разделы, куда роботам нельзя, а иногда и допустимую частоту запросов. Читайте его до написания парсера и не заходите туда, где стоит запрет. У учебных сайтов toscrape этого файла нет, у настоящих магазинов он есть почти всегда.

Дальше ограничьте нагрузку. Пауза в одну секунду между запросами, как в примере, уже снижает риск, что сервер сочтет вас атакой. Не запускайте десятки потоков на один сайт. Укажите в User-Agent название парсера, чтобы владелец сайта мог понять, кто приходит, и написать вам.

Прочитайте условия использования сайта. Многие магазины и доски объявлений прямо запрещают автоматический сбор.

Поэтому в статье примеры взяты с учебных сайтов, а не с реальных площадок.

Отдельно о базах данных. По статье 1334 ГК РФ изготовитель базы данных владеет исключительным правом на нее. Закон называет извлечением перенос всего содержания базы или существенной части составляющих ее материалов на другой носитель. Именно такое извлечение и последующее использование материалов требует разрешения правообладателя, кроме случаев, предусмотренных Гражданским кодексом. Парсер, который выгружает весь каталог или большую его часть, делает как раз это.

Пока не доказано иное, база не менее чем из 10 000 самостоятельных элементов считается созданной с существенными затратами. Каталог крупного магазина может подходить под такое описание, поэтому массовую выгрузку чужого каталога согласуйте с владельцем. Границы права в конкретном случае определяет юрист, а не парсер.

Персональные данные требуют особой осторожности. По статье 3 закона 152-ФЗ это любая информация, относящаяся к прямо или косвенно определенному физическому лицу: имя, телефон, email, фото, профиль в соцсети. То, что человек сам выложил на сайте, не делает данные свободными.

Статья 10.1 требует отдельного согласия на распространение таких данных, а молчание согласием не считается. Часть 5 статьи 18 запрещает хранить собранные данные граждан России в базах за границей, с редкими исключениями.

За нарушение статья 13.11 КоАП РФ грозит юридическому лицу штрафом от 150 000 до 300 000 ₽ по части 1. Поэтому не собирайте email-адреса, телефоны и профили людей, а в учебных проектах берите только данные о товарах и цитатах.

Если сайт отвечает ошибками 429 или 403, уменьшите частоту запросов или обратитесь к владельцу.

Обходить защиту не нужно: это нарушает условия сайта и ничего не решает надолго.

Какую библиотеку взять под задачу

  • Если данные есть в HTML страницы, берите Jsoup.
  • Когда в Network виден ответ JSON, хватит HttpClient и Jackson, браузер не нужен.
  • Страницу рисует JavaScript, но логика простая: подойдет HtmlUnit.
  • Для кликов, ожидания элементов и сложных скриптов запускайте Playwright.
  • Чтобы обойти все адреса сайта, разберите sitemap.xml и пройдите по списку loc.

Ошибки при парсинге сайтов на Java

Большинство сбоев повторяются из проекта в проект.

  • NullPointerException на отсутствующем элементе. Причина в том, что selectFirst() или getFirstByXPath() вернул null. Проверяйте результат до обращения к нему.
  • Кракозябры вместо русских букв. Укажите кодировку явно: в Jsoup есть Jsoup.parse(InputStream, "windows-1251", baseUri), в проекте задайте UTF-8 в project.build.sourceEncoding и при записи файлов.
  • Ссылки без адреса сайта. Берите absUrl("href") вместо attr("href").
  • Цена с пробелами, запятой и значком валюты, например «1 299,00 ₽». Удалите все кроме цифр и разделителя, замените запятую на точку и передайте в BigDecimal.
  • Парсер видит пустой HTML, хотя в браузере данные есть. Страницу рисует JavaScript. Посмотрите view-source и вкладку Network, затем перейдите на API, HtmlUnit или Playwright.
  • Бан за частые запросы. Увеличьте паузу, сократите число страниц за прогон и сохраняйте результат по ходу работы.

Сначала найдите во вкладке Network запрос с JSON, и только потом пишите парсер HTML: код для API короче и не ломается от смены верстки.

Комментарии

Оставьте свой комментарий
Ильдар К.

Позор!
Чел даже ссылку в searchUrl не мог нормально поместить.

ВК
Владимир К.

Ошибки в изначальной статье и в этом переводе.
Вот эта строчка даже компиляцию не пройдет:
List<HtmlElement> items = (List<HtmlElement>) page.getByXPath("//p[@class='result-info']" ;

Д

добавь скобку и всего делов )

М
Михаил

...огромная просьба к г.г. программистам - выносить юмор за скобки.
И так ни хрена не понятно...