CoderCastrov logo
CoderCastrov
Отладка

Что такое jsoup? Парсер HTML для Java

Что такое jsoup? Парсер HTML для Java
просмотров
7 мин чтение
#Отладка

Парсинг веб-сайтов, созданных для современных браузеров, намного сложнее, чем было десять лет назад. Jsoup - удобный API, который делает парсинг веб-сайтов тривиальным с помощью обхода DOM, CSS-селекторов, методов, подобных JQuery, и многого другого. Но это не без своих ограничений. Каждый парсер - это тикающая бомба.

Реальный HTML ненадежен. Он меняется без предупреждения, так как это не задокументированный API. Когда наша Java-программа не справляется с парсингом, мы внезапно оказываемся перед тикающей бомбой. В некоторых случаях это простая проблема, которую мы можем воспроизвести локально и развернуть. Но нюансированные изменения в дереве DOM могут быть сложнее заметить в локальном тестовом случае. В таких случаях нам нужно понять проблему в дереве разбора перед развертыванием обновления. В противном случае у нас может быть сломанный продукт в производстве.

Прежде чем мы перейдем к деталям отладки jsoup, давайте сначала ответим на вопрос выше и обсудим основные концепции, лежащие в основе jsoup.

Сайт jsoup определяет его следующим образом:

jsoup - это библиотека Java для работы с реальным HTML. Она предоставляет очень удобный API для получения URL-адресов, извлечения и манипулирования данными с использованием лучших методов DOM HTML5 и селекторов CSS.

jsoup реализует спецификацию WHATWG HTML5 и разбирает HTML в тот же DOM, что и современные браузеры.

С этим в виду, давайте перейдем непосредственно к простому примеру, также взятому с того же сайта:

Document doc = Jsoup.connect("https://en.wikipedia.org/").get();
log(doc.title());
Elements newsHeadlines = doc.select("#mp-itn b a");
for (Element headline : newsHeadlines) {
  log("%s\n\t%s",
    headline.attr("title"), headline.absUrl("href"));
}

Этот фрагмент кода получает заголовки новостей с Википедии. В приведенном выше коде вы можете увидеть несколько интересных особенностей:

  • Подключение к URL практически без проблем - просто передайте строку URL в метод connect
  • Есть особые случаи для некоторых дочерних элементов. Например, заголовок представлен в виде простого метода, который возвращает строку без выборки из дерева DOM
  • Однако мы можем выбрать запись, используя довольно сложный синтаксис селектора

Если вы смотрите на это и думаете "это выглядит хрупко". Да, это так.

Простой тест Jsoup

Для демонстрации отладки я создал простой демо-проект, который вы можете скачать здесь.

Вы можете использовать следующую зависимость Maven для установки Jsoup в любую программу на Java. Maven автоматически загрузит jar-файл Jsoup:

<dependency>
  <groupId>org.jsoup</groupId>
  <artifactId>jsoup</artifactId>
  <version>1.14.3</version>
</dependency>

Этот демо-проект представляет собой простое Java-приложение, которое возвращает полный список внешних ссылок и элементов с атрибутами src на странице. Он основан на коде отсюда, преобразованном в программу на Java с использованием Spring Boot. Код Jsoup относительно короткий:

public Set<String> listLinks(String url, boolean includeMedia) throws IOException {
   Document doc = Jsoup.connect(url).get();
   Elements links = doc.select("a[href]");
   Elements imports = doc.select("link[href]");
   Set<String> result = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
   if(includeMedia) {
       Elements media = doc.select("[src]");
       for (Element src : media) {
           result.add(src.absUrl("src"));
           //result.add(src.attr("abs:src"));
       }
   }
   for (Element link : imports) {
       result.add(link.absUrl("abs:href"));
   }
   for (Element link : links) {
       result.add(link.absUrl("abs:href"));
   }
   return result;
}

Как видите, мы получаем входную строку URL. Мы также можем использовать потоки ввода, но это делает парсинг относительных URL-адресов немного сложнее (нам все равно нужен базовый URL). Затем мы ищем ссылки и объекты с атрибутом src. Затем код добавляет их все во множество, чтобы сохранить записи отсортированными и уникальными.

Мы предоставляем это в виде веб-сервиса с использованием следующего кода:

@RestController
public class ParseLinksWS {
   private final ParseLinks parseLinks;
   public ParseLinksWS(ParseLinks parseLinks) {
       this.parseLinks = parseLinks;
   }
   @GetMapping("/parseLinks")
   public Set<String> listLinks(@RequestParam String url, @RequestParam(required = false) Boolean includeMedia) throws IOException {
       return parseLinks.listLinks(url, includeMedia == null ? true : includeMedia);
   }
}

После запуска приложения мы можем использовать его с помощью простой команды curl:

curl -H "Content-Type: application/json" "http://localhost:8080/parseLinks?url=https%3A%2F%2Flightrun.com"

Это выводит список URL-адресов, на которые ссылаются на домашней странице Lightrun.

Отладка ошибок парсинга

Типичные проблемы с парсингом строк возникают, когда изменяется объект элемента. Например, структура страницы Википедии может измениться, и метод select выше может внезапно перестать работать. Это часто является тонкой ошибкой, например, отсутствие элемента DOM в иерархии объектов Java, что может вызвать сбой метода select.

