Архив категорий Парсинг

Автор:human

Html Agility Pack — удобный .NET парсер HTML

Всем привет!
Как-то раз мне пришла в голову идея проанализировать вакансии размещенные на Хабре. Конкретно интересовало, есть ли зависимость между размером зарплаты и наличия высшего образования. А еще сейчас у студентов идет сессия (в том числе и у меня), то возможно кому-то уже надоело трепать нервы на экзаменах и этот анализ будет полезен.
Так как я программист на .Net, то и решать эту задачу — парсить объявления на Хабре я решил на C#. Вручную разбирать строки html мне не хотелось, поэтому было придумано найти html-парсер, который помог бы осуществить задачу.
Забегая вперед скажу, что из анализа ничего интересного не вышло и сессию придется сдавать дальше :(
Но зато немножко расскажу про весьма полезную библиотеку Html Agility Pack

Выбор парсера

Вышел на эту библиотеку я через обсуждение на Stackoverflow. В комментариях предлагались еще решения, например библиотекаSgmlReader, которая переводит HTML в XmlDocument, а для XML в .Net инструментов полный набор. Но почему-то меня это не подкупило и я пошел качать Html Agility Pack.

Беглый осмотр Html Agility Pack

Справку по библиотеке можно скачать на странице проекта. Функционал на самом деле очень радует.
Всего нам доступно двадцать основных классов:

Названия методов соответствуют интерфейсам DOM (замечание k12th) + плюшки: GetElementbyId(), CreateAttribute(), CreateElement() и т.д., так что работать будет особенно удобно, если приходилось сталкиваться с JavaScript
Похоже, что html все же перегоняется в Xml, а HtmlDocument и др. классы это обертка, ну и ничего страшного в этом, ввиду этого доступны такие возможности как:

  • Linq to Objects (via LINQ to Xml)
  • XPATH
  • XSLT

 

Парсим хабр!

Вакансии на хабре представлены в виде таблицы, в строках дана информация о требуемой специальности и зарплате, но так как нам нужна информация об образовании, то придется переходить на страницу вакансии и разбирать ее.
Итак, начнем, нам нужна таблица, чтобы вытащить оттуда ссылки и инфу о позиции с зарплатой:

  1. static void GetJobLinks(HtmlDocument html)
  2. {
  3.     var trNodes = html.GetElementbyId(«job-items»).ChildNodes.Where(=> x.Name == «tr»);
  4.     foreach (var item in trNodes)
  5.     {
  6.         var tdNodes = item.ChildNodes.Where(=> x.Name == «td»).ToArray();
  7.         if (tdNodes.Count() != 0)
  8.         {
  9.             var location = tdNodes[2].ChildNodes.Where(=> x.Name == «a»).ToArray();
  10.             jobList.Add(new HabraJob()
  11.             {
  12.                 Url = tdNodes[0].ChildNodes.First().Attributes[«href»].Value,
  13.                 Title = tdNodes[0].FirstChild.InnerText,
  14.                 Price = tdNodes[1].FirstChild.InnerText,
  15.                 Country = location[0].InnerText,
  16.                 Region = location[2].InnerText,
  17.                 City = location[2].InnerText
  18.             });
  19.         }
  20.     }
  21. }

А после осталось пройти по каждой ссылке и вытащить инфу об образовании и заодно еще и занятость — здесь есть небольшая проблема в том, что если таблица с ссылками на вакансию лежала в div-е с известным id, то информация о вакансия лежит в таблице без всяких id, поэтому пришлось немножко поизвращаться:

  1. static void GetFullInfo(HabraJob job)
  2. {
  3.     HtmlDocument html = new HtmlDocument();
  4.     html.LoadHtml(wClient.DownloadString(job.Url));
  5.     // html.LoadHtml(GetHtmlString(job.Url));
  6.     // так делать нельзя :-(
  7.     var table = html.GetElementbyId(«main-content»).ChildNodes[1].ChildNodes[9].ChildNodes[1].ChildNodes[2].ChildNodes[1].ChildNodes[3].ChildNodes.Where(=> x.Name == «tr»).ToArray();
  8.     foreach (var tr in table)
  9.     {
  10.         string category = tr.ChildNodes.FindFirst(«th»).InnerText;
  11.         switch (category)
  12.         {
  13.             case «Компания»:
  14.                 job.Company = tr.ChildNodes.FindFirst(«td»).FirstChild.InnerText;
  15.                 break;
  16.             case «Образование:»:
  17.                 job.Education = HabraJob.ParseEducation(tr.ChildNodes.FindFirst(«td»).InnerText);
  18.                 break;
  19.             case «Занятость:»:
  20.                 job.Employment = HabraJob.ParseEmployment(tr.ChildNodes.FindFirst(«td»).InnerText);
  21.                 break;
  22.             default:
  23.                 continue;
  24.         }
  25.     }
  26. }

 

Результаты

Ну а дальше, сохраняем результаты в XML и смотрим в Excel-e, что же получилось… и видим, что ничего хорошего не получилось, потому что большинство компаний либо не указывают размер зарплаты, либо не указывают информацию об образовании (забывают, указывают в теле вакансии, или действительно неважно), либо не указывают все сразу.
Кому интересно, вот результаты в xlsx , а здесь исходник.  О том, как парсить интернет-магазин здесь. О том, как парсить сайты с помощью SlimerJS здесь.

Оригинал на хабре — пост 112325

Автор:human

Парсинг сайтов с помощью SlimerJS

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


Начало работы

Для того, чтобы начать работать со SlimerJs, необходимо скачать последнюю версию скриптового браузера (я обычно качаю полную версию, в которую включен XulRunner, позволяющий запускать SlimerJS в отсутствие Firefox) для вашей ОС.

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

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

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

Еще одна полезная возможность — это создание событий. Очень просто имитировать набор символов в тексовом поле, что часто требуется при авторизации или регистрации, например:

Вообще функция senEvent() очень гибкая, подробнее о ней можно прочитать здесь.

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

SlimerJS очень похож на PhantomJS, но самое важное отличие, которое хотелось бы отметить — это окно бразуера во время выполнения SlimerJS. Другими словами, отладка и разработка ведутся проще, чем с фантомом.

Итог

SlimerJS позволяет быстро и просто парсить сложные динамические сайты, которые обычным cUrl спарсить не получается.

Оригинал на habre. post 247847

Автор:human

Парсинг интернет-магазинов. Немного примера

Разделим парсинг (скраппинг) сайтов на две подзадачи.

  1. Собственно сам парсинг – поиск данных, которые нам интересны на страницах.
  2. Осмысливание полученных данных.


Вначале опишем приложения:

  1. Парсер «постоянной» информации о товарах с сайта. Этот парсер будет запускаться редко (исключительно для проверки наличия новых товаров), будет разбирать страницы и извлекать из них информацию о товаре: наименование, фотографии, свойства.
  2. Парсер условно переменной информации. Это приложение будет запускаться часто и автоматически, будет разбирать страницы сайта для поиска цен и наличия на складе для обновления этой информации в БД (мы его рассматривать не будем, тут нет ничего необычного).
  3. Админка, структурирование полученных данных. Это приложения будет запускаться после парсера «постоянной» информации и позволяет админу разобрать/структурировать полученные данные.

 

Итак, поговорим о парсерах

Методов парсинга много, это и регулярные выражения и банальный поиск подстроки. Все эти способы имеют один большой недостаток – при небольших изменениях на сайте необходимо править сам парсер.
Для себя лично (пишу под .net на c#) остановился на библиотеке HtmlAgilityPack, описание например на Хабре.
Что она дает – она читает HTML (даже многие не валидные документы) и строит DOM дерево. А дальше вступает в дело вся мощь XPATH запросов. При правильно написанных XPATH запросах нет необходимости править парсер при изменениях на сайте.

Пример:
Для ориентации в DOM часто используются классы, но еще чаще минорные изменения в дизайне сайта выполняются добавлением соответствующих классов к элементам (было class=”productInfo” стало class=”productInfo clearfix”). Поэтому в XPATH лучше написать:

вместо

Да, это может сказаться на производительности, но не сильно. Универсальность кода – важнее.

Точно также при ориентации в дереве при помощи id элементов я, как правило, использую «//» (т.е. поиск по всему поддереву), вместо «/» (поиск только среди дочерних элементов). Это спасает в ситуациях, когда дизайнер обертывает какие-либо тэги (как правило, для фикса какого-нибудь бага отображения):

вместо

 

Следующий вопрос – «что парсить?»

Парсить нам нужно основную информацию о товарах: наименование, фотография и, самое главное, список свойств. Полученные данные мы будем хранить в БД с примитивной структурой:

Т.е. для каждого товара будет список пар значений: название свойства (propertyName) и ее значение (propertyValue).
Допустим мы написали парсер, разобрали все данные с сайта и хотим теперь создать БД и сайт для поиска товаров по параметрам. Для этого нам нужно структурировать данные.

Структурирование данных

Создадим еще пару таблиц в БД для хранения структурированных данных.

Dict_Property – справочник свойств (цвет, размер, вес и т.д., т.е. все свойства по которым мы потом будем искать)
Product_Property – значения этих свойств у конкретного товара.

Маленькая «хитрость» — поле FloatValue – для числовых свойств, формируется при помощи попытки конвертации поля Value во float:

Оно будет нужно для поиска (например: поле «вес», запрос от 100 до 300 грамм, поиск по текстовому полю «Value» будет медленный и не правильный, а по floatValue – быстрый и корректный).

Теперь все готово для того чтобы начать структурировать данные.

Практика показала, что даже самые плохо оформленные Китайские магазины стараются унифицировать описания товаров.
Пример: сайт dx.com, распарсено 1267 товаров. Всего у них 49398 свойств. Если сгруппируем по названию получим всего 580значений, что в принципе не много.

Сгруппируем свойства по названиям и по значениям, получим табличку (свойство, значение, сколько раз встречается) и отсортируем их по частоте появления.

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

  • Первые 100 строк таблицы (наиболее встречаемые значения свойств) покрывают порядка 35-40% всех значений свойств.
  • Очень много свойств и значений, отличающихся друг от друга только регистром или/и пробелами/опечатками.
  • Цифровые данные – как правило в одном формате – например вес, габариты, объем оперативной памяти.

Для структурирования данных напишем приложение для создания «правил парсинга». Введем 2 типа правил:

  • Точное совпадение. Например: свойство «Color», значение «Black»
  • Совпадение по регулярному выражению. Например: свойство Weight, значение
    (?<value1>\d*\.?\d+)g

Для каждого правила определим набор свойств, которые необходимо добавить к товару.
Как это выглядит на практике:

Правило для сайта «PandaWill», свойство «Net Weight(Including battery)», значений должно удовлетворять регулярному выражению:
(?<value1>\d*\.?\d+)g
При совпадении к товару будет добавлено свойство «Weight» со значением VALUE1 – выбранным из регулярного выражения.

Этим правилом мы «структурируем» порядка 90% значений для поля Weight, остальные 10% — через правила точных соответствий:

Для сайта «PandaWill», если свойство «GPS» равно «Yes, built in» — добавить свойство GPS со значением GPS, аналогично для значений «Yes», «No», «N/A»

Конечно встречаются свойства для которых приходится создавать множество правил, например разрешение экрана. Для каких-то товаров оно названо «Display»:

А для каких-то «Screen resolution»:

Немного статистики: в моем случае создано порядка 7000 правил для структурирования данных, которые добавили чуть меньше 50000 значений свойств. Т.е. одно правило — 7 значений.
С добавлением новых товаров это число увеличивается, что не может не радовать.

После всех этих манипуляция мы получаем БД со структурированной информацией о товарах. А уж поиск по этой БД – тривиальная задача.

P.S. Намеренно не привожу ссылки на результат трудов, чтобы не сочли за рекламу, и не выкинули еще дальше с хабра, кто захочет – тот нагуглит. О том, как парсить сайты с помощью SlimerJS здесь.

Оригинал на habre — пост  169409

Яндекс.Метрика