Задача: распарсить HTML страницу
Задача: разобрать несколько десятков тысяч HTML страниц. На страницах таблицы и <div> теги, в которых находятся данные. Количество форматов ограничено – порядка сорока разных форматов для страниц.
Требуемое решение: простой универсальный способ разбора HTML страниц в чистом C# коде.
У меня есть несколько самописных решений – одно на RegEx, другое на конвертации в XНТML и разборе XPath выражениями (LINQ2XML). Все разной степени кривизны. Поэтому прошу совета, идеи, фрагментов кода – как это сделать максимально гибко и красиво. В идеале код должен быть насколько простым, чтобы его можно было отдать очень начинающему разработчику и он смог бы его модифицировать под разные страницы.
Принялся было писать движок парсера со своим языком описания шаблонов, но в последний момент программерский ангел хранитель дал по ушам и сказал, что лишние велосипеды не нужны.
Выбрали решения @sdelaisam и @outcoldman, поэтому подарю две флешки. Коллеги, шлите в мыло почтовые адреса, телефоны и ФИО - флешки ждут :)
Comments
- Anonymous
December 24, 2009
Использовать DOM модель. http://lamahashim.blogspot.com/ http://msdn.microsoft.com/en-us/library/system.windows.forms.webbrowser.aspx
- дай ему книгу "Linq язык интегрировнных запросов в С# 2008" хорошо прописано Linq to XML
Anonymous
December 24, 2009
А на всех сайтах надо вытаскивать одну и ту же инфу, или инфа может быть любой в принципе и должен быть способ для указания инфы которую надо вытащить на каждом сайте?Anonymous
December 24, 2009
The comment has been removedAnonymous
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/
- статья на хабре http://habrahabr.ru/blogs/i_am_advertising/68150/
- Anonymous
December 24, 2009
Я решал такую задачу так:
- Приведение к 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(); } }
- Далее два пути. Зависит от того, что нужно извлечь. Первый - это применять непосредственно 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("&", "&").Trim(); }
- Второй путь - при извлечении большого количества данных. Тогда реально удобно пользоваться XSLT. Он предназначен для этого. Я завел десяток шаблонов и просто трансформирую исходные данные в нужный мне xml. Использую http://mvpxml.codeplex.com/ но это больше дело вкуса, это просто расширение стандартного процессора.
- И наконец, что делать с результатом в 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 манер). Алгоритм -
- html -> xhtml (htmlagility pack)
- построение диффграмм (описание того, что различается в файлах, XmlDiffPatch)
- Вычленение общих xpath путей и на их основании построение групп шаблонов (алгоритм распознавания образов) на всё ушло больше месяца, эффективность распознавания 70-90%
- Anonymous
December 27, 2009
К сожалению как XML не всегда можно распарсить HTML, из-за его возможной кривости в виде незакрытых тегов. Как один из вариантов, использовать объект IE, а дальше по коллекции контролов.