К сожалению, это может быть тонкая ошибка. Особенно при работе с вложенными узлами и зависимостями между документами. Большинство разработчиков решают эту проблему, регистрируя большое количество данных. Это может быть проблемой по двум основным причинам:

  • Большие журналы - они сложны для чтения и очень дорогостоящие для обработки
  • Нарушение конфиденциальности/GDPR - скрапинг сайта может включать конфиденциальную информацию, относящуюся к конкретному пользователю. Что еще хуже!
  • Скрапинг сайта может измениться, чтобы включить конфиденциальную информацию после того, как был внедрен парсинг. Регистрация этой конфиденциальной информации может нарушать различные законы.

Если мы не регистрируем достаточно данных и не можем воспроизвести проблему локально, возникают сложности. Мы застреваем в цикле "добавить журналы, собрать, протестировать, развернуть, воспроизвести - повторить".

Lightrun предлагает лучший способ. Просто отслеживайте конкретную ошибку непосредственно в продакшене, проверьте проблему и создайте исправление, которое будет работать с одним развертыванием.

ПРИМЕЧАНИЕ: В этом руководстве предполагается, что вы установили Lightrun и понимаете основные концепции его использования. Если нет, пожалуйста, ознакомьтесь с документацией.

Навигация в браузерной модели DOM

Предположим, что вы не знаете, где искать, хорошим местом для начала является изучение API jsoup. Это может привести вас обратно к пользовательскому коду. Здорово в том, что это работает независимо от вашего кода. Мы можем найти правильную строку/файл для снимка, изучая вызов API.

Я нажал Ctrl+Click (на Mac используйте Meta+Click) на вызов метода select здесь:

Elements links = doc.select("a[href]");

И это привело меня к классу Element. Внутри него я нажал Ctrl+Click на метод Selector "select" и попал в "интересное" место.

Здесь я могу разместить условный снимок, чтобы увидеть каждый случай, когда выполняется запрос "a[href]":

Это может показать мне методы/строки, выполняющие этот запрос:

Это может сильно помочь в сужении общей проблемной области в иерархии объектов документа.

Иногда снимка может быть недостаточно. Нам может понадобиться использовать журнал. Преимущество журналирования заключается в том, что мы можем получить много информации, но только для конкретного случая и по требованию.

Ценность журналов заключается в том, что они могут отслеживать проблему так же, как и шаги в коде. Место, где мы разместили снимок, проблематично для журналов. Мы знаем, какой запрос был отправлен, но у нас еще нет значения, которое возвращается. Мы можем легко решить эту проблему с помощью журналов. Сначала мы добавляем журнал с следующим текстом:

"Выполняется запрос {query}"

Затем, чтобы узнать, сколько записей мы получили, мы просто переходим к вызывающему коду (который мы знаем благодаря стеку в снимке) и добавляем следующий журнал там:

Запрос ссылок вернул {links.size()}

Это создает следующий журнал, который позволяет нам увидеть, что у нас есть 147 ссылок a[href]. Преимущество этого заключается в том, что дополнительные журналы вплетаются в существующие журналы в контексте:

Feb 02, 2022 11:25:27 AM org.jsoup.select.Selector select
INFO: LOGPOINT: Выполняется запрос a[href]
Feb 02, 2022 11:25:27 AM com.lightrun.demo.jsoupdemo.service.ParseLinks listLinks
INFO: LOGPOINT: Запрос ссылок вернул 147
Feb 02, 2022 11:25:27 AM org.jsoup.select.Selector select
INFO: LOGPOINT: Выполняется запрос link[href]
Feb 02, 2022 11:25:27 AM org.jsoup.select.Selector select
INFO: LOGPOINT: Выполняется запрос [src]

Избегайте проблем с безопасностью и GDPR

GDPR и проблемы безопасности могут стать проблемой утечки информации о пользователе в журналы. Это может быть серьезной проблемой, и Lightrun помогает существенно снизить этот риск.

Lightrun предлагает два потенциальных решения, которые могут использоваться вместе, когда это применимо.

Передача журналов

Большая проблема с GDPR - это прием журналов. Если вы регистрируете личные данные пользователей, а затем отправляете их в облако, они остаются там на долгое время. Их трудно найти после факта, и их очень сложно исправить.

Lightrun предоставляет возможность перенаправлять все внедренные журналы Lightrun непосредственно в среду разработки. Это имеет преимущество удаления шума от других разработчиков, которые могут работать с журналами. Он также может пропустить прием (по выбору).

Чтобы отправлять журналы только в плагин, выберите режим перенаправления как "плагин".

Сокращение/Блоклисты Персональной Идентифицирующей Информации (ПИИ)

Персонально Идентифицирующая Информация (ПИИ) является основой GDPR и также представляет собой значительный риск безопасности. Злонамеренный разработчик в вашей организации может попытаться использовать Lightrun для сбора информации о пользователях. Блоклисты предотвращают разработчиков от размещения действий в определенных файлах.

Сокращение ПИИ позволяет скрывать информацию, соответствующую определенным шаблонам, из журналов (например, формат номера кредитной карты и т. д.). Это можно определить в веб-интерфейсе Lightrun с помощью роли менеджера.

TL;DR

С использованием Java для парсинга контента, jsoup является очевидным лидером. Разработка с использованием jsoup представляет собой не только операции со строками или обработку аспектов подключения. Помимо получения объекта документа, он также обрабатывает сложные аспекты, необходимые для работы с элементами DOM и скриптинга.

Парсинг - это рискованное занятие. Он может сломаться в мгновение ока, когда веб-сайт незначительно изменяется.

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

Благодаря Lightrun мы можем отлаживать такие сбои непосредственно в производственной среде и быстро публиковать рабочую версию. Вы можете использовать Lightrun бесплатно, зарегистрировавшись здесь.