Парсинг сайтов на C#

11.09.2017 at 11:55

Использование Selenium на C#

Самый распостраненный способ автоматизировать любые действия с вебсайтом — использование библиотеки Selenium. Эта библиотека позволяет запустить браузер и управлять всеми действиями в нем. Поддерживаются все распостраненные браузеры.

Подключение Selenium к проекту

В контекстном меню проекта выбираем пункт «Manage NuGet Packages…»

Далее на вкладке «Browse» находим и устанавливаем пакеты Selenium.WebDriver и Selenium.WebDriver.ChromeDriver. Первый пакет — сама библиотека, второй добавляет в папку с собранным проектом драйвер к хрому ChromeDriver.exe.

Автоматизация действий с помощью Selenium

В качестве тестовой задачи попробуем на этом блоге открыть первую страницу с результатами поиска по запросу «C#» и на консоль вывести заголовки и адреса найденных статей.

Начнем с того, что откроем браузер и перейдем на главную страницу сайта.

using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
...
IWebDriver driver = new ChromeDriver();
driver.Url = @"http://lsreg.ru";

Теперь нужно найти инпут для ввода критерия поиска и ввести в него текст. Поиск элементов на странице осуществляется с помощью методов IWebDriver.FindElement и IWebDriver.FindElements. Эти методы умеют искать по множеству критериев: тег, css класс, xpath и другие.

Поиск элементов по XPath

XPath — это язык запросов к дереву элементов. Вот как выглядит запрос дива с id=»my_div»:

.//div[@id='my_div']

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

.//div[@id='my_div']//a - все ссылки внутри дива
.//div[@id='my_div']/a - ссылки на первом уровне вложенности

Кроме id фильтровать можно и по другим атрибутам.

Действия с элементами страницы

Метод FindElement возвращает экземпляр IWebElement. Для ввода текста используется метод SendKeys, для клика есть метод Click.

Находим инпут и вводим в него строку поиска

driver.FindElement(By.XPath(@".//div[@id='search-3']/form/input[@id='s']")).SendKeys("c#");

Кликаем на кнопку поиска

driver.FindElement(By.XPath(@".//input[@id='searchsubmit']")).Click();
Thread.Sleep(3000);

Sleep нужен для того, чтобы результаты поиска успели отобразиться.

Теперь находим все ссылки внутри заголовков и отображаем их текст и href

var links = driver.FindElements(By.XPath(".//h2/a"));
foreach (IWebElement link in links)
    Console.WriteLine("{0} - {1}", link.Text, link.GetAttribute("href"));

Получаем вот такой результат

Плюсы и минусы парсинга с помощью Selenium

Плюсы:

  • Сервер не отличит программу от реального пользователя
  • Можно работать в контентом, подгружаемым динамически

Минусы

  • Использование реального браузера замедляет работу и увеличивает потребление ресурсов
  • Не поддерживается .Net Core

Парсинг HTML с помощью HtmlAgilityPack

Если на сайте нет защиты от ботов и весь необходимый контент отдается сразу, то можно обойтись более легковесным решением — использовать библиотеку HtmlAgilityPack. Подключается она так же через NuGet. Эта библиотека строит DOM дерево по HTML. Проблема в том, что загружать код страницы придется самим. Для загрузки страницы я использую вот такую функцию

static string LoadPage(string url)
{
    var result = "";
    var request = (HttpWebRequest)WebRequest.Create(url);
    var response = (HttpWebResponse)request.GetResponse();

    if (response.StatusCode == HttpStatusCode.OK)
    {
        var receiveStream = response.GetResponseStream();
        if (receiveStream != null)
        {
            StreamReader readStream;
            if (response.CharacterSet == null)
                readStream = new StreamReader(receiveStream);
            else
                readStream = new StreamReader(receiveStream, Encoding.GetEncoding(response.CharacterSet));
            result = readStream.ReadToEnd();
            readStream.Close();
        }
        response.Close();
    }
    return result;
}

Загружаем код страницы

using HtmlAgilityPack;
..
var pageContent = LoadPage(@"http://lsreg.ru/?s=c%23");
var document = new HtmlDocument();
document.LoadHtml(pageContent);

Здесь сразу указываем урл страницы с результатами поиска. Дальше аналогично по XPath находим нужные ссылки и выводим результат.

HtmlNodeCollection links = document.DocumentNode.SelectNodes(".//h2/a");
foreach (HtmlNode link in links)
    Console.WriteLine("{0} - {1}", link.InnerText, link.GetAttributeValue("href", "")); 

Второй параметр в методе GetAttributeValue — это значение по умолчанию.