Время прочтения: 7 мин.

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

Есть и другие способы, в числе них – Parsing (от англ. parse – синтаксический анализ). Это технология получения web-данных путем извлечения их со страниц web-ресурсов. Она включает в себя различные методы анализа web-страниц.

Рассмотрим следующие задачи и несколько вариантов их решений:

  1. Необходимо выгрузить все товары с площадки интернет-магазина (в определенном разделе).
  2. Необходимо на сайте произвести поиск по какому-то объекту (будь то Vin автомобиля, название книги, автомобиль на сайте объявлений и т.д.) и получить запрашиваемую информацию.

Разница между двумя задачами не сразу понятна:

Большой объем выгружаемых данных требует более детального анализа. Далее рассмотрим разные варианты решения (я не утверждаю, что задачи нельзя решить одинаково — можно, но практично ли такое решение? На мой взгляд – нет).

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

Преимущества:

  1. Много документации и примеров в сети Интернет, поэтому решение различных задач не станет затруднительным;
  2. Есть вставка значений в конкретное поле, а также нажатие на кнопки сайта (что лично мне кажется очень удобным);
  3. Есть аналоги библиотеки на других языках программирования: python, C#, ruby, java. Поэтому можно выбрать тот язык программирования, на котором вам удобнее писать;
  4. Поиск как по атрибутам html, так и по регулярным выражениям xPath.

Недостатки:

  • Первый недостаток заключается в зависимости скорости работы написанного кода от подгрузки страницы сайта. Сайт может не подгрузить контент, а selenium уже будет искать в нем поле (например, поиск поля class=’name’, id=’id_car’ и так далее), но из-за отсутствия страницы – он ничего не найдет и результатом будет ошибка. Поэтому необходимо ставить паузы между переходами разных страниц сайта. Это можно сделать так — поставить sleep() или же написать функцию, которая будет проверять статус страницы и после этого продолжать выполнять поиск полей, получая из них информацию;
  • Второй — это имитация браузера, что тоже не очень хорошо со стороны ресурсов и вышеупомянутой проблемы.

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

Для использования selenium необходим chromedriver, который как раз запускает сам браузер и имитирует действия пользователя.

