Задача: распарсить HTML страницу


Задача: разобрать несколько десятков тысяч HTML страниц. На страницах таблицы и <div> теги, в которых находятся данные. Количество форматов ограничено – порядка сорока разных форматов для страниц.


Требуемое решение: простой универсальный способ разбора HTML страниц в чистом C# коде.


У меня есть несколько самописных решений – одно на RegEx, другое на конвертации в XНТML  и разборе XPath выражениями (LINQ2XML). Все разной степени кривизны. Поэтому прошу совета, идеи, фрагментов кода – как это сделать максимально гибко и красиво. В идеале код должен быть насколько простым, чтобы его можно было отдать очень начинающему разработчику и он смог бы его модифицировать под разные страницы.


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


Выбрали решения @sdelaisam и @outcoldman, поэтому подарю две флешки. Коллеги, шлите в мыло почтовые адреса, телефоны и ФИО - флешки ждут 🙂

Comments (12)
  1. igor01 says:

    Использовать DOM модель.

    http://lamahashim.blogspot.com/

    http://msdn.microsoft.com/en-us/library/system.windows.forms.webbrowser.aspx

    + дай ему книгу "Linq язык интегрировнных запросов в С# 2008"

    хорошо прописано Linq to XML

  2. Ахмед says:

    А на всех сайтах надо вытаскивать одну и ту же инфу, или инфа может быть любой в принципе и должен быть способ для указания инфы которую надо вытащить на каждом сайте?

  3. Dennis Apter says:

    Как я понял HTML2XML ты реализовал посредством Majestic-12

     http://www.majestic12.co.uk/projects/html_parser.php

    Если так, то даже не знаю что еще можно придумать проще

  4. PingWin says:

    Я обычно в таких случаях прогоняю страницу через http://developer.mindtouch.com/SgmlReader и дальше уже XPath’ом.

    Да, затраты по памяти выше, зато минимум "велосипедного" кода 🙂

  5. Первый вариант – Сизифов вариант. Объяснение в блоге Jeff Atwood (http://www.codinghorror.com/blog/archives/001311.html)

  6. Маша says:

    Перевод .html страницы в DOM модель встроен в любой браузер. Зачем писать парсер самим – переиспользуйте готовый.

    P.S. Гайдар, наша компания собирается выпускать диск со свободными программами под Microsoft Windows по аналогии с http://www.theopendisc.com/ По пессимистичным прогнозам он вряд ли станет гвоздём продаж Евросети, поэтому мы ищем партнёров в этом мероприятии.

    Можно ли было бы получить от Вашей компании какую-нибудь поддержку в реализации этого проекта – например, оплатить печать дисков? Люди, покупающие лицензионные программы под Windows имеют лишний повод задуматься о лицензионности самой операционной системы. Опять же, связка Microsoft + свободное ПО всё ещё непривычна для российского пользователя, и мы сделаем шаг к популяризации этой связки. Также мы готовы сотрудничать по содержанию и оформлению диска.

  7. algel says:

    Быть может использовать Data Extracting SDK?

    http://extracting.codeplex.com/

    + статья на хабре

    http://habrahabr.ru/blogs/i_am_advertising/68150/

  8. Aerio says:

    Я решал такую задачу так:

    1) Приведение к xhtml через http://developer.mindtouch.com/SgmlReader

    По моему опыту это самый адекватный инструмент из всех найденных.

       /// <summary>

       /// Converts invalid HTML file to valid XML stream

       /// </summary>

       /// <param name="SourceFileName"></param>

       /// <param name="DestinationFileName"></param>

       static public Stream html2xml(string htmlFileName, string rootElement)

       {

         using (SgmlReader reader = new SgmlReader()) {

           reader.DocType = "HTML";

           reader.CaseFolding = CaseFolding.ToLower;

           // The only possible way to get cyrillic letters from html file is to use "Href" property (instead of "InputStream").

           // 1. Another checked way is to make "reader.InputStream = new Sgml.HtmlStream(htmlStream, null);"

           // but HtmlStream is internal class, so it is unavailable for us.

           // 2. Another checked way is to insert BOM mark in file if it is utf-8 encoded

           reader.Href = htmlFileName;

           MemoryStream writerStream = new MemoryStream();

           XmlTextWriter writer = new XmlTextWriter(writerStream, null);

           // convertion

           writer.WriteStartDocument();

           writer.WriteStartElement(rootElement);

           writer.WriteAttributeString("subdirectory", GetParentDirectory(htmlFileName));

           reader.Read();

           while (!reader.EOF)

             writer.WriteNode(reader, true);

           // end of document

           writer.WriteEndElement();

           writer.Flush();

           writerStream.Position = 0;

           // SgmlReader do not strip invalid XML characters, so we do it manually

           return writerStream.stripNonValidXMLCharacters();

         }

       }

    2) Далее два пути. Зависит от того, что нужно извлечь. Первый – это применять непосредственно XPath. Он реально удобен для операций с небольшим количеством данных. Использую метод XPathGetSingleNode.

       //

       static public XPathNodeIterator XPath(this XDocument xml, string xpathRequest)

       {

         XPathNavigator navigator = xml.CreateNavigator();

         XmlNamespaceManager nsManager = new XmlNamespaceManager(navigator.NameTable);

         nsManager.AddNamespace("xsl", @"http://www.w3.org/1999/XSL/Transform&quot;);

         nsManager.AddNamespace("msxsl", @"urn:schemas-microsoft-com:xslt");

         return navigator.Select(xpathRequest, nsManager);

       }

       //

       static public string XPathGetSingleNode(this XDocument xml, string xpathRequest)

       {

         XPathNodeIterator iterator = xml.XPath(xpathRequest);

         string result = string.Empty;

         if (iterator.MoveNext())

           result = iterator.Current.Value;

         return result.Replace("&amp;", "&").Trim();

       }

    3) Второй путь – при извлечении большого количества данных. Тогда реально удобно пользоваться XSLT. Он предназначен для этого. Я завел десяток шаблонов и просто трансформирую исходные данные в нужный мне xml. Использую http://mvpxml.codeplex.com/ но это больше дело вкуса, это просто расширение стандартного процессора.

    4) И наконец, что делать с результатом в xml. Я обычно работаю с MSSQL и поступаю так http://blog.ad.by/2009/07/mssql-fast-data-upload.html

    Очень просто, быстро и удобно.

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

    Из возникших в эксплуатации замечаний – форматы html-страниц рано или поздно меняются, поэтому в xslt-шаблоны я добавил проверки структуры. Если не совпадает, то наружу бросается эксепшен.

    Например так

       <!– error validations –>

       <xsl:if test="count(td) != 8">

         <xsl:message terminate="yes">Incompatible structure of table 2 – count of columns was changed.</xsl:message>

       </xsl:if>

    Другое дело, что многие на дух XSLT не переносят. А зря, он очень адекватен для определенного класса задач. И прост, кстати.

  9. А Data Extracting SDK это такой способ сделать HtmlAgilityPack, только нарушив все возможные принципы хорошей архитектуры.

  10. Max Paulousky says:

    У меня простого способа не получилось

    Решалась задача разделения представленных html страниц на группы, которые построены по одному шаблону. Задача решалась с ипользованием технологии распознавания образов (естественно, на html манер).

    Алгоритм –

    1. html -> xhtml (htmlagility pack)

    2. построение диффграмм (описание того, что различается в файлах, XmlDiffPatch)

    3. Вычленение общих xpath путей и на их основании построение групп шаблонов (алгоритм распознавания образов)

    на всё ушло больше месяца, эффективность распознавания 70-90%

  11. Gengzu says:

    К сожалению как XML не всегда можно распарсить HTML, из-за его возможной кривости в виде незакрытых тегов.

    Как один из вариантов, использовать объект IE, а дальше по коллекции контролов.

Comments are closed.

Skip to main content