Время прочтения: 7 мин.
Есть множество способов сбора информации с различных сайтов. Например, пользователь может самостоятельно просматривать сайты и собственноручно выгружать в базу данных необходимую информацию. Или, при наличии доступа к API сайта от разработчиков — можно автоматизировано выгружать необходимые данные. Это наиболее эффективный и простой метод получения информации с веб-ресурса.
Есть и другие способы, в числе них – Parsing (от англ. parse – синтаксический анализ). Это технология получения web-данных путем извлечения их со страниц web-ресурсов. Она включает в себя различные методы анализа web-страниц.
Рассмотрим следующие задачи и несколько вариантов их решений:
- Необходимо выгрузить все товары с площадки интернет-магазина (в определенном разделе).
- Необходимо на сайте произвести поиск по какому-то объекту (будь то Vin автомобиля, название книги, автомобиль на сайте объявлений и т.д.) и получить запрашиваемую информацию.
Разница между двумя задачами не сразу понятна:
Большой объем выгружаемых данных требует более детального анализа. Далее рассмотрим разные варианты решения (я не утверждаю, что задачи нельзя решить одинаково — можно, но практично ли такое решение? На мой взгляд – нет).
Первый инструмент, который позволит решить обе задачи – это библиотека Selenium. Рассмотрим преимущества и недостатки.
Преимущества:
- Много документации и примеров в сети Интернет, поэтому решение различных задач не станет затруднительным;
- Есть вставка значений в конкретное поле, а также нажатие на кнопки сайта (что лично мне кажется очень удобным);
- Есть аналоги библиотеки на других языках программирования: python, C#, ruby, java. Поэтому можно выбрать тот язык программирования, на котором вам удобнее писать;
- Поиск как по атрибутам 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/).
Преимущества:
- Удобство в типичных задачах, а также хорошо описывается 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;
- Работа с классами css, а также поиск по наименованию атрибутов класса.
- Изначально скачивает html код и далее уже работает по нему. А также парсинг цельных страниц намного проще и быстрее.
Недостатки:
- Специфическое API. Если ничего не найдено, то возвращает null, а не пустую коллекцию (что требует ручной фиксации).
- Работает только с ссылкой на сайт.
- Нет возможности нажатия на определенную кнопку на странице, так как это не 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/