Обложка: Парсинг данных онлайн-магазина на C#

Парсинг данных онлайн-магазина на C#

Всем привет! Сегодня мы учимся парсить любой веб-портал из кода нашего приложения. Для парсинга данных используем язык программирования C#. Если ваш любимый язык не C#, то не беда — используя данный подход можно решить задачу парсинга на любом современном языке.

Итак, ставим задачу.

Задача: спарсить карточку товара из онлайн магазина baucenter.ru, зная артикул товара.

Что понадобится ?

1. Сниффер HTTPS пакетов. Использую Fiddler.

2. Среда разработки приложения Visual Studio 2019.

Этап 1. Сбор данных сниффером пакетов

Запускаем Fiddler, параллельно открываем браузер и переходим на baucenter.ru. В поле поиска товара вставляем любой известный на данном сайте артикул товара (при открытии любого товара отображается артикул). Использую артикул 416001653.

Рисунок 1. Список собранных пакетов   

Результат сбора данных представлен на рисунке 1. Только собранных пакетов будет не 32 как нарисунке, а больше (у меня 320).

Этап 2. Фильтрация пакетов

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

Ненужные пакеты — это:

  • Пакеты вида Tunnel to …;
  • Изображения и шрифты;
  • JavaScript файлы;
  • CSS файлы;
  • Запросы на другие порталы нежели baucenter.ru.

Удалив ненужное, получаем следующее:

Рисунок 2. Список отфильтрованных пакетов

Этап 3. Анализ отфильтрованных пакетов.

Открыв карточку товара с артикулом 416001653 в браузере, видим карточку товара со всеми данными. Нас интересует название товара и цена.

Название товара: Тумба с раковиной Onika Крит 52 см.

Цена товара: 3390.

В Fiddler через поиск по тексту ищем, в каком запросе есть текст с названием товара, ценой и артикулом. В моём случае это запрос №241. Рассмотрим его.

Рисунок 3. Запрос карточки товара

Обратим внимание, чтобы выполнить данный запрос нужно знать адрес карточки товара https://baucenter.ru/mebel_dlya_vannoy_razmer_50_59sm1217/686594/. А его в данный момент у нас нет.

Снова через поиск по тексту в Fiddler ищем текст: https://baucenter.ru/mebel_dlya_vannoy_razmer_50_59sm1217/686594/. В моём случае этот текст находится в запросе №217.

Рисунок 4. Запрос на получение адреса карточки товара

Рисунок 5. HTML-код запроса для поиска адреса карточки товара

Рассмотрев данный запрос и ответ веб-сайта на данный запрос (рисунки 4 и 5), делаем следующие выводы:

  • в HTML-коде ответа на запрос №217 есть ссылка на карточку товара. Зная эту ссылку, мы из кода программы сделаем туда запрос и получим необходимые данные о товаре (название и цену, например);
  • для выполнения запроса №217 нужно знать только артикул товара, а он у нас есть («416001653»);
  • чтобы получить карточку товара из кода приложения, нужно сделать 2 запроса: POST-запрос на адрес https://baucenter.ru, передав артикул товара, и GET-запрос на полученный адрес из первого запроса.

Этап 4. Создать классы GetRequest и PostRequest в приложении

Чтобы спарсить данные из приложения, необходимо, чтобы ваше приложение отправило необходимые запросы на веб-портал. Эти запросы должны быть неотличимы от запросов веб-браузера. Для выполнения парсинга данных создадим классы GetRequest и PostRequest.

public class GetRequest
    {
        HttpWebRequest _request;
        string _address;

        public Dictionary<string, string> Headers { get; set; }

        public string Response { get; set; }
        public string Accept { get; set; }
        public string Host { get; set; }
        public string Referer { get; set; }
        public string Useragent { get; set; }
        public WebProxy Proxy { get; set; }

        public GetRequest(string address)
        {
            _address = address;
            Headers = new Dictionary<string, string>();
        }

        public void Run(CookieContainer cookieContainer)
        {
            _request = (HttpWebRequest)WebRequest.Create(_address);
            _request.Method = "Get";
            _request.CookieContainer = cookieContainer;
            _request.Proxy = Proxy;
            _request.Accept = Accept;
            _request.Host = Host;
            _request.Referer = Referer;
            _request.UserAgent = Useragent;

            foreach (var pair in Headers)
            {
                _request.Headers.Add(pair.Key, pair.Value);
            }

            try
            {
                HttpWebResponse response = (HttpWebResponse)_request.GetResponse();
                var stream = response.GetResponseStream();
                if (stream != null) Response = new StreamReader(stream).ReadToEnd();
            }
            catch (Exception)
            {
            }
        }
    }
