Freigeben über


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

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

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

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

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

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

Comments

  • дай ему книгу "Linq язык интегрировнных запросов в С# 2008" хорошо прописано Linq to XML
  • Anonymous
    December 24, 2009
    А на всех сайтах надо вытаскивать одну и ту же инфу, или инфа может быть любой в принципе и должен быть способ для указания инфы которую надо вытащить на каждом сайте?

  • Anonymous
    December 24, 2009
    The comment has been removed

  • Anonymous
    December 24, 2009
    Я обычно в таких случаях прогоняю страницу через http://developer.mindtouch.com/SgmlReader и дальше уже XPath'ом. Да, затраты по памяти выше, зато минимум "велосипедного" кода :)

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

  • Anonymous
    December 24, 2009
    Перевод .html страницы в DOM модель встроен в любой браузер. Зачем писать парсер самим - переиспользуйте готовый. P.S. Гайдар, наша компания собирается выпускать диск со свободными программами под Microsoft Windows по аналогии с http://www.theopendisc.com/ По пессимистичным прогнозам он вряд ли станет гвоздём продаж Евросети, поэтому мы ищем партнёров в этом мероприятии. Можно ли было бы получить от Вашей компании какую-нибудь поддержку в реализации этого проекта - например, оплатить печать дисков? Люди, покупающие лицензионные программы под Windows имеют лишний повод задуматься о лицензионности самой операционной системы. Опять же, связка Microsoft + свободное ПО всё ещё непривычна для российского пользователя, и мы сделаем шаг к популяризации этой связки. Также мы готовы сотрудничать по содержанию и оформлению диска.

  • Anonymous
    December 24, 2009
    http://www.codeplex.com/htmlagilitypack, конечно.

  • Anonymous
    December 24, 2009
    Быть может использовать Data Extracting SDK? http://extracting.codeplex.com/

  • Anonymous
    December 24, 2009
    Я решал такую задачу так:
  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");      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 не переносят. А зря, он очень адекватен для определенного класса задач. И прост, кстати.
  • Anonymous
    December 24, 2009
    А Data Extracting SDK это такой способ сделать HtmlAgilityPack, только нарушив все возможные принципы хорошей архитектуры.

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

  1. html -> xhtml (htmlagility pack)
  2. построение диффграмм (описание того, что различается в файлах, XmlDiffPatch)
  3. Вычленение общих xpath путей и на их основании построение групп шаблонов (алгоритм распознавания образов) на всё ушло больше месяца, эффективность распознавания 70-90%
  • Anonymous
    December 27, 2009
    К сожалению как XML не всегда можно распарсить HTML, из-за его возможной кривости в виде незакрытых тегов. Как один из вариантов, использовать объект IE, а дальше по коллекции контролов.