PHP中如何解析和处理HTML/XML?

2305

如何解析HTML/XML并从中提取信息?

31个回答

2007

本地XML扩展

我更喜欢使用其中一个本地XML扩展, 因为它们与PHP捆绑在一起,通常比所有第三方库更快,并且可以完全控制标记。

DOM

DOM扩展允许您通过PHP 5中的DOM API对XML文档进行操作。它是W3C的文档对象模型核心级别3的实现,这是一个平台和语言中立的接口,允许程序和脚本动态访问和更新文档的内容、结构和样式。

DOM能够解析和修改现实世界(破碎的)HTML,并且它可以执行XPath查询。它基于libxml

要想熟练使用DOM需要一些时间,但我认为这段时间值得花费。由于DOM是一个语言不可知的接口,所以你会发现许多语言都有其实现,因此如果您需要更改编程语言,那么您很可能已经知道如何使用该语言的DOM API。

如何使用DOM扩展已经在StackOverflow上得到了详细介绍,因此如果您选择使用它,可以确信您遇到的大多数问题都可以通过搜索/浏览Stack Overflow来解决。

其他答案中提供了基本用法示例一般概念概述

XMLReader

XMLReader扩展是一个XML拉取解析器。阅读器充当游标,在文档流上向前移动,并在途中停留在每个节点。

像DOM一样,XMLReader也基于libxml。我不知道如何触发HTML解析器模块,因此使用XMLReader解析损坏的HTML可能比使用DOM更不稳健,因为您可以明确告诉它使用libxml的HTML解析器模块。

另一个答案中提供了基本用法示例

XML Parser

此扩展允许您创建XML解析器,然后为不同的XML事件定义处理程序。每个XML解析器还有一些可以调整的参数。

XML解析器库也基于libxml,并实现了SAX风格的XML推送解析器。它可能比DOM或SimpleXML更适合内存管理,但比XMLReader实现的拉取解析器更难使用。

SimpleXml

简单XML扩展提供了一个非常简单且易于使用的工具集,可以将XML转换为可使用普通属性选择器和数组迭代器处理的对象。如果需要解析损坏的HTML,请不要考虑SimpleXml,因为它会出错。PHP手册中提供了基本用法示例以及大量其他示例

第三方库(基于libxml)

如果您想使用第三方库,我建议使用一个实际上使用DOM/libxml的库,而不是字符串解析。

FluentDom

FluentDOM为PHP中的DOMDocument提供了类似于jQuery的流畅XML接口。选择器使用XPath或CSS编写(使用CSS到XPath转换器)。当前版本扩展了DOM,实现了标准接口,并添加了来自DOM Living Standard的功能。FluentDOM可以加载JSON、CSV、JsonML、RabbitFish等格式。可以通过Composer安装。

HtmlPageDom

Wa72\HtmlPageDom是一个用于使用DOM轻松操作HTML文档的PHP库。它需要Symfony2组件中的DomCrawler进行遍历DOM树,并通过添加操作HTML文档的DOM树的方法来扩展它。

phpQuery

phpQuery是基于jQuery JavaScript库的服务器端、可链接的、CSS3选择器驱动的文档对象模型(DOM)API。该库使用PHP5编写,并提供额外的命令行界面(CLI)。

虽然被描述为“废弃且有缺陷:使用需谨慎”,但似乎仍在最低限度地维护。

laminas-dom

Laminas\Dom组件(前身为Zend_DOM)提供了处理DOM文档和结构的工具。目前,我们提供Laminas\Dom\Query,它提供了一个统一的接口,利用XPath和CSS选择器查询DOM文档。

此软件包被认为已经功能完备,并处于仅安全性维护模式。

fDOMDocument

fDOMDocument扩展了标准DOM以在所有错误情况下使用异常而不是PHP警告或通知。他们还添加了各种自定义方法和快捷方式,以方便使用DOM并简化其用法。

sabre/xml

sabre/xml是一个库,它包装和扩展XMLReader和XMLWriter类以创建一个简单的“xml到对象/数组”映射系统和设计模式。写入和读取XML是单遍的,因此可以在大型XML文件上快速且需要低内存。

FluidXML

FluidXML是一个PHP库,用于使用简洁流畅的API操作XML。它利用XPath和流畅编程模式使操作变得有趣且有效。


第三方(非基于libxml)