public class PostRequest
    {
        HttpWebRequest _request;
        string _address;

        public Dictionary<string, string> Headers { get; set; }

        public string Response { get; set; }
        public string Accept { get; set; }
        public string Host { get; set; }
        public string Data { get; set; }
        public string ContentType { get; set; }
        public WebProxy Proxy { get; set; }
        public string Referer { get; set; }
        public string Useragent { get; set; }

        public PostRequest(string address)
        {
            _address = address;
            Headers = new Dictionary<string, string>();
        }

        public void Run(CookieContainer cookieContainer)
        {
            _request = (HttpWebRequest)WebRequest.Create(_address);
            _request.Method = "Post";
            _request.CookieContainer = cookieContainer;
            _request.Proxy = Proxy;
            _request.Accept = Accept;
            _request.Host = Host;
            _request.ContentType = ContentType;
            _request.Referer = Referer;
            _request.UserAgent = Useragent;

            byte[] sentData = Encoding.UTF8.GetBytes(Data);
            _request.ContentLength = sentData.Length;
            Stream sendStream = _request.GetRequestStream();
            sendStream.Write(sentData, 0, sentData.Length);
            sendStream.Close();

            foreach (var pair in Headers)
            {
                _request.Headers.Add(pair.Key, pair.Value);
            }

            try
            {
                HttpWebResponse response = (HttpWebResponse)_request.GetResponse();
                var stream = response.GetResponseStream();
                if (stream != null) Response = new StreamReader(stream).ReadToEnd();
            }
            catch (Exception)
            {
            }
        }
    }

Данные классы позволяют выполнять Get и Post запросы на веб-порталы. В конструктор класса передается адрес веб-сайта. В свойства класса передаются стандартные заголовки HTTP запроса: Accept, Host, Data, ContentType, Referer, Useragent. Свойство Proxy служит для установки прокси-сервера, через который будет отправлен запрос.

Это нужно для удобной проверки выполнения своего кода, передав в это свойство значение прокси-сервера Fiddler (по умолчанию 127.0.0.1:8888). Таким образом, при выполнении запросов из программы вы увидите запросы в Fiddler и сможете легко понять проблему, если она будет.

Выполнение запроса происходит при вызове метода Run, и передаче в данный метод контейнер куки. Контейнер куки создаёте перед выполнением всех запросов. После выполнения запросов контейнер записывает в себя, полученные от веб-портала куки. Это позволяет выполнять последующие запросы с сохраненными куки-данными.

Результат выполнения запроса записывается в виде текста в свойство Response.

Этап 5. Создание приложения для получения карточки товара

        static void Main(string[] args)
        {
            // артикул товара
            var code = "416001653";
            
            // прокси-сервер
            var proxy = new WebProxy("127.0.0.1:8888");
            
            // контейнер куки
            var cookieContainer = new CookieContainer();

            // запрос №1. получение адреса карточки товара по артикулу товара
            var postRequest = new PostRequest("https://baucenter.ru/");
            postRequest.Data = $"ajax_call=y&INPUT_ID=title-search-input&q={code}&l=2";
            postRequest.Accept = "*/*";
            postRequest.Useragent = "Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko";
            postRequest.ContentType = "application/x-www-form-urlencoded";
            postRequest.Referer = "https://baucenter.ru/";
            postRequest.Host = "baucenter.ru";
            postRequest.Proxy = proxy;
            postRequest.Headers.Add("Bx-ajax", "true");
            postRequest.Run(cookieContainer);

            // поиск в HTML-коде ответа адрес карточки товара
            var strStart = postRequest.Response.IndexOf("search-result-group search-result-product");
            strStart = postRequest.Response.IndexOf("<a href=", strStart) + 9;
            var strEnd = postRequest.Response.IndexOf("\"", strStart);
            var getPath = postRequest.Response.Substring(strStart, strEnd - strStart);
            
            // вывод в консоль найденный адрес карточки по артикулу
            Console.WriteLine($"getPath={getPath}");

            // запрос №2. получение карточки товара
            var getRequest = new GetRequest($"https://baucenter.ru{getPath}");
            getRequest.Accept = "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9";
            getRequest.Useragent = "Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko";
            getRequest.Referer = "https://baucenter.ru/";
            getRequest.Host = "baucenter.ru";
            getRequest.Proxy = proxy;
            getRequest.Run(cookieContainer);

            // создание объекта класса карточки товара для парсинга искомых данных
            var card = new Card();
            card.Parse(getRequest.Response);

            // вывод в консоль параметров найденного товара: название и цена
            Console.WriteLine($"title={card.Title}");
            Console.WriteLine($"price={card.Price}");

            Console.ReadKey();
        }
    /// <summary>
    /// Класс карточка товара.
    /// Метод Parse - парсинг HTML, запись свойств Title и Price
    /// </summary>
    public class Card
    {
        public string Price { get; set; }
        public string Title { get; set; }

        public void Parse(string html)
        {
            var priceStart = html.IndexOf("Цена") + 11;
            var priceEnd = html.IndexOf("<span", priceStart);
            Price = html.Substring(priceStart, priceEnd - priceStart).Trim();

            var titleStart = html.IndexOf("<h1>") + 4;
            var titleEnd = html.IndexOf("</h1>", titleStart);
            Title = html.Substring(titleStart, titleEnd - titleStart).Trim();
        }
    }

Результат выполнения программы представлен на рисунке 6.

Рисунок 6. Результат выполнения программы

Код приложения можно найти здесь.

Подробное видео разработки данного проекта здесь: