Win32。:如何在没有正则表达式的情况下抓取HTML?

时间:2009-11-24 14:52:30

标签: html windows regex winapi screen-scraping

最近的blog entry by a Jeff Atwood表示你永远不应该使用正则表达式解析HTML - 但是没有提供替代方案。

我想抓取搜索搜索结果,提取值:

<div class="used_result_container"> 
   ...
      ...
         <div class="vehicleInfo"> 
            ...
               ...
                  <div class="makemodeltrim">
                     ...
                     <a class="carlink" href="[Url]">[MakeAndModel]</a>
                     ...
                  </div> 
                  <div class="kilometers">[Kilometers]</div> 
                  <div class="price">[Price]</div> 
                  <div class="location">
                     <span class='locationText'>Location:</span>[Location]
                  </div> 
               ...          
            ...
         </div> 
      ...
   ...
</div> 

...and it repeats

您可以看到我想要提取的值,[括在括号中]:

  • 地址
  • MakeAndModel
  • 公里
  • 价格
  • 位置

假设我们接受解析HTML的前提:

这样做的方法是什么?

假设:

  • 原生Win32
  • loose html

假设澄清:

原生Win32

  • .NET / CLR不是本机Win32
  • Java不是本机Win32
  • perl,python,ruby不是原生的Win32
  • 假设C ++,在Visual Studio 2000中,编译为本机Win32应用程序

原生Win32应用程序可以调用库代码:

  • 复制源代码
  • 包含函数入口点的DLL
  • 包含COM对象的DLL
  • 包含COM对象的DLL,这些对象是受管.NET对象的COM可调用包装器(CCW)

HTML

  • xml不是HTML HTML
  • xhtml不是松散的HTML
  • 严格的HTML不是松散的HTML

松散的HTML意味着HTML格式不正确xml(严格的HTML无论如何都不是格式良好的xml),因此无法使用XML解析器。实际上,我假设任何HTML解析器必须在它接受的HTML中慷慨。


澄清#2

假设 您喜欢将HTML转换为文档对象模型(DOM)的想法,那么如何访问重复的数据结构? 如何走DOM树?我需要一个DIV节点,它是一个 used_result_container 的类,它有一个类 vehicleInfo 的子DIV。但节点不一定必须是彼此的直接子节点。

听起来我正在为另一组交换一组正则表达式问题。如果他们改变HTML的结构,我将不得不重新编写我的代码来匹配 - 就像我对正则表达式一样。假设我们想要避免这些问题,因为那些是正则表达式的问题,我该怎么做呢?

我不会为DOM节点编写正则表达式解析器吗?我正在编写一个引擎来解析一串对象,使用内部状态机和前后捕获。不,必须有更好的方式 - 杰夫提到的方式。

我故意将原来的问题模糊不清,以免引导人们走错路。我不想暗示解决方案必然与之有关:

  • 走DOM树
  • xpath查询

澄清#3

我提供的示例HTML我修剪了重要的元素和属性。我用来修剪HTML的机制是基于我使用正则表达式的内部偏见。我自然认为我需要HTML中的各种“签名帖子。”

所以不要将呈现的HTML与整个HTML混淆。也许其他一些解决方案取决于所有原始HTML的存在。

更新4

唯一提出的解决方案似乎涉及使用库将HTML转换为文档对象模型(DOM)。那么问题就必须变成:那么什么

既然我有DOM,我该怎么办呢?似乎我仍然需要使用某种常规DOM表达式解析器来运行树,能够进行前向匹配和捕获。

在这种特殊情况下,我需要所有 used_result_container DIV 节点,其中包含 vehicleInfo DIV节点作为子节点。任何不包含 vehicleInfo used_result_container DIV节点都与子节点无关。

是否存在具有捕获和转发匹配的DOM正则表达式解析器?我不认为XPath可以根据较低级别节点的标准选择更高级别的节点:

\\div[@class="used_result_container" && .\div[@class="vehicleInfo"]]\*

注意:我很少使用XPath,以至于我无法很好地编写假设的xpath语法。

12 个答案:

答案 0 :(得分:8)

的Python:

lxml - 更快,也许更好地解析错误的HTML

BeautifulSoup - 如果lxml输入失败,请尝试此操作。

Ruby:(听说过以下几个库,但从未尝试过它们)

Nokogiri

hpricot

虽然如果你的解析器窒息,并且你可以粗略地指出导致窒息的原因,我坦率地认为在将它传递给解析器之前使用正则表达式hack删除该部分是可以的。

如果您决定使用lxml,heresome XPath教程,您可能会发现它们很有用。 lxml教程假设您知道什么是XPath(我第一次阅读它时没有这样做。)

