Существует много языков программирования. Естественно, практически любой из них можно использовать для web-скрейпинга: Java, Python, Go, C# и т.д. Вопрос не только в личных предпочтениях, но и в наборе уже существующих библиотек и фреймворков, так как последние часто используются для быстрого старта – ускорения написания прикладных программ.
Эта статья не о выборе лучшей платформы для разработки, а о конкретном языке: Java. А точнее о наиболее востребованных библиотеках, которые помогают в создании собственных web-парсеров на Java.
Почему Java является популярным выбором для веб-парсинга
Во-первых, Java является одним из первых кроссплатформенных языков программирования. Благодаря Java-машинам, программный код Java можно запустить практически в любой операционной системе. Все должны помнить игры на кнопочных телефонах. Так вот они были написаны на Java.
Во-вторых, Java – очень популярный язык. Он обладает хорошей производительностью и отлично показывает себя при работе со сложной логикой.
В-третьих, Java предлагает достаточный ассортимент готовых библиотек и фреймворков для парсинга HTML и XML документов.
В-четвёртых, не стоит забывать о многопоточности. Здесь у Java полный порядок. Он обеспечивает эффективное распределение задач между несколькими потоками одновременно и позволяет значительно ускорить процесс сбора данных с большого числа страниц или сайтов.
Обзор популярных библиотек Java для веб-скрапинга
Библиотеки – это набор готовых классов и методов для определённых задач. Так как Java используется в разных сферах, под него создано достаточно специфичных библиотек.
Ниже мы собрали только те, которые помогут вам с созданием парсеров. Начнём с самой популярной и практически незаменимой библиотеки – Jsoup. Это как BeautifulSoup для Python.
Jsoup
Java-библиотека с открытым исходным кодом, которая предназначена для анализа структуры HTML-кода. А ведь именно HTML-код передаётся в ответ на любой HTTP/HTTPS-запрос. Напомним, HTTP расшифровывается как HyperText Transfer Protocol (протокол передачи гипертекста). Благодаря тому, что jsoup поддерживает актуальную спецификацию WHATWG HTML5, вы можете получать и рендерить код страницы так, как это делают современные браузеры (ведь они работают с той же спецификацией).
Разработка библиотеки ведётся с 2009 года. С помощью jsoup можно получать HTML-код с конкретных URL-адресов, а также из файлов, извлекать данные на основе HTML-тегов и CSS-селекторов, устанавливать свои атрибуты, перемещаться по странице, заполнять и отправлять формы, и многое другое. Jsoup имеет хорошо документированный API.
Преимущества
- Открытый исходный код и регулярные обновления.
- Поддержка спецификации WHATWG HTML5 (как в современных браузерах).
- Удобный синтаксис для web-парсинга на Java.
- Поддержка XML и ASCII.
- Подробная документация и сборник рецептов для типовых задач.
- Встроенный клинер для проверки и очистки небезопасных файлов/HTML-кода.
- Большой набор методов на все случаи жизни (API для парсинга, работы с DOM-структурой и CSS-селекторами).
- Минимальный размер (менее 1 Мб).
- Онлайн-версия библиотеки – для проверки её в работе.
- Jsoup полностью самодостаточен и не имеет зависимостей.
- Библиотека работает на Java 8 и всех более поздних версиях.
- Поддерживается подключение через прокси.
Недостатки
- Набор методов очень большой, поэтому на их изучение может уйти много времени.
- Jsoup не умеет обрабатывать динамические сайты, подразумевается только анализ статичных HTML-страниц.
- При работе с большими документами или частых операциях поиска по DOM-дереву производительность может падать.
- Хотя библиотека поддерживает базовые CSS-селекторы, она не охватывает все возможности современных селекторов, такие как псевдоклассы.
Примеры кода
// Импортируем библиотеки
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import java.io.IOException;
// Простой пример парсинга Википедии, сначала создаём класс
public class Wikipedia {
public static void main(String[] args) throws IOException {
// Создаём документ и наполняем содержимым с главной страницы Википедии
Document doc = Jsoup.connect("http://en.wikipedia.org/").get();
// в лог выводим тайтл
log(doc.title());
// Получение списка новостей
Elements newsHeadlines = doc.select("#mp-itn b a");
for (Element headline : newsHeadlines) {
// Собираем в цикле заголовки с атрибутом title и ссылки с атрибутом href
log("%s\n\t%s", headline.attr("title"), headline.absUrl("href"));
}
}
// Печатаем свой список
private static void log(String msg, String... vals) {
System.out.println(String.format(msg, vals));
}
}
А вот так может выглядеть код парсинга через прокси:
URL url = new URL("http://www.your-taget-site.com/");
// здесь данные вашего подключения к прокси
Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("127.0.0.1", 8080));
// открываем соединение
HttpURLConnection uc = (HttpURLConnection)url.openConnection(proxy);
hc.setRequestProperty("User-Agent", " Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 YaBrowser/24.10.0.0 Safari/537.36");
uc.setRequestProperty("Content-Language", "ru-RU");
uc.setRequestMethod("GET");
uc.connect();
// Создаём документ и далее переходим к логике парсинга
Document doc = Jsoup.parse(uc.getInputStream());
…
HtmlUnit
Это самодостаточный headless-браузер, который изначально написан на Java, чтобы быть кроссплатформенным и легко сочетаться с другими скриптами и программами, написанными на Java. Но благодаря высокоуровневому API, может работать с любыми другими языками программирования. В отличие от более популярных безголовых браузеров, вообще не имеет графического интерфейса. Но зато потребляет минимум ресурсов и обеспечивает обработку сайтов с максимальной скоростью.
Умеет обрабатывать JavaScript и Ajax, а также куки, поддерживает HTTPS-протокол, отправку форм и адекватно эмулирует поведение пользователей. Разработка ведётся с 2002 года. Код открыт по лицензии Apache 2.0. Библиотека идеальна для автоматизирования тестов (при веб-разработке), а также для скрапинга контента.
Преимущества
- Высокоуровневое API и кроссплатформенность (за счёт запуска в Java-машинах).
- Возможность имитации любых реальных браузеров (Chrome, Edge, Firefox и т.п.).
- Обработка JavaScript (динамических сайтов) и Ajax.
- Возможность скачивания файлов и отправки данных через формы. Поддерживаются все стандартные http-методы (POST, GET, DELETE, HEAD и т.п.).
- Работа с куками.
- Библиотека портирована на .Net + есть специальная версия для интеграции c Android-приложениями.
- Есть готовый web-драйвер для интеграции с Selenium.
- Прокси поддерживаются из коробки.
- Встроенный парсер HTML.
Недостатки
- За рендеринг JavaScript отвечает модифицированная версия движка Rhino (разрабатывается Mozilla). Этот движок сам по себе имеет массу проблем из-за обеспечения обратной совместимости и уже устарел. В новых версиях Firefox Mozilla использует другой движок (SeaMonkey), поэтому Rhino поддерживается не так активно, как раньше.
- HtmlUnit не имеет графического интерфейса, поэтому вы не сможете обнаружить проблемы с рендерингом (а неправильный рендеринг, например, шрифтов, может использоваться для блокировки ботов).
- В этот браузер нельзя установить плагины. А они могут быть важны для определённых задач.
- HtmlUnit не использует при рендеринге аппаратное ускорение, как все современные браузеры с графическим интерфейсом. Что может приводить к существенным задержкам в обработке сайтов с большим количеством внешних JS-скриптов.
Примеры кода
Вот так может выглядеть процесс подключения через прокси с авторизацией:
public void homePage_proxy() throws Exception {
try (final WebClient webClient = new WebClient(BrowserVersion.CHROME, PROXY_HOST, PROXY_PORT)) {
//Использование прокси с авторизацией
final DefaultCredentialsProvider credentialsProvider = (DefaultCredentialsProvider) webClient.getCredentialsProvider();
credentialsProvider.addCredentials("логин", "пароль", PROXY_HOST, PROXY_PORT);
final HtmlPage page = webClient.getPage("https://целевой-домен.зона");
Assert.assertEquals("Заголовок страницы: ", page.getTitleText());
}
}
Selenium
Это набор библиотек и инструментов для автоматизации практически любых браузеров. Selenium выступает в роли прослойки, организующей API-интерфейс через специальные web-драйверы. Драйверы разработаны для всех известных браузеров и платформ: Firefox, Chrome, Safari, Edge и т.д. Поддерживается даже обозначенный выше HtmlUnit и интеграция с большинством антидетект-браузеров. Selenium работает во всех десктопных операционных системах: Windows, Linux, MacOS. Специально для оригинальной IDE-системы был создан свой собственный язык команд – Selenese.
Проект существует с 2004 года. В настоящее время предоставляются не только веб-драйверы, но и IDE-система, docker-контейнеры, а также система управления большим количеством браузеров (в том числе на удалённых серверах) – Selenium Grid. Поддерживается одновременно несколько языков программирования, в том числе Java.
Читайте также: Cравнение инструментов для тестирования сайтов (Selenium, Playwright, Puppeteer, Lighthouse).
Преимущества
- Вы получаете возможность удалённого управления любыми реальными браузерами (даже если у них нет своего API-интерфейса).
- Так как сайт загружается в полноценном браузере, то весь код и скрипты отрабатывают максимально корректно, в том числе с поддержкой аппаратного ускорения.
- Для Chrome вместо веб-драйвера можно использовать обращения по оригинальному API (DevTools Protocol, примерно, как это делает библиотека chromedp).
- Можно имитировать любые действия пользователя: перемещать указатель, заполнять поля, отправлять и скачивать файлы, кликать и т.п.
- Исчерпывающая кроссплатформенность и поддержка разных языков программирования.
- Готовое решение для управления браузерами на удалённых ПК/серверах.
- Поддержка асинхронных запросов и возможность ожидания определённых элементов на странице.
- Поиск элементов на странице и взаимодействие с ними.
- Возможность создания скриншотов и PDF-версий страниц.
Недостатки
- Официально поддерживаемых браузеров только пять: Chrome, Safari, Edge, Internet Explorer и Firefox. Вебдрайвера к остальным нужно искать отдельно (обычно их можно найти в GitHub-хранилищах самих браузеров).
- Порог входа достаточно высокий. Не каждый сможет разобраться с процессами установки и настройки веб-драйверов.
- Синтаксис встроенного парсера слабый, поэтому в сложных задачах нужно задействовать синтаксис других библиотек и анализаторов (XPath).
Примеры кода
Типовое использование вебдрайвера Selenium для Java-парсера:
package dev.selenium.hello;
// Импорт библиотек
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
// Запуск экземпляра Headless-браузера
public class HelloSelenium {
public static void main(String[] args) {
// Подключение к Google Chrome
WebDriver driver = new ChromeDriver();
// Открытие страницы
driver.get("https://selenium.dev");
// Закрытие браузера
driver.quit();
}
}
А это пример кода для сбора текстового содержимого из элемента:
// В примере находим первый элемент с тегом H1 и копируем его содержимое
String text = driver.findElement(By.tagName("h1")).getText();
System.out.println(text);
Больше предметных примеров веб-скраперов на Java можно найти в официальном репозитории.
Apache Nutch
Это готовая реализация web-парсера на Java, написанная и поддерживаемая под крылом команды Apache. Позиционируется как web-краулер с открытым исходным кодом. В отдельных источниках систему называют фреймворком для построения своих поисковых систем (теоретически можно создать свой аналог Google). Имеет модульную расширяемую структуру. Итоговый проект может работать как на одной машине, так и в распределённой среде – в кластере Hadoop. Собранные данные могут индексироваться такими поисковыми движками, как Elasticsearch и Apache Solr.
Обратите внимание, была попытка разработки Apache Nutch 2.0, которая предназначалась для работы с абстрактными хранилищами данных, но от неё отказались. Получается, что первая ветка является наиболее актуальной (она развивается и по сей день).
Преимущества
- Вы получаете готовый Java-парсер, которым можно управлять из командной строки.
- При желании парсер расширяется и дополняется. Есть официальные и неофициальные плагины.
- Задания на парсинг можно составлять на основе регулярных выражений и больших списков URL (можно спарсить хоть весь Интернет).
- Система сама может находить дубликаты и избавляться от них.
- Данные по результатам краулинга хранятся в индексированном виде, что облегчает их поиск и выгрузку.
- Nutch может устанавливаться на сервер или в распределённых кластерах.
- Поддерживается REST API. Есть готовые docker-контейнеры.
- Возможна работа через прокси (достаточно правильно отредактировать конфиги).
- Поддерживается работа через web-драйвер Selenium.
Недостатки
- Nutch достаточно сложный в управлении. Фактически нужно предварительно изучить возможности API системы. Всё делается только из консоли (графических интерфейсов нет).
- Не менее сложная установка и настройка. Особенно, если вы хотите интегрировать краулер с локальным поисковым движком.
- Встроенный парсер не умеет работать с динамическими сайтами (написанными на JavaScript). А интеграцию с Selenium нужно ещё уметь настроить.
- Объём кода нереально большой. Использование Nutch в качестве парсера чем-то напоминает стрельбу из пушки по воробьям.
- Вся документация хранился в Atlassian. А сервис блокирует весь трафик из РФ, соответственно, разработчикам с российскими IP потребуются прокси для доступа.
Примеры кода
Если вы справитесь с установкой базового окружения, то останется только:
- Изменить настройка краулера (делается это в конфиге conf/nutch-site.xml).
- Поправить фильтры на основе регулярных выражений (файл conf/regex-urlfilter.txt).
- Добавить списки URL на парсинг (для этого создаётся каталог urls/ и в нём файл seed.txt, адреса записываются каждый на новой строке).
- Запустить краулер (команда для консоли может выглядеть так - bin/nutch crawl urls -dir crawl -depth 3 -topN 5).
При желании к nutch можно обращаться из своего Java-кода:
import org.apache.hadoop.conf.Configuration;
import org.apache.nutch.crawl.Crawl;
import org.apache.nutch.fetcher.Fetcher;
import org.apache.nutch.util.NutchConfiguration;
public class NutchScraper {
public static void main(String[] args) throws Exception {
// Инициализируем экземпляр Nutch
String url = "https://здесь.целевой.url/";
// Создаём конфигурацию
Configuration conf = NutchConfiguration.create();
Crawl nutch = new Crawl(conf);
// Передаём URL на краулинг
nutch.crawl(url);
Fetcher fetcher = new Fetcher(conf);
fetcher.fetch(url);
// Выводим в консоли Тайтл сайта
System.out.println("Тайтл сайта: " + fetcher.getPage().getTitle());
}
}
WebMagic
Это ещё один готовый java-фреймворк, предназначенный для ускорения создания своих собственных парсеров. В отличие от Apache Nutch намного проще как в реализации, так и в настройке.
Преимущества
- Из коробки есть поддержка многопоточности.
- Легко настраивается интеграция с Selenium.
- Простой синтаксис для извлечения данных с сайта – поддерживается XPath и селекторы CSS.
- Максимально быстрый старт и простая настройка.
- Готовые примеры применения.
- Лёгкая интеграция со своими java-программами.
- Асинхронность из коробки.
- Удобная конвейерная обработка запросов и настраиваемый планировщик.
Недостатки
- Проект развивается фактически одним разработчиком, поэтому апдейты выкатываются редко.
- Документация скудная, а предметных мануалов в сети очень мало.
Примеры кода
Вот так может выглядеть код Java-парсера:
public static void main(String[] args) {
Spider.create(new GithubRepoPageProcessor())
// здесь указывается стартовая страница
.addUrl("https://целевой.url")
.addPipeline(new JsonFilePipeline("D:\\site-files\\"))
// Открываем 2 потока
.thread(2)
// Запускаем паука
.run();
}
А вот так выглядит полный код для создания парсера, который приводится в официальной документации (скрипт парсит раздел на GitHub):
import us.codecraft.webmagic.Page;
import us.codecraft.webmagic.Site;
import us.codecraft.webmagic.Spider;
import us.codecraft.webmagic.processor.PageProcessor;
// Тут создаётся обработчик со всей важной логикой и регулярными выражениями
public class GithubRepoPageProcessor implements PageProcessor {
// Определяем числа попыток коннекта и таймер ожидания
private Site site = Site.me().setRetryTimes(3).setSleepTime(100);
@Override
public void process(Page page) {
// Тут описывается структура запросов – URL-адресов и получается HTML-код
page.addTargetRequests(page.getHtml().links().regex("(https://github\\.com/\\w+/\\w+)").all());
// Тут формируется поле «Автор»
page.putField("author", page.getUrl().regex("https://github\\.com/(\\w+)/.*").toString());
// А тут поле «Имя» - на основе заголовка H1
page.putField("name", page.getHtml().xpath("//h1[@class='entry-title public']/strong/a/text()").toString());
// Если имя пустое
if (page.getResultItems().get("name")==null){
// Игнорировать страницы с пустыми именами
page.setSkip(true);
}
// А тут описываем логику заполнения поля readme
page.putField("readme", page.getHtml().xpath("//div[@id='readme']/tidyText()"));
}
@Override
public Site getSite() {
return site;
}
// Тут задача запускается и передаётся обработчику – для парсинга конкретного раздела сайта в 5 потоков
public static void main(String[] args) {
Spider.create(new GithubRepoPageProcessor()).addUrl("https://github.com/code4craft").thread(5).run();
}
}
Резидентные прокси
Идеальные прокси для получения ценных данных со всего мира.
Проблемы и ограничения веб-скрапинга на Java
Как мы уже упомянули выше, язык Java весьма неплохо показывает себя при написании парсеров. Но есть и проблемы, с которыми можно столкнуться в процессе разработки. Наиболее существенные ограничения опишем ниже:
- Java-приложения выполняются в специальной среде – JVM (Java Virtual Machine). С одной стороны, это позволяет отвязаться от программной платформы (операционной системы), а с другой, накладывает свои ограничения и подразумевает определённые технические требования. У JVM много версий и у каждой свои технические особенности.
- Автоматическое управление памятью идёт на пользу новичкам, но, если объём данных, с которыми работает скрипт, увеличивается (что нельзя назвать редкостью при парсинге), могут случаться утечки. Чтобы исправить такую ошибку, придётся кардинально пересмотреть архитектуру программы. Такое под силу только настоящим профи. В частности, утечки часто бывают в браузере HtmlUnit, там обычно виноват движок рендеринга. Поэтому в случае ошибок его советуют отключить.
- Строгая типизация и оригинальные требования к подключению сторонних модулей/библиотек, могут оттолкнуть начинающих разработчиков. Погружение в Java намного сложнее, если сравнивать язык с Python или Go.
- Количество готовых библиотек и фреймворков для web-парсинга на Java не такое впечатляющее, как для Python. Так исторически сложилось, что Питон оказался востребованнее при работе с данными и скрейпингом.
Плюс, не стоит забывать об особенностях написания web-парсеров (как класса ПО).
- Многие сайты защищаются от мусорного трафика, чтобы снизить нагрузку на свои серверы. В результате проверяется множество параметров сессии и браузера, в котором работает пользователь. По этой причине нужно уделять особое внимание не только HTTP-заголовкам и юзер-агенту, но и уметь имитировать другие параметры. Всё это требует профильных знаний и глубокого погружения.
- Некоторые системы защиты не получится обойти такими решениями, как HtmlUnit. Дело в том, что у упомянутого браузера нет полноценного аппаратного рендеринга, что неизбежно приводит к специфическим смещениям и несовпадениям в итоговой структуре документа. Этим активно пользуются системы защиты сайтов.
- Не менее важна поддержка JavaScript. Многие современные сайты представляют собой полноценные web-приложения. Если в браузере отключена поддержка JavaScript, то сайты могут просто перестать работать. Вывод – нужно использовать headless-браузеры, которые могут задействовать аппаратный рендеринг.
- Работа через headless-браузеры приводит к серьёзному потреблению ресурсов и к дополнительным задержкам. Соответственно, в скриптах важно заботиться об асинхронности.
- Ещё одна сложность – проверка цифровых отпечатков. Чтобы обойти такую защиту, нужно качественно имитировать действия пользователя, правдоподобно наполнять его куки, историю просмотра и другие параметры. А если у вас многопоточное сканирование, то у каждого пользователя должен быть свой цифровой отпечаток. Чтобы хранить и обрабатывать большое число независимых браузерных профилей, нужен качественный антидетект-браузер.
- Так как многие механизмы защиты привязываются к IP-адресам клиентов, логично их менять с определённой частотой или вообще при каждом новом запросе. Подробнее о ротируемых прокси. Качество и тип адресов имеют значение. Самыми надёжными для парсинга можно назвать мобильные и резидентные прокси.
Более подробный гайд о парсинге без блокировок
Заключение и рекомендации
Необходимость написания своих парсеров возникает по разным причинам. Но какими бы ни были причины, всегда хочется упростить и без того сложную работу. Как раз для этого можно использовать профильные библиотеки и фреймворки.
Так как Java популярный язык программирования, для него существует достаточно готовых решений и библиотек. Все самые популярные и функциональные мы перечислили выше.
Если вам не хватает качественных ротируемых прокси с глобальным охватом, чтобы запустить свой парсер, обратите внимание на Froxy. Наш сервис предлагает более 10 млн. IP адресов: мобильных, резидентных и серверных. Таргетинг до города и оператора связи. Оплачивается только трафик. Количество портов может быть очень большим – до 1000 штук.