如果不使用regexp,HTML解析如何工作?


96

我每天看到很多问题,问如何解析或从HTML字符串中提取内容,第一个答案/评论始终是“不要使用RegEx解析HTML,以免您感到愤怒!” (最后一部分有时被省略)。

这对我来说很令人困惑,我一直认为,一般而言,解析任何复杂字符串的最佳方法是使用正则表达式。那么HTML解析器如何工作?它不使用正则表达式进行解析。

使用正则表达式的一个特殊论点是,并不总是存在解析替代方法(例如JavaScript,其中DOMDocument不是普遍可用的选项)。以jQuery为例,使用正则表达式将HTML字符串转换为DOM节点似乎可以很好地进行管理。

不确定是否要继续进行此操作,这是一个真正的问题,我想得到答复,而并不是要成为讨论话题。


重新标记以添加解析和html解析-@Andy E,我希望您一切都好-我认为这会有所帮助。
JXG 2010年

@JXG:我很好,谢谢:-)
Andy E

Answers:


65

通常通过使用令牌处理程序。草稿HTML5规范具有用于处理“真实世界HTML” 的广泛算法


1
很好的发现...引述“要处理这些情况,解析器具有脚本嵌套级别(必须最初设置为零)和解析器暂停标志(必须首先设置为false)。” -换句话说,您必须自己进行迭代并具有许多自定义逻辑:P
Timothy Khouri 2010年

1
赞成。最好强调算法复杂性,而不是某些技术。
阿尼斯·拉萨

1
自己使用大量自定义逻辑对其进行迭代并不是一个好主意。如果可以,请使用支持标准算法的库。例如search.cpan.org/~tobyink/HTML-HTML5-Parser-0.03/lib/HTML/HTML5/... / code.google.com/p/html5lib
昆廷-

8
HTML解析器的主要问题是遇到错误时,您不可以吐出“解析错误”并将其留在那。您进入怪癖模式,并尝试从遇到的混乱中获得最大的收益,包括不匹配的标签,[{]}风格的隔行扫描以及各种怪异的现象,试图使结果看起来尽可能好并不可避免失败最少的痛苦...这不是用正则表达式可以做的事情。
SF。

7
@Timothy K:'注意:由于此算法导致元素更改父级的方式,因此被称为“收养代理算法”(与其他处理错放内容的可能算法相反,其中包括“乱伦算法”, “秘密事务算法”和“海森堡算法”)。
JXG 2010年

133

那么HTML解析器如何工作?它不使用正则表达式进行解析吗?

好吧,不。

如果您回到大脑中去学习一门计算课程,或者上过编译器课程,或者类似的课程,您可能会记得,存在着不同种类的语言和计算模型。我没有资格讨论所有细节,但我可以和您一起回顾一些要点。

语言和计算(出于这些目的)最简单的类型是常规语言。这些可以用正则表达式生成,并可以用有限自动机识别。基本上,这意味着这些语言中的“解析”字符串使用状态,而不使用辅助存储器。HTML当然不是常规语言。如果您考虑一下,标签列表可以任意嵌套。例如,表可以包含表,并且每个表可以包含很多嵌套标签。使用正则表达式,您可能可以挑选出一对标签,但是当然不能随意嵌套任何标签。

非常规的经典简单语言是正确匹配的括号。尝试一下,您将永远无法构建始终有效的正则表达式(或有限自动机)。您需要内存来跟踪嵌套深度。

具有堆栈用于存储的状态机是计算模型的下一个优势。这称为下推式自动机,它可以识别由上下文无关的语法生成的语言。在这里,我们可以识别正确匹配的括号-实际上,堆栈是它的理想存储模型。

好吧,这对HTML足够好了吗?可悲的是没有。实际上,对于超级Duper而言,也许经过仔细验证的XML始终可以完美地排列所有标签。在实际的HTML中,您可以轻松找到的片段<b><i>wow!</b></i>。这显然不会嵌套,因此为了正确解析它,堆栈只是不够强大。

下一级别的计算是由通用语法生成的语言,并由图灵机识别。通常公认这是最有效的计算模型-状态机,带有辅助存储器,可以在任何地方修改其存储器。这就是编程语言可以做到的。这就是HTML所处的复杂程度。

用一句话总结一下这里的所有内容:要解析常规HTML,您需要一种真正的编程语言,而不是一个正则表达式。

HTML的解析方式与其他语言的解析方式相同:词法分析和语法分析。词法化步骤将单个字符流分解为有意义的标记。解析步骤使用状态和内存将令牌组合到可以执行的逻辑上一致的文档中。


22

正则表达式只是解析器的一种形式。诚实到善良的HTML解析器将比使用正则表达式表达的复杂得多,使用递归下降,预测和其他几种技术来正确解释文本。如果您真的想了解它,可以查看lex&yacc和类似的工具。

禁止将正则表达式用于HTML解析的禁令应该更正确地写为:“不要使用幼稚的正则表达式来解析HTML ...” (不要感到愤怒) “ ...并谨慎对待结果。” 对于某些特定的目标,正则表达式可能就足够了,但是您需要非常小心以了解正则表达式的局限性,并要谨慎对待要解析的文本的来源(例如,如果用户输入,的确要非常小心)。


+1,一个很好的答案。我必须承认,即使我不受HTML的控制,但在任何公开发布的应用程序中都没有使用过正则表达式。我也确实感到了“愤怒”,因为它很幼稚。但这是很久以前的了:-)
Andy E

6

解析HTML是将线性文本转换为树形结构。正则表达式通常不能处理树结构。在每个点上获取下一个标记所需的正则表达式始终在变化。您可以在解析器中使用正则表达式,但是对于每种可能的解析状态,您将需要一个完整的正则表达式数组。


2

如果您想获得100%的解决方案:您需要编写自己的自定义代码,以逐个字符地遍历HTML,并且需要大量逻辑来确定是否应停止当前节点并启动该节点。下一个。

原因是这是有效的HTML:

<ul>
<li>One
<li>Two
<li>Three
</ul>

但这也是:

<ul>
<li>One</li>
<li>Two</li>
<li>Three</li>
</ul>

如果您对“ 90%解决方案”表示满意:那么可以使用XML解析器加载文档。或使用正则表达式(尽管如果您是内容的掌握者,那么xml会更容易)。


4
XML解析器更像是1%的解决方案。格式良好的XML的HTML文档数量很少。
昆汀2010年

4
是的,它们确实...不要从字面上理解“按字符排列”,因为您可以尝试流式传输内容。但我的观点是,您必须编写自己的解析器。新时代的程序员不习惯编写这种代码……我们习惯于“ HtmlDocumentUtility.Load”之类的东西:)
Timothy Khouri 2010年

4
@Andy E:正则表达式不是魔术,它们也可以逐个字符地工作,就像任何其他类型的解析(或解析)其他字符串函数一样。
巴特·范·海克洛姆

1
顺便说一句:您的第一个示例不仅是“半有效HTML”。实际上是有效的HTML 4.01 Strict。您可以使用例如W3C验证器对此进行验证。结束标记对于<li>是官方可选的(请参见HTML 4规范)。
sleske 2010年

2
@Bart:好点,有时候我的大脑忘记了所有逻辑,认为事情是靠魔术起作用的。
安迪E
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.