上次用PHP做资讯聚合器的时候,被载入外部网页后的DOM乱码折腾得够呛。明明浏览器里显示正常的HTML5页面,用DOMDocument::loadHTML一解析,标签胡乱闭合,article和section这些元素全被当成了普通div,甚至有些自闭合标签直接吃掉了后面的内容。后来才知道,老版本的PHP DOM底层依赖libxml2,它是按HTML 4.01的规则来解析的,对现代HTML5文档的容错和支持非常有限。
PHP 8.4 终于把这个问题从根上解决了——它引入了对HTML5解析的内置支持,底层使用Lexbor库,可以像浏览器一样理解HTML5语义。这次升级让我们不用再依赖第三方的symfony/dom-crawler或者PHP Simple HTML DOM Parser,直接用原生DOM就能稳稳地解析各种来源的HTML5。这篇文章我们就以一个新闻列表抓取任务为例,把整个新解析流程完整跑一遍。
PHP 8.4 之前的痛点
先看一个典型场景:你从某个新闻网站拿到一段HTML5代码,包含 <header>、<footer>、<article> 等语义标签,还有一些这样的自闭合标签。在PHP 8.3及以前,用
DOMDocument::loadHTML 解析后,结构会变得面目全非——<article> 可能被当作未知标签直接忽略,里面的子节点被提出来变成平级;<img> 因为没有闭合标签导致后面若干内容被吞掉;更不用说 <template> 这类元素直接被丢弃。
要想解析得靠谱,过去要么自己先正则预处理,要么引入 masterminds/html5 这类第三方库把HTML5转成XML才能喂给DOM。绕了一大圈,稳定性还是打折扣。
PHP 8.4 的HTML5解析模式怎么开启
好消息是,PHP 8.4 在 DOMDocument 上新增了一个常量 DOM_HTML5,同时扩容了 loadHTML 和 loadHTMLFile 的第二个参数 $options。你只需要在加载HTML时传入这个常量,或者把它和别的选项按位或即可:
$doc = new DOMDocument();
// 开启HTML5解析
$doc->loadHTML($html, DOM_HTML5);
如果要同时抑制解析错误(比如不规范的标记),可以结合 LIBXML_NOERROR 等常量:
$doc->loadHTML($html, DOM_HTML5 | LIBXML_NOERROR | LIBXML_NOWARNING);
一旦开启了 DOM_HTML5,底层的解析引擎就会切换到 Lexbor,它专门为HTML5规范设计,能够正确处理所有HTML5语义标签、隐式闭合、以及对模板、表格等复杂结构的容错。
实战:抓取一个新闻站点的文章列表
假设我们要抓取某个技术博客的首页,提取出每篇文章的标题、链接、摘要和缩略图。目标页面的HTML结构大致如下(简化版):
<main>
<article class="post">
<h2><a href="/post/123" rel="external nofollow" >文章标题</a></h2>
<p class="excerpt">摘要内容...</p>
<img src="/img/thumb.jpg" alt="">
</article>
<!-- 更多 article -->
</main>
以前用老解析器,article 标签里的结构经常散掉,特别是 img 后面的内容很可能会被错误闭合。现在用HTML5模式就稳了。
1. 获取网页内容
使用 file_get_contents 拿到HTML字符串。如果遇到编码问题,可以之后在DOM中处理。
$html = file_get_contents('https://example-tech-blog.com');
2. 加载并解析
$doc = new DOMDocument();
$doc->loadHTML($html, DOM_HTML5 | LIBXML_NOERROR);
3. 用XPath定位文章节点
创建一个XPath实例,然后查询所有的 article 元素。
$xpath = new DOMXPath($doc);
$articles = $xpath->query("//main/article");
注意如果不开启HTML5模式,这里可能根本找不到 article 元素,因为老解析器把它们转换成了别的标签。
4. 遍历提取数据
$result = [];
foreach ($articles as $article) {
// 提取标题和链接
$titleNode = $xpath->query('.//h2/a', $article)->item(0);
if (!$titleNode) continue;
$title = $titleNode->textContent;
$link = $titleNode->getAttribute('href');
// 提取摘要
$excerptNode = $xpath->query('.//p[@class="excerpt"]', $article)->item(0);
$excerpt = $excerptNode ? $excerptNode->textContent : '';
// 提取图片
$imgNode = $xpath->query('.//img', $article)->item(0);
$thumb = $imgNode ? $imgNode->getAttribute('src') : '';
$result[] = [
'title' => trim($title),
'link' => $link,
'excerpt' => trim($excerpt),
'thumb' => $thumb,
];
}
这一步走下来,即使HTML源文件里有嵌套错误、标签未闭合,HTML5解析器也会按照浏览器的方式修正文档树,保证每个 article 都是完整的块。
处理编码与实体打印
有些网页的 meta charset 声明可能不正确,导致中文乱码。可以在加载之前把字符集统一转成UTF-8,或者在 loadHTML 调用前添加正确的 meta 标签声明编码。推荐做法是先用 mb_convert_encoding 转码,再传入。
$html = mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8');
另外 DOMNode::textContent 返回的是原样的文本,如果包含实体(如 ),也会保留。根据需求可以再用 html_entity_decode 处理。
高级技巧:处理非标准标签与SVG
因为是HTML5解析,自定义 Web Components 标签(比如 <my-widget>)也能被完好保留,不会像老解析器一样被丢弃。这对于需要抓取现代SPA生成的静态标记非常有利。同样,行内 <svg> 元素也会被正确解析为DOM节点,我们可以进一步提取矢量图内的文本或属性。
对比传统DOM解析的改进点
- 语义标签保留:
article、section、nav等不再被转换为未知元素或忽略。 - 自闭合标签:
img、br、input等不会导致后续节点丢失。 - 模板元素:
template标签及其内容现在会被保存,老解析器会直接删掉。 - 容错性:缺失的
tbody等会被自动补全,就像浏览器一样。
性能与兼容性
HTML5解析器比libxml2稍微慢一些,但在单个页面的抓取任务中几乎感觉不到差异。如果一次处理上千个页面,可以自己做个基准测试。PHP 8.4 是目前唯一内置该解析器的版本,往前只能使用第三方的HTML5库。不过现在这个特性已经让原生PHP在简单的网页抓取场景中自给自足,省掉了一个依赖。
注意事项
- DOM_HTML5 是常量 0x01,只能在
loadHTML和loadHTMLFile中使用,loadXML不受影响。 - 开启后,文档编码的自动检测可能略有不同,建议始终明确处理编码。
- 如果抓取的目标页面极其巨大,可能需要增加
memory_limit,因为DOM树会更完整,占用略多内存。 - 仍然要注意请求频率和robots协议,合法抓取数据。
总结
PHP 8.4 的HTML5 DOM解析,把过去最让人头疼的网页抓取稳定性问题彻底根治了。现在我们能用熟悉的DOMDocument + XPath,直接应对现代HTML5页面,不再需要额外安装 HTML5-PHP 或者 symfony/dom-crawler。在资讯聚合、竞品监控、内容迁移这类场景里,拿这套原生方案去写提取逻辑,代码量更少、出错几率更低,维护起来也自然得多。
如果你的项目刚好升级到PHP 8.4,不妨把那些曾经因为解析失败而写的正则拼接和预处理逻辑删掉,换成一行 DOM_HTML5。你会发现,原生PHP的抓取能力,原来一直都被低估了。

