Win32:如何在不使用正则表达式的情况下抓取HTML?

15
最近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

您可以看到我想要提取的值,它们被包含在方括号中:

  • 网址
  • 制造商和型号
  • 公里数
  • 价格
  • 位置

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

那么应该怎么做呢?

假设条件:

  • 本地Win32
  • 松散的html

澄清假设:

本地Win32

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

本地Win32应用程序可以调用库代码:

  • 复制的源代码
  • 包含函数入口点的DLL
  • 包含COM对象的DLL
  • 包含COM对象的DLL是COM-callable包装器(CCW),围绕托管.NET对象

宽松HTML

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

宽松HTML意味着HTML不是良好格式化的xml(严格的HTML无论如何也不是良好格式化的xml),因此不能使用XML解析器。实际上,我认为任何HTML解析器都必须在接受的HTML方面很慷慨。


澄清#2

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

听起来我正在用另一组正则表达式问题来交换问题。如果它们更改HTML的结构,则必须重新编写匹配代码-就像使用正则表达式一样。并且假设我们想避免这些问题,因为这些是正则表达式的问题,那么我该怎么办?

我不会为DOM节点编写正则表达式解析器吧?我正在编写一个引擎来解析对象字符串,使用内部状态机和前向和后向捕获。不,一定有更好的方法-就像Jeff所暗示的那样。

我故意保持原始问题含糊不清,以免误导人们走错路。我不想暗示解决方案与以下内容有任何关系:

  • 遍历DOM树
  • xpath查询

澄清#3

我提供的示例HTML已经删减到重要的元素和属性。我用于删除HTML的机制是基于我使用正则表达式的内在偏见。我自然而然地认为我需要在HTML中寻找各种标志

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

更新4

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

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

在这种特定情况下,我需要所有包含vehicleInfo DIV节点作为子节点的used_result_container DIV节点。不包含vehicleInfo作为子节点的任何used_result_container DIV节点都不相关。

是否有带有捕获和前向匹配的DOM正则表达式解析器?我认为XPath不能根据较低级别节点的标准选择更高级别节点:

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

注意: 我很少使用XPath,因此我不能很好地编写假设的xpath语法。


+1 你已经指定需要接受格式不良的HTML。您可以指定其他可能的假设。解决方案应尽可能抵抗被爬取页面结构的更改。还要指定哪些语言是可接受的,以及.NET / COM组件是否可接受? - MarkJ
1
解析HTML通常不是一个坏主意,但用正则表达式尝试解析它是一个坏主意。 - Svante
COM组件可以从Win32应用程序中调用,最好是它们已经在支持的Microsoft Windows操作系统上注册过了。.NET组件只有在具有COM可调用包装器(CCW)的情况下才能从本机Win32中调用,这取决于库。 - Ian Boyd
“重复的数据结构”是什么意思?您是指页面上有一个vehicleInfo divs列表,并且您想提取每个div的carlink吗? - int3
1
你很幸运,页面的作者非常擅长以反映内容而非展示方式来命名 div。即使有了你的限制,这也使问题变得容易了数个数量级。 - Stephen Harmon
显示剩余7条评论
12个回答

8
Python:

lxml - 更快,也许更擅长解析不良的HTML

BeautifulSoup - 如果lxml无法处理您的输入,请尝试这个。

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

Nokogiri

hpricot

虽然如果您的解析器出现问题,并且您可以大致确定导致故障的原因,我认为使用正则表达式来删除该部分然后将其传递给解析器是可以接受的。

如果你决定使用lxml,这里有一些XPath教程可能对你有用。lxml教程假设你知道XPath是什么(我第一次阅读它们时不知道)。 编辑:你的帖子自首次发布以来已经发展了很多...我会尽力回答我能回答的问题。

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

它可以。尝试//div[@class='vehicleInfo']/parent::div[@class='used_result_container']。如果需要向上进一步,请使用ancestor。lxml还在其搜索结果上提供了一个getparent()方法,您也可以使用该方法。真正的问题在于,您应该查看我链接的XPath网站;您可能可以从那里解决问题。

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

似乎DOM查询非常适合您的需求。XPath查询将返回您找到的元素列表 - 您还需要什么呢?尽管它的名称是lxml,但它确实接受“松散的HTML”。此外,解析器识别HTML中的“标志”,并相应地结构化整个文档,因此您不必自己这样做。
是的,您仍然需要在结构上进行搜索,但在更高的抽象级别上。如果网站设计师决定进行页面改版并完全更改其div的名称和结构,则很遗憾,您必须重写查询,但这应该比重写正则表达式花费更少的时间。除非您想将一些AI功能编写到您的页面爬取器中,否则没有任何东西会自动执行它...
我很抱歉没有提供“本机Win32”库,我最初认为您只是指“在Windows上运行”。但其他人已经回答了那一部分。

