CoderCastrov logo
CoderCastrov
C#

Более быстрый парсинг браузера на C# с использованием Selenium и HtmlAgilityPack

Более быстрый парсинг браузера на C# с использованием Selenium и HtmlAgilityPack
просмотров
4 мин чтение
#C#

Любой, кто занимается парсингом веб-сайтов, наверняка знаком с Selenium. Это отличный инструмент для управления браузером при парсинге данных, загруженных с помощью JavaScript. У меня нет претензий к Selenium, за исключением того, что парсинг большого количества данных на странице может быть очень медленным. Если вы следите за выводом консоли для Selenium, вы увидите, как работает функция FindElement(). Она создает селекторы из объектов By и затем отправляет их в браузер для выполнения в JavaScript. В большинстве случаев это нормально, но когда вам нужно выполнить 10+ вызовов FindElement() на странице, это может занять очень много времени.

Вместо выполнения JavaScript в браузере, что вводит много задержек, я решил использовать HtmlAgilityPack для обработки исходного кода отрендеренной страницы, чтобы мне приходилось взаимодействовать с драйвером только для выполнения того, в чем драйвер хорош - загрузки данных, к которым я не могу получить доступ с помощью простых HTTP-запросов.

Я создал небольшой класс-расширение, который добавляет несколько функций к классу IWebDriver из Selenium и к классам HtmlNode и HtmlNodeCollection из HtmlAgilityPack, позволяющий получать HtmlNodes из Selenium и использовать селекторы By с HtmlAgilityPack. Я использовал библиотеку Css2XPath Reloaded от Jon Humphrey для преобразования объектов By в xpath, который использует HtmlAgilityPack.

TL;DR

Функции Selenium отправляют запросы к драйверу, что вводит задержку. Если мы делаем один запрос для получения исходного кода страницы и затем обрабатываем его сами, мы можем значительно быстрее собирать данные, чем если бы мы выполняли отдельные функции FindElement в браузере.

Когда это эффективно?

Получение исходного кода страницы с помощью Selenium занимает примерно столько же времени, сколько выполнение функции FindElement(), поэтому этот процесс имеет смысл только тогда, когда вам нужно найти множество данных на странице. HtmlAgilityPack намного быстрее находит элементы после получения исходного кода, но он не поможет вам нажимать на них или выполнять любое другое интерактивное поведение. Выполнение команды FindNode() на экземпляре IWebDriver на самом деле медленнее, чем FindElement(), потому что оно должно как общаться с браузером, так и загружать его в HtmlAgilityPack перед поиском узла, в то время как FindElement() ищет узел в процессе общения с браузером.

Новые функции

Расширенные функции включают:

IWebDriver.GetDocumentNode(): Получает узел документа из экземпляра IWebDriver, загружая исходный код страницы с помощью HtmlAgilityPack и выбирая корневой узел документа.

IWebDriver.FindNode(): Находит HtmlNode в driver.GetDocumentNode() с использованием xpath или объекта By.

IWebDriver.FindNodes(): Находит HtmlNodeCollection в driver.GetDocumentNode() с использованием xpath или объекта By.

HtmlNode.FindNode(): Псевдоним для HtmlNode.SelectSingleNode(), назван для согласованности. Этот метод также может принимать объект By или xpath.

HtmlNode.FindNodes(): Псевдоним для HtmlNode.SelectNodes(), также назван для согласованности и также может принимать объект By или xpath.

By.ToXPath(): Получает xpath для объекта By.


Тесты

Хорошо, достаточно разговоров о моем простом классе. Давайте посмотрим на этот процесс на практике.

Я собираюсь использовать IMDB, потому что это классический тест для веб-парсера. "Mr. Robot" кажется подходящим вариантом, верно?

Тест 1

Найти один элемент с помощью методов FindElement() и FindNode() на экземпляре IWebDriver.

Мы будем искать элемент заголовка с использованием xpath "//div[@class='title_wrapper']/h1"

Тест 2

Найти несколько элементов с одним и тем же селектором с помощью методов FindElements() и FindNodes() на экземпляре IWebDriver.

Мы будем искать актеров, перечисленных под "Звезды", с использованием селектора xpath "//div[./h4[contains(text(), 'Звезды')]]/a[not(contains(text(), 'Полный список актеров и съемочная группа'))]". Думаете, мои xpath-ы - это мусор? Приходите ко мне.

Тест 3

Найти несколько элементов с использованием различных селекторов с помощью driver.FindElement(), driver.FindElements(), driver.FindNode() и driver.FindNodes() на экземпляре IWebDriver.

Мы будем искать заголовок и звезды, используя те же селекторы, что и ранее, а также будем искать ссылку на каждого актера с помощью xpath «//table[@class=’cast_list’]//td[not(@class=’primary_photo’)]/a[contains(@href, ‘/name/’)]»

Тест 4

Найти несколько элементов с использованием различных селекторов с помощью FindElement() и FindElements() на экземпляре IWebDriver и FindNode() на результате IWebDriver.GetDocumentNode().

Это будет использовать те же селекторы, что и тест 3.

Прогнозы

Поскольку основная задержка времени при использовании встроенных функций IWebDriver - это задержка связи с браузером, я ожидаю, что первые три теста приведут к небольшому снижению скорости, в то время как последний тест будет значительно быстрее при использовании HtmlAgilityPack.

Результаты

Как и ожидалось, первые три теста показывают небольшое снижение скорости при использовании функций Agility, в то время как четвертый тест показывает существенное увеличение скорости.

Вывод

Кажется, что выполнение функции selenium против драйвера почти всегда занимает около секунды, поэтому, если вы используете driver.GetDocumentNode() в вызове функции driver.FindNode(), это не будет быстрее, чем FindElement(), но если вы получаете узел документа сначала, а затем выполняете несколько функций с полученным результатом, вы получите значительное увеличение скорости.

Код

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

Тесты

Методы расширения