Pull to refresh

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

.NET *
Всем привет!
Как-то раз мне пришла в голову идея проанализировать вакансии размещенные на Хабре. Конкретно интересовало, есть ли зависимость между размером зарплаты и наличия высшего образования. А еще сейчас у студентов идет сессия (в том числе и у меня), то возможно кому-то уже надоело трепать нервы на экзаменах и этот анализ будет полезен.
Так как я программист на .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.  
  5.     foreach (var item in trNodes)
  6.     {
  7.         var tdNodes = item.ChildNodes.Where(=> x.Name == «td»).ToArray();
  8.         if (tdNodes.Count() != 0)
  9.         {
  10.             var location = tdNodes[2].ChildNodes.Where(=> x.Name == «a»).ToArray();
  11.  
  12.             jobList.Add(new HabraJob()
  13.             {
  14.                 Url = tdNodes[0].ChildNodes.First().Attributes[«href»].Value,
  15.                 Title = tdNodes[0].FirstChild.InnerText,
  16.                 Price = tdNodes[1].FirstChild.InnerText,
  17.                 Country = location[0].InnerText,
  18.                 Region = location[2].InnerText,
  19.                 City = location[2].InnerText
  20.             });
  21.         }
  22.  
  23.     }
  24.  
  25. }

А после осталось пройти по каждой ссылке и вытащить инфу об образовании и заодно еще и занятость — здесь есть небольшая проблема в том, что если таблица с ссылками на вакансию лежала в 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.     // так делать нельзя :-(
  8.     var table = html.GetElementbyId(«main-content»).ChildNodes[1].ChildNodes[9].ChildNodes[1].ChildNodes[2].ChildNodes[1].ChildNodes[3].ChildNodes.Where(=> x.Name == «tr»).ToArray();
  9.  
  10.     foreach (var tr in table)
  11.     {
  12.         string category = tr.ChildNodes.FindFirst(«th»).InnerText;
  13.  
  14.         switch (category)
  15.         {
  16.             case «Компания»:
  17.                 job.Company = tr.ChildNodes.FindFirst(«td»).FirstChild.InnerText;
  18.                 break;
  19.             case «Образование:»:
  20.                 job.Education = HabraJob.ParseEducation(tr.ChildNodes.FindFirst(«td»).InnerText);
  21.                 break;
  22.             case «Занятость:»:
  23.                 job.Employment = HabraJob.ParseEmployment(tr.ChildNodes.FindFirst(«td»).InnerText);
  24.                 break;
  25.             default:
  26.                 continue;
  27.         }
  28.     }
  29. }

Результаты


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

P.S.


При парсинге возникла такая проблема — страницы скачивались очень медленно. Поэтому я сначала попробовал WebClient, а потом WebRequest, но разницы не было. Поиск в гугле указал на то, что следует явно отключать Proxy в коде, и тогда все будет хорошо, однако это тоже не помогло.
Tags:
Hubs:
Total votes 58: ↑41 and ↓17 +24
Views 153K
Comments Comments 69