构建在DOM/libxml之上的好处是,由于您基于本机扩展,因此可以获得良好的性能。然而,并非所有第三方库都采用这种方法。以下是其中一些:

PHP简单HTML DOM解析器

  • 一个用PHP5+编写的HTML DOM解析器,让您以非常简单的方式操纵HTML!
  • 需要PHP 5+。
  • 支持无效的HTML。
  • 像jQuery一样使用选择器在HTML页面上查找标签。
  • 在一行中提取HTML内容。

我通常不推荐使用此解析器。代码库很糟糕,解析器本身相当缓慢且占用内存。并非所有jQuery选择器(例如子选择器)都可行。任何基于libxml的库都应该轻松胜过它。

PHP HTML解析器

PHPHtmlParser是一个简单、灵活的HTML解析器,允许您使用任何CSS选择器(如jQuery)选择标签。目标是为开发需要快速、简便的方式来抓取HTML的工具提供帮助,无论它是否有效!这个项目最初是由sunra/php-simple-html-dom-parser支持的,但支持似乎已经停止了,所以这个项目是我对他以前的工作的适应。

再次强调,我不建议使用这个解析器。它的速度相对较慢,CPU 使用率高。同时,也没有清除已创建 DOM 对象内存的功能。这些问题尤其在嵌套循环中表现得更为明显。文档本身存在不准确和拼写错误,自 2016 年 4 月 14 日以来也没有修复响应。


HTML 5

您可以使用上述方法解析HTML5,但由于HTML5允许标记的存在,可能会出现一些怪异情况。因此,对于HTML5,您可能需要考虑使用专用解析器。请注意,这些解析器是用PHP编写的,因此与低级语言中的编译扩展相比,性能较慢且内存使用增加。

HTML5DomDocument

