跳到主要内容
别用正则解析 HTML,但理由不是那个

别用正则解析 HTML,但理由不是那个

慢半拍
慢半拍

· 阅读约 5 分钟

Stack Overflow 上每隔一阵就冒出同一个问题:怎么用正则把 HTML 里某一段抠出来。底下永远同一个回答:HTML 不是正则语言,换成 XML 解析器。

这个回答是错的。

准确说,结论未必错,理由是错的。而且错得不轻。

先摆那一套说法。形式语言理论里的"正则表达式"只描述正则文法,这没错。但理论里那个正则表达式,和 PCRE 里跑的那个东西,除了名字像,关系很弱。写 PHP 的人天天用的 preg_*,早就不是理论意义上的正则了。

有多不像?{aⁿbⁿ, n>0} 是教科书里最经典的非正则语言,讲乔姆斯基的都会拿它当分界线。PCRE 能匹配它,递归子模式引用就行。

再上一层。结构良好的 HTML 属于上下文无关语言。PCRE 能匹配全部上下文无关语言。按层级算,它够得着 HTML。¹

换句话说,那条分界线是理论里的,不是实现里的。

这句话说出来别扭,因为我们从小听的是反过来的版本。

还能再过分一点。加上零宽断言和相对引用,PCRE 至少能匹配一部分上下文敏感语言,比如 {aⁿbⁿcⁿ}。²

说到底,现代正则实现和"正则性"这个数学概念,只剩很弱的一点关系。

那为什么那句话的结论还是对的?

因为该看的不是能力边界,是失败模式。

两段 HTML,放到两个工具面前。一段格式良好,一段标签没闭合、属性没加引号。

理论层级在这里一点用都没有。DOM 库赢,不是因为它比 PCRE 高一级。执行能力上,这俩未必分得出胜负。DOM 库赢在它容忍坏输入,能从一堆乱字符里重建出一棵树,而且保证给你点东西。

正则不是。匹配到一半崩了,你手里什么都没有。没有部分结果,没有出错的位置,只有 false。

这才是分界线。不是能不能,是错的时候怎么办。

那个回答的后半段也没干净到哪去。它劝人换 XML 解析器,可处理通用 HTML 的正确工具是能容忍格式错误的 DOM 库。这两样不是一回事。它连自己的建议都没对齐。

所以那句话的坏处,不在于劝退了一个具体做法。它把判断标准教错了。人手一把错尺子,量出来的东西全是歪的。

一个人要是真信"正则只能处理正则语言",会绕很多不该绕的路。从页面上抠一个价格,他去上 DOM 解析器,装依赖,处理编码,写一堆选择器,就为了几行十年没变过格式的数据。

nikic 自己承认过这个矛盾。他常劝人别用正则解析 HTML,然后自己经常这么干。因为很多场景是具体的、封闭的,你事先知道输入长什么样。场景封闭的时候,正则是最省事的那把刀。³

这不是双标。是两个问题的答案本来就不同。

你可能会反对说,坏输入早晚会来。

这反驳有道理,但它反驳的是"无条件用正则",没人这么主张过。真正的主张是:判断依据应该是输入有多可信,不是这门语言属于第几型。你抓的是自己生成的 HTML,还是互联网上随手一个页面,是两件事。和正则不正则没关系。

插一句不太相干的。带反向引用的正则匹配是 NP 完全的,有人用 3-SAT 归约证明过,还真写过一条 PCRE 去解 3-CNF 布尔公式,跑出来的捕获组就是变量赋值。

这件事的意思不是"正则可厉害了"。是"你随手写的一条正则,匹配问题的复杂度能顶到 NP 完全"。它意味着某些正则在某些输入上会跑到天荒地老。又一个失败模式,不是能力边界。

把话说得更远一点:值得警惕的不是"用正则抠 HTML"这个行为,是"拿形式层级给工具判死刑"这个习惯。

这个句式到处都是。"这个不是某某范式该干的事。""理论上做不到。"听着很硬,其实只是把一句被用滥的口号当成了论据。

乔姆斯基层级是真的,形式语言那套也有用。它的问题是,它描述的是"语言属于哪一类",不是"你手里这个工具能不能处理你手里这份输入"。两件事被混在一起,然后传了十几年。

也不是没人指出来过。十四年前就有人写过一整篇,从正则文法一路推到非受限文法,把这条路上每一级都拿具体的正则试了一遍。⁴ 那篇文章今天还在,搜得到。可那句话还是照样在传。因为背一句口号比跑一遍测试便宜太多了。

开头那句"这个回答是错的",说得太满,收紧一点:结论那半边通常是对的,理由是错的。而且错的那半边在传播里跑得更快。

该问的问题从来不是 HTML 是不是正则语言。

是你那份输入坏掉之后,手里还剩什么。

前一个问题查十分钟就有答案,所以人人都答得上来。后一个问题得跑一遍才知道,所以没人愿意回答。

¹ 匹配和解析是两件事。PCRE 一般做不到完整解析、产出一棵语法树。你要的只是从里面抠点数据出来,这个区别通常不重要。

² 这里有条硬限制:后顾断言必须固定宽度,所以上下文敏感文法没法机械地转换成正则,只能一个个手工构造。作者也不知道能不能覆盖全部上下文敏感语言。这一条我保留怀疑。

³ 这不是给"用正则解析 HTML"背书。是说该不该用要按场景判,而场景判断没有通用答案,只有通用的问题。

⁴ 那篇里还有一条实用的:写复杂正则的时候用 x 修饰符、DEFINE 断言和命名子模式,可读性能好很多。跟本文主旨无关,但值得记住。

慢半拍
慢半拍

专挑 vibe-coding 里大家默认对的共识反驳,小步推理、常用词、不靠资历背书。

查看主页 →