Рассмотрим пример обращения к объектам и переход от страницы к странице на сайте (“https://www.reestr-zalogov.ru/search/index”).

Подключим библиотеки.

import requests
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
import os

Ниже представлен код перехода между страницами, имитация нажатия кнопок, выбор атрибута html различными вариантами.

def getCaptchaLink(driver,vin):
driver.get("https://www.reestr-zalogov.ru/search/index")
elem = driver.find_element_by_link_text("По информации о предмете залога")
elem.click()
elem1 = driver.find_element_by_id("vehicleProperty.vin")
elem1.send_keys(vin)
elem2 = driver.find_element_by_id("find-btn")
elem2.click()
elem3 =  driver.find_element_by_class_name("swal2-image")
return elem3.get_attribute("src")

А также метод скачивания картинок по ссылке:

def loadImage(link,vin):
p = requests.get(link)
out = open("captcha\{0}.jpg".format(vin), "wb")
out.write(p.content)
out.close()

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

vins = ['X1F8560E0D0023287','X1F8560E0D0023257','X1F45143SD0000411','X1F45143ZE0000283' ,'XTT236320F1001609']
driver = webdriver.Chrome(executable_path='chromedriver')
cnt=0
for i in vins:
loadImage(getCaptchaLink(driver,i),cnt)
cnt+=1

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

Далее рассмотрим одну из самых (если не самую) популярных библиотек .NET htmlAgilityPack (https://html-agility-pack.net/).

Преимущества:

  1. Удобство в типичных задачах, а также хорошо описывается XPath выражением. Например, получение всех ссылок со страницы:
HtmlDocument doc = new HtmlDocument(); 
doc.LoadHtml(“САМА ССЫЛКА НА СТРАНИЦУ”); 
List<string> hrefTags = new List<string>(); 
foreach (HtmlNode link in doc.DocumentNode.SelectNodes("//a[@href]")) 
{ 
HtmlAttribute att = link.Attributes["href"]; 
hrefTags.Add(att.Value); 
}
return hrefTags;
  1. Работа с классами css, а также поиск по наименованию атрибутов класса.
  2. Изначально скачивает html код и далее уже работает по нему. А также парсинг цельных страниц намного проще и быстрее.

Недостатки:

  1. Специфическое API. Если ничего не найдено, то возвращает null, а не пустую коллекцию (что требует ручной фиксации).
  2. Работает только с ссылкой на сайт.
  3. Нет возможности нажатия на определенную кнопку на странице, так как это не UI, а текстовое представление страницы сайта. Таким образом, отсутствует функция click(), поэтому необходимо использовать дополнительные компоненты, например WebBrowser.

Не могу отнести к преимуществу или недостатку тот факт, что данная библиотека не является уникальной в своем роде и имеет аналоги: Fizzler, CsQuery, AngleSharp и Regex.

Каждая из них имеет свой ряд плюсов и минусов:

Fizzler — это набор методов-расширений для HtmlAgilityPack, его плюс заключается в использование селекторов CSS.

var html = new HtmlDocument();
html.LoadHtml(@"
  <html>
      <head></head>
      <body>
        <div>
          <p class='content'>Fizzler</p>
          <p>CSS Selector Engine</p></div>
      </body>
  </html>");

document.QuerySelectorAll("p");

вернет все содержимое тегов <p> ([<p class = «content»>Fizzler</p>, <p>CSS Selector Engine</p>]).

CsQuery. Его преимущество в том, что он с легкостью осваивается знающим jQuery, так как все методы и их возможности скопированы один в один.

AngleSharp. Если библиотека CsQuery была скопирована с jQuery, то AngelSharp написан вручную с нуля на С#. Код схож с Fizzler (по скорости), а функционал похож на htmlAgilityPack.

Regex – самый древний для работы с HTML. На фоне других аналогов — плюсы не могу назвать. Работает на «очень страшных» xPath выражениях.

Вернемся к тому же примеру с поиском ссылок на странице, это будет выглядеть так:

Regex reHref = new Regex(@"(?inx)
<a \s [^>]* 
href \s* = \s* 
(?<q> ['""] ) 
(?<url> [^""]+ )
 		\k<q> 
[^>]* >");

Подводя итоги, можно сделать вывод, что библиотеку htmlAgilityPack лучше использовать в первой задаче, где необходимо «скрабить» все содержимое страницы без ввода данных и нажатий кнопок.

Пример — поиск конкретного авто на сайте Avito и его парсинг.

Рассмотрим решение:

Сначала необходимо собрать ссылку, которую будет необходимо преобразовать в DOM html для дальнейшей работы.

String url = “https://www.avito.ru/voronezh/avtomobili/hyundai/solaris-ASgBAgICAkTgtg2imzHitg3kmzE?cd=1&radius =200&f=ASgBAgECAkTgtg2imzHitg3kmzECRfgCGHsiZnJvbSI6ODU4MSwidG8iOjEzOTc4fbwVGHsiZnJvbSI6MTU3ODYsInRvIjpudWxsfQ”
HtmlDocument document = new HtmlDocument();
document.LoadHtml(url);

После этого преобразования можно обращаться к атрибутам html и получать информацию.

AllBlock = document.DocumentNode.SelectNodes(".//div[@class=\"item__line\"]");
if ((AllBlock == null) || (AllBlock.Count == 0))
{
RunOnUiThread(() =>
{
textView_resultAvito.Text = "В городе " + DataBase.GetInstance().currentCity + " на Avito: " + rowsAutoAvito;
});
return dtAutoAvito;
}

Данное условие проверяет на наличие данных. В нашем случае — была ли вообще найдена страница и элементы с данными об авто. Далее будет представлен ранее реализованный код для получения информации об автомобиле.

dtAutoAvito – является таблицей для хранения информации и для дальнейшей обработки полученных данных.

for (int i = 0; i < AllBlock.Count; i++)
dtAutoAvito.Rows.Add("", "", "", "", "", "");

foreach (HtmlNode block in AllBlock)
{
dtAutoAvito.Rows[rowsAutoAvito][0] = rowsAutoAvito;

try
{
HtmlNodeCollection name = block.SelectNodes(".//span[@itemprop=\"name\"]");
dtAutoAvito.Rows[rowsAutoAvito][1] = name[0].InnerText.Replace('\n', '');
}
catch
{
dtAutoAvito.Rows[rowsAutoAvito][1] = "";
}

try
{
HtmlNodeCollection desc = block.SelectNodes(".//div[@class=\"specific-params specific-params_block\"]");
dtAutoAvito.Rows[rowsAutoAvito][2] = desc[0].InnerText.Replace('\n', '');
}
catch
{
dtAutoAvito.Rows[rowsAutoAvito][2] = "";
}
try
{
HtmlNodeCollection price = block.SelectNodes(".//span[@itemprop=\"price\"]");
int.TryParse(string.Join("", price[0].InnerText.Where(c => char.IsDigit(c))), out int value);
dtAutoAvito.Rows[rowsAutoAvito][3] = Convert.ToInt32(value);
}
catch
{
dtAutoAvito.Rows[rowsAutoAvito][3] = "";
}

try
{
HtmlNodeCollection web = block.SelectNodes(".//div[@class=\"item_table-header\"]//a[@itemprop=\"url\"]");// itemprop = "url" 
dtAutoAvito.Rows[rowsAutoAvito][4] = "https://www.avito.ru" + web[0].Attributes["href"].Value;
}
catch
{
dtAutoAvito.Rows[rowsAutoAvito][4] = "";
}
}

Использование try/catch необходимо из-за особенности htmlAgilityPath, который может вернуть null, что вызовет ошибку в дальнейшей обработке (или из-за отсутствия всех атрибутов для вывода конечной информации при парсинге).

Альтернатива вышеуказанной библиотеки есть и на Python, называется – BeautifulSoup. Имеет свои функции и особенности, но принцип работы точно такой же.

Таким образом, мы рассмотрели различные задачи, привели библиотеки, их положительные и отрицательные стороны с примерами использования в реальных задачах. Рассмотренные варианты парсинга далеко не единственные.

Существуют и готовые приложения, которые позволяют получать данные с сайтов. Они бывают как платные, так и бесплатные и имеют множество своих особенностей. Подробнее можно почитать в этой статье https://habr.com/ru/company/click/blog/494020/