编写一个HTML解析器

36

我目前正在尝试(或计划尝试)编写一个简单(尽可能简单)的程序,将HTML文档解析成树形结构。

在谷歌上搜索后,我找到了很多答案说“不要这么做,它已经被完成了”(或类似的话),并提供了HTML解析器的示例以及一篇非常强调为什么不应该使用正则表达式的文章。但是,我没有找到任何关于编写解析器的“正确”方法的指南。(顺便说一下,我尝试这个更多是作为学习练习而不是使用预制的解析器,所以我想自己写一个)

我相信通过阅读文档并将标签/文本等添加到树中,在遇到关闭标记时上升一级(同样简单,在这个阶段不需要复杂的线程或效率)。但是,对于HTML,并非所有标记都是关闭的。

因此,我的问题是:您建议采用什么方法来处理这个问题?我唯一想到的想法是像XML一样处理它,但具有不一定关闭的标记列表,每个标记都有关闭条件(例如<p>以</p>或下一个<p>标记结束)。

是否有其他(更好的)建议?总之,有没有更好的方法来完成这项工作?


1
不要这样做!但你还是会去做。所以看一下 http://jsoup.org - Lukas Eder
5
听起来是一项有趣的练习。继续努力。 - Einacio
3
请注意,你必须关注不仅是没有闭合的标签,还有隐式开放的标签(<body>是可选的),再加上整个混乱的糟糕格式的HTML代码,而HTML解析器却可以处理。HTML5规范包含了相当具体的解析算法。 - Matthew Wilson
2
为什么你不想使用现有的呢? - jantimon
1
一个非常高尚的练习 :) 我的建议是查看您最喜欢的语言中现有解析器的源代码。 - Richard H
显示剩余3条评论
6个回答

14
HTML的宽松性可以通过根据需要确定缺失的开放和关闭标记来容纳。这基本上就是类似Tidy的验证器所做的事情。
您将保留当前上下文的堆栈(可能是隐式的树)。例如,{<html><body>}表示您当前位于HTML文档的正文中。当您遇到新节点时,您将该节点的要求与当前堆栈进行比较。
假设您的堆栈当前仅为{html}。您遇到一个<p>标记。您在表中查找<p>,该表告诉您段落必须在<body>内。由于您不在正文中,因此您会隐式地将<body>推入堆栈(或向树中添加一个正文节点)。然后,您可以将<p>放入树中。
现在假设您看到另一个<p>。根据您的规则,您不能将段落嵌套在段落中,因此您知道在将新段落推入堆栈之前必须弹出当前的<p>(就像您已看到一个关闭标记一样)。
在文档结束时,您会将堆栈中剩余的每个元素弹出,就像您已看到每个元素的关闭标记一样。
关键在于找到一种表示每个元素上下文要求的良好方法。

这更接近实际情况,这意味着HTML解析涉及比XML解析更多的工作,并需要修复一些不规范的标签。 - duckduckgo

9

那么,我将在这里尝试回答 -

基本上,“普通”HTML解析(不是指有效的XHTML)与XML解析之间的区别在于有大量的规则,比如永远不会结束的<img>标签,或者严格地说,即使是最不严谨的所有HTML标记都会在浏览器中以某种方式呈现。 您需要使用验证器和解析器来构建您的树。但是您必须决定要支持的HTML标准,以便在遇到标记中的弱点时,您会知道它是一个错误而不仅仅是松散的HTML。

了解所有规则,构建验证器,然后就可以构建解析器。这是A计划。

B计划将允许您的解析器具有一定的错误耐受性,这将使验证步骤变得不必要。例如,解析所有标记并将它们放在列表中,省略任何属性,以便您可以轻松操作该列表,确定标记是否保留打开状态,或者根本没有打开,最终获得“良好”的布局树,这将是松散布局的近似解决方案,同时对正确的布局非常准确。

希望这有所帮助!


1
这真的非常有用。我没有想到像那样把它们放在列表中,并将其作为树的基础(我一直认为必须一次性完成,没有想到可以对文档进行多次遍历)。谢谢 ^^ - James

8
自从HTML5标准出现,编写HTML解析器不再是试错或神秘知识。相反,您只需实现标准化的parsing algorithm即可。

5

直接回答

HTML不同于XML,XHTML是XML。大多数网站使用HTML,一些则使用XHTML。在XHTML中,所有标签必须被关闭(或没有内容,但仍需关闭)。
如果您想编写一个HTML解析器作为学习实验,那就去试试吧。如果您想编写下一个“最伟大的HTML解析器”,那就放弃吧。Apache(或其他人)已经胜出;重要的信息是:您并不比专门从事解析HTML的大型团队更懂。
回答“我该怎么处理这个问题?”的问题,请阅读W3C关于HTML的规范。它会回答您的问题。如果您的回答是“但我不想”,那么您实际上是在说“我是一个懒惰的家伙,想假装学习”。如果是这样的话,我建议您删除帖子并继续前进;Microsoft IE团队可能有一些文件会对您感兴趣。

简化回答

HTML很难解析。在最宽松的情况下,您不需要head或body元素,并且许多标记不需要关闭。解析HTML的基本规则是如果遇到新的块元素,则自动关闭前一个块元素。您不能使用标准的XML解析器,因为HTML不是XML。
与XML类似,您需要将文档拆分为元素,包括自由文本元素。
XHTML要简单得多,因为它必须是格式良好的XML。您可以使用XML解析器来解析XHTML。

1
我知道这不会是最好的解析器,这就是为什么我将其指定为学习练习的原因。然而,我不知道规范中有关于解析器实现的建议,我现在会去看一下。 ;) - James

4
几乎晚了十年,但无论如何。如果对你不相关,它对未来的访问者也是有用的。
另一个选项是实现规范。 WHATWG 有一个关于 HTML 的规范。在这个规范中考虑到了所有的怪癖,你可以确信没有忘记 HTML 的一些奇怪机制(有很多)。
规范还包含 HTML 规范的正式规定。其中第 § 13.2 解析 HTML 文档 节概述了用户代理(您的解析器)应该将 html 文档解析为 DOM 树。所有边缘情况都已经被考虑到了。最困难的部分是使用正确的数据结构和编程流程,在您选择的语言中实现它。
祝你好运,保持精神,读者!

-2

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