我每天都会看到问题,询问如何从某些HTML字符串中解析或提取某些内容,第一个答案/注释始终是“不要使用RegEx来解析HTML,以免感到愤怒!” (最后一部分有时会被省略)。
这对我来说相当混乱,我一直认为解析任何复杂字符串的最佳方法是使用正则表达式。那么HTML解析器如何工作呢?它不使用正则表达式来解析。
使用正则表达式的一个特殊参数是,并不总是有一种解析替代方法(例如JavaScript,其中DOMDocument不是普遍可用的选项)。例如,jQuery似乎可以使用正则表达式将HTML字符串转换为DOM节点。
不确定CW是否,这是一个真正的问题,我想要回答,而不是真正想成为一个讨论主题。
答案 0 :(得分:131)
那么HTML解析器是如何工作的呢?它不使用正则表达式来解析吗?
嗯,不。
如果你回到脑中学习计算理论课程,如果你选择了一门课程,或者编写了一个编程课程,或者类似的东西,你可能会记得有不同种类的语言和计算模型。我没有资格详细介绍所有细节,但我可以和你一起回顾几个要点。
最简单的语言类型&计算(用于这些目的)是一种常规语言。这些可以使用正则表达式生成,并使用有限自动机进行识别。基本上,这意味着这些语言中的“解析”字符串使用状态,而不是辅助存储器。 HTML当然不是常用语言。如果你考虑一下,标签列表可以任意嵌套。例如,表可以包含表,每个表可以包含许多嵌套标记。使用正则表达式,您可以选择一对标签,但肯定不能任意嵌套。
非常规的经典简单语言是正确匹配的括号。尽量尝试,你将永远无法构建一个永远有效的正则表达式(或有限自动机)。你需要记忆来跟踪嵌套深度。
具有内存堆栈的状态机是计算模型的下一个优势。这称为下推自动机,它识别由无上下文语法生成的语言。在这里,我们可以识别正确匹配的括号 - 实际上,堆栈是它的完美内存模型。
嗯,这对HTML来说还算不错吗?可悲的是没有。也许对于超级经验丰富的经过验证的XML,实际上,所有标签总是完美排列。在真实HTML中,您可以轻松找到<b><i>wow!</b></i>
之类的代码段。这显然不会嵌套,所以为了正确解析它,堆栈就不够强大了。
下一级计算是由一般语法生成的语言,并由图灵机识别。这通常被认为是有效的最强计算模型 - 具有辅助存储器的状态机,其存储器可以在任何地方进行修改。这就是编程语言可以做的事情。这就是HTML生活的复杂程度。
用一句话总结这里的一切:要解析一般的HTML,你需要一种真正的编程语言,而不是正则表达式。
HTML的解析方式与解析其他语言的方式相同:lexing和parsing。 lexing步骤将单个字符流分解为有意义的标记。解析步骤使用状态和内存将令牌组合成可以对其执行操作的逻辑连贯文档。
答案 1 :(得分:64)
通常使用tokeniser。用于处理“真实世界HTML”的草案HTML5 specification has an extensive algorithm。
答案 2 :(得分:22)
正则表达式只是解析器的一种形式。诚实的HTML解析器将比使用recursive descent,预测和其他几种技术正确解释文本时在正则表达式中表达的复杂得多。如果您真的想进入它,可以查看lex & yacc和类似工具。
禁止使用正则表达式进行HTML解析应该更正确地写为:“不要使用天真的正则表达式来解析HTML ...”(以免你感到愤怒)“......并谨慎对待结果。”对于某些特定目标,正则表达式可能完全足够,但您需要非常小心地了解正则表达式的局限性,并且要谨慎对待正在解析的文本的来源(例如,如果它是用户输入,确实非常小心。)
答案 3 :(得分:6)
解析HTML是将线性文本转换为树结构。正则表达式通常不能处理树结构。每个点所需的正则表达式可以随时更改下一个标记。您可以在解析器中使用正则表达式,但是对于每种可能的解析状态,您将需要一整套正则表达式。
答案 4 :(得分: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解析器加载文档就可以了。或者使用Regex(尽管如果你是内容的主人,xml会更容易)。