对于lxml特别赞一下——在这个应用程序中取得了巨大的成功。 - overthink
在Ruby方面,还有ScrAPI:http://blog.labnotes.org/2006/07/11/scraping-with-style-scrapi-toolkit-for-ruby/ - Douglas F Shearer
有没有任何可用于本地 Win32 应用程序中使用的? - Ian Boyd

5
使用 Html Agility Pack 来处理 .NET 相关内容。 更新 由于您需要一些原生/古老的东西,并且标记很可能不好,我建议先通过 Tidy 清理标记,然后再使用 Xerces 进行解析。

2
我写的是同样的内容 - 只不过我在前面加了“你没有指定你选择的开发工具...但是你已经指定了Windows,所以如果你使用.NET,那么:” - Murph
我没有指定编译器,但我指定了本地Win32。假设它是C ++。 - Ian Boyd

5

本地Win32

你可以始终使用IHtmlDocument2。这已经内置于Windows中了。通过这个COM接口,你可以获取到一个强大的DOM解析器(IE的DOM解析器!)的本地访问。


我过去使用过IHtmlDocument2。我也有一个可以解析无效HTML并将其转换为DOM的对象的源代码。那么如何遍历DOM树和重复结构? - Ian Boyd
@Ian,关键是IHtmlDocument2将能够处理来自野外的HTML - 无论它有多么混乱。遍历DOM就像调用“all”并使用元素(DOM是分层的)一样容易。这并不好玩,但如果你想保持本地化,这是一个“简单”的解决方案。http://msdn.microsoft.com/en-us/library/aa752582(VS.85).aspx - Frank Krueger

3

使用Beautiful Soup

Beautiful Soup是Python的HTML/XML解析器,可以将无效标记转换为解析树。它提供了简单、惯用的方法来浏览、搜索和修改解析树。通常可以节省程序员数小时或数天的工作时间。还有一个名为Rubyful Soup的Ruby端口。


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布局和解析器。


0
使用DOM解析器
例如,对于Java,请查看此列表 Java中的开源HTML解析器(我喜欢使用cobra)
或者,如果您确定只想解析某个子集的HTML,最好也是XML有效的,则可以使用一些XML解析器仅解析您传递给它的片段,然后甚至使用XPath请求您感兴趣的值。 Java中的开源XML解析器(例如dom4j易于使用)

问题说的是本地Win32,也就是32位Windows,并没有指定语言。 - MarkJ
问题还提到了“松散的HTML”,这意味着不接受XML解析器。 - MarkJ

0
另一种方法是使用HTML DOM解析器。不幸的是,它似乎大多数解析器不能处理格式不良的HTML,因此您需要先将其运行通过HTML Tidy或类似工具。

我认为我们正在寻找特定解析器和整理工具的具体建议。 - MarkJ
谢谢,当时他没有提供任何具体信息,但现在似乎他已经添加了信息。 - Rob

0
如果DOM解析器不可行 - 无论出于什么原因, 我会选择 PHP 的 explode() 或者您所使用的编程语言中提供的任何变体。
例如,您可以通过 <div class="vehicleInfo"> 进行拆分,这将为您提供每个结果(记得忽略第一个位置)。之后,您可以循环结果,通过 <div class="makemodeltrim"> 等拆分每个结果。
这绝对不是最优解决方案,而且它将非常脆弱(几乎在文档布局的任何更改都会导致代码崩溃)。
另一个选项是使用某些CSS选择器库,如 phpQuery 或类似的库来处理您所使用的编程语言。

1
老派的爬虫技术。如果你在使用这种方法,不妨考虑使用正则表达式。 - Nosredna

0

我认为libxml2尽管名字中带有XML,但也可以很好地解析标签混乱的HTML。它是一个C库,因此应该满足您的要求。您可以在这里找到它。

顺便说一句,另一个答案推荐了lxml,它是一个Python库,但实际上是基于libxml2构建的。如果lxml对他有效,那么libxml2对您也很可能有效。


0

使用Internet Explorer作为ActiveX控件怎么样?它将会给你一个完全渲染的结构,就像它浏览页面时的样子。


这是 IHtmlDocument2 - 除了您不必浪费资源来启动渲染器。 - Frank Krueger

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接