编辑:自首次发布以来,您的帖子确实增长了...我会尝试回答我的问题。

  

我认为XPath不能根据较低级别节点的标准选择更高级别的节点:

它可以。试试//div[@class='vehicleInfo']/parent::div[@class='used_result_container']。如果您需要更高级别,请使用ancestor。 lxml还在其搜索结果中提供getparent()方法,您也可以使用它。真的,你应该看看我链接的XPath站点;你可以从那里解决你的问题。

  

那么你如何访问重复的数据结构?

DOM查询似乎完全适合您的需求。 XPath查询返回一个找到的元素列表 - 你还想要什么?尽管它的名字,lxml确实接受'松散的HTML'。此外,解析器识别HTML中的“sign-posts”并相应地构造整个文档,因此您不必自己完成。

是的,您仍然需要对结构进行搜索,但需要进行更高级别的抽象。如果网站设计者决定进行页面检查并完全更改其div的名称和结构,那么这太糟糕了,您必须重写查询,但它应该比重写正则表达式花费更少的时间。没有什么能自动为你做,除非你想在你的页面刮板中写一些AI功能......

我为没有提供“原生Win32”库而道歉,我首先假设您只是意味着“在Windows上运行”。但其他人已经回答了这一部分。

答案 1 :(得分:5)

对.NET使用Html Agility Pack

<强>更新

由于您需要原生/古董,并且标记可能不好,我建议通过Tidy运行标记,然后使用Xerces

进行解析

答案 2 :(得分:5)

原生Win32

您始终可以使用IHtmlDocument2。此时内置于Windows。使用此COM接口,您可以 native 访问功能强大的DOM解析器(IE的DOM解析器!)。

答案 3 :(得分:3)

使用Beautiful Soup

  

Beautiful Soup是一个HTML / XML解析器   对于Python甚至可以变得无效   标记到解析树中。它提供   简单,惯用的导航方式,   搜索和修改解析   树。它通常可以节省程序员   几小时或几天的工作。还有一个   Ruby端口名为Rubyful Soup

答案 4 :(得分:2)

如果你真的在Win32下,你可以使用一个小而快的COM对象来实现它

使用vbs的示例代码:

Set dom = CreateObject("htmlfile")
dom.write("<div>Click for <img src='http://www.google.com/images/srpr/logo1w.png'>Google</a></div>")
WScript.Echo(dom.Images.item(0).src)

您也可以在Windows上使用JScript或VB / Dephi / C ++ / C#/ Python等执行此操作。它直接使用mshtml.dll dom布局和解析器。

答案 5 :(得分:0)

使用DOM解析器

e.g。对于java检查此列表

Open Source HTML Parsers in Java(我喜欢使用眼镜蛇)

或者,如果您确定,例如你只想解析你的html的某个子集,理想情况下也是xml有效你可以使用一些xml解析器来解析你传入它的片段,然后甚至使用xpath来请求你感兴趣的值。

Open Source XML Parsers in Java(例如dom4j易于使用)

答案 6 :(得分:0)

另一种方法是使用html dom解析器。不幸的是,似乎他们中的大多数都有糟糕的html问题,所以另外你需要先通过html整理或类似的东西来运行它。

答案 7 :(得分:0)

如果DOM解析器不可能 - 无论出于何种原因, 我会选择PHP explode()的某些变体或您使用的编程语言中提供的任何变体。

例如,您可以通过<div class="vehicleInfo">拆分开始,这将为您提供每个结果(请记住忽略第一个位置)。之后,您可以循环结果,将每个结果拆分为<div class="makemodeltrim">等。

这绝不是一个最佳解决方案,它将非常脆弱(几乎任何文档布局的变化都会破坏代码)。

另一个选择是为你的编程语言提供一些CSS选择器库,如phpQuery或类似的。

答案 8 :(得分:0)

我认为libxml2尽管有其名称,但也尽力解析标签汤HTML。它是一个C库,所以它应该满足您的要求。你可以找到它here

BTW,另一个答案推荐lxml,这是一个Python库,但实际上是建立在libxml2上的。如果lxml对他有效,那么libxml2很可能适合你。

答案 9 :(得分:0)

如何将Internet Explorer用作ActiveX控件?它会在查看页面时为您提供完全呈现的结构。

答案 10 :(得分:0)

Perl中的HTML :: Parser和HTML :: Tree模块非常适合在Web上解析大多数典型的所谓HTML。从那里,您可以使用类似XPath的查询来定位元素。

答案 11 :(得分:0)

您如何看待ihtmldocument2, 我认为它应该有所帮助。