HTML5DOMDocument扩展了原生的DOMDocument库。它修复了一些错误并添加了一些新功能。

  • 保留HTML实体(DOMDocument不会)
  • 保留空标签(DOMDocument不会)
  • 允许插入HTML代码,将正确的部分移到它们应该去的地方(head元素插入到head中,body元素插入到body中)
  • 允许使用CSS选择器查询DOM(目前可用的有:*tagnametagname#id#idtagname.classname.classnametagname.classname.classname2.classname.classname2tagname[attribute-selector][attribute-selector]div, pdiv pdiv > pdiv + pp ~ ul
  • 添加对element->classList的支持。
  • 添加对element->innerHTML的支持。
  • 添加对element->outerHTML的支持。

HTML5

HTML5是一个符合标准的HTML5解析器和编写器,完全由PHP编写。它稳定可靠,被许多生产网站使用,并已经超过500万次下载。

HTML5提供以下功能。

一个HTML5序列化器 支持PHP命名空间 Composer支持 基于事件的(SAX-like)解析器 DOM树构建器 与QueryPath的互操作性 运行在PHP 5.3.0或更新版本

正则表达式

最后也是最不推荐的,你可以使用正则表达式从HTML中提取数据。一般来说,在HTML上使用正则表达式是不被鼓励的。

大多数在网上找到的匹配标记的片段都很脆弱。在大多数情况下,它们只适用于非常特定的HTML片段。微小的标记更改,比如在某个地方添加空格或添加或更改标记中的属性,可能会导致正则表达式失败,如果没有正确编写,则会出现问题。在对HTML使用RegEx之前,您应该知道自己在做什么。

HTML解析器已经知道HTML的语法规则。必须为您编写的每个新正则表达式教授正则表达式。在某些情况下,RegEx很好,但这确实取决于您的用例。

可以编写更可靠的解析器, 但使用正则表达式编写完整且可靠的自定义解析器是浪费时间的,因为前面提到的库已经存在并且在此方面执行得更好。

还请参阅以克苏鲁方式解析HTML


书籍

如果你想花点钱,可以看看:

我与PHP Architect或作者没有关联。


12
@Naveed 这取决于你的需求。我不需要使用CSS选择器查询,这就是为什么我只使用DOM和XPath。phpQuery旨在成为jQuery的端口。Zend_Dom很轻巧。你必须自己尝试使用它们来确定哪一个最适合你。 - Gordon
3
@Ms2ger 大多数情况下是这样,但并非完全如此。就像上面已经指出的那样,您可以使用基于libxml的解析器,但在某些特殊情况下,它们会出现问题。如果您需要最大兼容性,则最好使用专用解析器。我更喜欢保持区别。 - Gordon
10
您不使用PHP简易HTML DOM解析器的观点似乎没有意义。 - Petah
4
截至2012年3月29日,DOM不支持HTML5,XMLReader不支持HTML,并且PHP的html5lib的最后提交日期是2009年9月。用什么来解析HTML5、HTML4和XHTML? - Shiplu Mokaddim
8
@Nasha 我故意在上面的列表中不包括臭名昭著的Zalgo怒吼,因为它本身并不太有帮助,并且由于它的存在导致了相当多的迷信。无论正则表达式是否是解决方案,人们都会被这个链接打击。为了获得更加平衡的观点,请查看我确实包含的链接,并浏览 http://stackoverflow.com/questions/4245008/php-regex-simple-regex-for-bbcode-s-or-strike-fails-to-work 上的评论。 - Gordon
显示剩余10条评论

345

尝试使用Simple HTML DOM Parser

  • 一个基于PHP 5+编写的HTML DOM解析器,可以非常简单地操纵HTML!
  • 需要PHP 5+。
  • 支持无效的HTML。
  • 像jQuery一样使用选择器在HTML页面上查找标记。
  • 从HTML中提取内容只需要一行代码。
  • 下载

注意:正如其名称所示,它可以用于简单的任务。它使用正则表达式而不是HTML解析器,因此在处理更复杂的任务时速度会相对较慢。它的大部分代码库是在2008年编写的,自那时以来只进行了小幅改进。它不遵循现代PHP编码标准,并且难以合并到现代的PSR兼容项目中。

示例:

如何获取HTML元素:

// Create DOM from URL or file
$html = file_get_html('http://www.example.com/');

// Find all images
foreach($html->find('img') as $element)
       echo $element->src . '<br>';

// Find all links
foreach($html->find('a') as $element)
       echo $element->href . '<br>';

如何修改HTML元素:

// Create DOM from string
$html = str_get_html('<div id="hello">Hello</div><div id="world">World</div>');

$html->find('div', 1)->class = 'bar';

$html->find('div[id=hello]', 0)->innertext = 'foo';

echo $html;

从HTML中提取内容:

// Dump contents (without tags) from HTML
echo file_get_html('http://www.google.com/')->plaintext;

爬取Slashdot网站:

// Create DOM from URL
$html = file_get_html('http://slashdot.org/');

// Find all article blocks
foreach($html->find('div.article') as $article) {
    $item['title']     = $article->find('div.title', 0)->plaintext;
    $item['intro']    = $article->find('div.intro', 0)->plaintext;
    $item['details'] = $article->find('div.details', 0)->plaintext;
    $articles[] = $item;
}

print_r($articles);

9
首先,我需要为一些事情做好准备,例如出现错误的DOM、无效代码,还要使用JavaScript分析DNSBL引擎,以便查找恶意站点/内容。由于我已经围绕一个框架构建了我的网站,因此需要保持代码的整洁、易读和结构良好。SimpleDim很棒,但代码有点混乱。 - RobertPitt
正如我所说,我以前多次使用过简单的DOM,它非常出色,只是在寻找一个代码更清晰、高度可扩展、面向对象(P|D)等方面更好的系统。 - RobertPitt
9
@Robert,你也可以考虑查看 http://htmlpurifier.org/,了解与安全相关的内容。 - Gordon
3
他有一个正确的观点:simpleHTMLDOM 很难扩展,除非使用装饰器模式,但我发现它很笨重。我发现自己只能直接修改基础类本身,这让我感到不寒而栗。 - Erik
1
我所做的是在将html发送到SimpleDOM之前,先通过tidy运行它。 - MB34
2
我目前正在使用它,将其作为项目的一部分来处理几百个URL。它变得非常缓慢,经常出现超时问题。这是一个很好的初学者脚本,直观简单易学,但对于更高级的项目来说太基础了。 - luke_mclachlan

250

只需使用DOMDocument->loadHTML()即可完成。libxml的HTML解析算法非常出色且快速,并且与流行观点相反,不会因HTML格式错误而出错。


20
没问题。它适用于PHP内置的XPath和XSLTProcessor类,这对于提取内容非常棒。 - Kornel
8
如果HTML代码太过混乱,你可以先使用htmltidy对其进行处理,然后再交给DOM处理。每当我需要从HTML中抓取数据时,我总是使用DOM,或者至少使用simplexml。 - Frank Farmer
10
加载格式错误的HTML时,需要调用libxml_use_internal_errors(true)函数以防止警告中止解析。 - Husky
6
我使用DOMDocument解析了大约1000个HTML源(各种语言编码的不同字符集),没有遇到任何问题。但是您可能会遇到编码问题,但这些问题不是无法克服的。您需要知道三件事:1)loadHTML使用meta标记的charset来确定编码;2)如果HTML内容中不包含此信息,则#2可能导致不正确的编码检测;3)坏的UTF-8字符可能会使解析器出错。在这种情况下,请使用mb_detect_encoding()和Simplepie RSS解析器的编码/转换/剥离坏的UTF-8字符代码的组合来进行解决。 - Zero
1
是的,但DOMDocument不支持CSS和XPATH查询,只支持getElementById或getElementsByTagName。 - umpirsky
显示剩余3条评论

155

为什么你不应该(以及何时应该)使用正则表达式?

首先,一个常见的错误:正则表达式并不适用于 "解析" HTML。但是它们可以用于 "提取" 数据。提取数据是它们的制作目的。与正确的 SGML 工具包或基线 XML 解析器相比,正则表达式 HTML 提取的主要缺点是它们的语法复杂度和可靠性各不相同。

考虑一下如何制作一个相对可靠的 HTML 提取正则表达式:

<a\s+class="?playbutton\d?[^>]+id="(\d+)".+?    <a\s+class="[\w\s]*title
[\w\s]*"[^>]+href="(http://[^">]+)"[^>]*>([^<>]+)</a>.+?

与简单的 phpQuery 或 QueryPath 等价表达相比,它的可读性要低得多:

$div->find(".stationcool a")->attr("title");

然而,有一些特定的用例它们可以帮上忙。

  • 很多 DOM 遍历前端不会显示 HTML 注释 <!--,但是注释有时更适合于提取。特别是伪 HTML 变体 <$var> 或 SGML 残留物可以使用正则表达式轻松处理。
  • 通常情况下,正则表达式可以节省后处理时间。但是 HTML 实体通常需要手动处理。
  • 最后,对于类似提取 <img src= urls 的极其简单的任务,实际上它们是一种可行的工具。与 SGML/XML 解析器相比,它们的速度优势主要体现在这些非常基本的提取程序中。

有时候,事先使用正则表达式 /<!--CONTENT-->(.+?)<!--END-->/ 提取 HTML 片段并使用更简单的 HTML 解析器前端处理其余部分甚至是明智的选择。

注意:我实际上有一个应用程序,在其中交替使用 XML 解析和正则表达式。就在上周,PyQuery 解析崩溃了,但正则表达式仍然有效。是的,很奇怪,我自己也无法解释。但就这样发生了。
所以,请不要因为它不符合 "正则表达式=邪恶" 的梗而将现实考虑投票下降。 但是也请不要过于高估它。这只是这个主题的一个侧面说明。


21
DOMComment可以读取注释,因此没有必要使用正则表达式来实现。 - Gordon
4
SGML工具包和XML解析器都不适合用于解析现实世界的HTML。为此,只有专门的HTML解析器才是合适的。 - Alohci
12
DOM 使用 libxml 库,而 libxml 有一个独立的 HTML 解析器模块。当使用 loadHTML() 方法加载 HTML 时,会自动使用该解析器模块,因此可以很好地加载“现实世界”(也就是有缺陷的)HTML。 - Gordon
6
关于你的“真实世界考虑”观点,我想说几句。当解析 HTML 时,正则表达式确实有一些有用的情况;使用 GOTO 也有一些有用的情况;变量变量也是如此。因此,并没有任何一种特定的实现方式是肯定会导致代码腐化的。但这是一个非常强烈的警告信号。而普通开发者往往不够细致,无法分辨其中的差别。所以,总的来说,正则表达式、GOTO 和变量变量都是邪恶的。当然,也有非邪恶的使用情况,但这些情况是例外的(而且罕见)……(以我个人的看法)。 - ircmaxell
11
@mario:实际上,可以使用正则表达式“正确地”解析HTML,尽管通常需要多个正则表达式才能做到。在一般情况下,这是非常麻烦的。但是,在具有明确定义输入的特定情况下,它接近微不足道。这些情况下人们应该使用正则表达式。对于一般情况,你真正需要的是庞大而复杂的解析器,尽管对于普通用户来说,很难确定该如何划分这条线。无论哪种代码更简单和更容易,都将获胜。 - tchrist
显示剩余2条评论

137

请注意,此答案推荐的库已经停用了10多年。

phpQueryQueryPath 在复制 jQuery 流畅 API 方面非常相似。这也是它们是在 PHP 中进行 HTML 解析的两种最简单方法之一的原因。

QueryPath 示例

基本上,你首先需要从一个 HTML 字符串创建一个可查询的 DOM 树:

 $qp = qp("<html><body><h1>title</h1>..."); // or give filename or URL

生成的对象包含HTML文档的完整树形结构表示。它可以使用DOM方法遍历。但通常的方法是像jQuery一样使用CSS选择器:

 $qp->find("div.classname")->children()->...;

 foreach ($qp->find("p img") as $img) {
     print qp($img)->attr("src");
 }
大多数情况下,您会使用简单的#id.classDIV标签选择器来进行->find()。但是您也可以使用XPath语句,有时它们更快。此外,像->children()->text()以及特别是->attr()这样的通常的jQuery方法简化了提取正确的HTML片段。(并且已经解码了它们的SGML实体。)
 $qp->xpath("//div/p[1]");  // get first paragraph in a div

QueryPath还允许将新标签注入流中(->append),然后输出和美化更新后的文档(->writeHTML)。它不仅可以解析格式不正确的HTML,还可以解析各种带有命名空间的XML方言,并且可以从HTML微格式(XFN、vCard)中提取数据。

 $qp->find("a[target=_blank]")->toggleClass("usability-blunder");

.

phpQuery或QueryPath?

通常情况下,QueryPath更适合文档操作。虽然phpQuery也实现了一些伪AJAX方法(仅是HTTP请求)以更接近jQuery的方式进行操作,但因为功能较少,所以phpQuery通常比QueryPath快。

有关差异的进一步信息,请参见这篇来自tagbyte.org的互联网存档比较文章。(原始来源已经丢失,所以这里是一个互联网存档链接。是的,你仍然可以找到丢失的页面,人们。)

优点

  • 简单而可靠
  • 易于使用的替代方案 ->find("a img, a object, div a")
  • 正确的数据反转义(与正则表达式匹配相比)

92

简单的HTML DOM是一个伟大的开源解析器:

simplehtmldom.sourceforge

它以面向对象的方式处理DOM元素,并且新的迭代版本对不规范的代码有很好的支持。还有一些像JavaScript中一样的优秀函数,比如“find”函数,它会返回所有该标签名称元素的实例。

我在许多不同类型的网页上测试过这个工具,并认为它运作得非常好。


63

这里没有提到的一种通用方法是将HTML通过Tidy处理,可以设置为输出保证有效的XHTML。然后您可以在其上使用任何旧的XML库。

但对于您的具体问题,您应该查看这个项目:http://fivefilters.org/content-only/ -- 它是Readability算法的修改版本,旨在从页面中提取文本内容(而不是标题和页脚)。


58
对于1a和2:我会投票支持新的Symfony Componet类DOMCrawler(DomCrawler)。 该类允许类似于CSS选择器的查询操作。有关实际示例,请查看此演示文稿:news-of-the-symfony2-world
该组件旨在独立工作,可以在没有Symfony的情况下使用。
唯一的缺点是它只能与PHP 5.3或更高版本一起使用。

类似jQuery的CSS查询很好,因为在W3C文档中缺少一些东西,但是这些功能作为额外功能存在于jQuery中。 - Nikola Petkanski

56

8
不严格地说(参见:http://en.wikipedia.org/wiki/Screen_scraping#Screen_scraping),关键在于“screen”;在所描述的情况下,没有涉及屏幕。尽管如此,不可否认的是,这个术语最近遭受了很多误用。 - Bobby Jack
4
我不会进行屏幕抓取,将被解析的内容是在内容供应商根据我的协议授权下的。 - RobertPitt

44
我们之前为自己的需要创建了相当多的爬虫。归根结底,通常是简单的正则表达式能够最好地完成任务。虽然上面列出的库因为它们的创造而很好,但是如果你知道自己在寻找什么,那么正则表达式是更安全的方法,因为你还可以处理非有效的HTML/XHTML结构,如果通过大多数解析器加载,这些结构将会失败。

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