在 Python 中解析 HTML 时获取位置信息

Posted

技术标签:

【中文标题】在 Python 中解析 HTML 时获取位置信息【英文标题】:Obtaining position info when parsing HTML in Python 【发布时间】:2015-04-28 00:09:08 【问题描述】:

我正在尝试找到一种在 Python 中解析(可能是格式错误的)html 的方法,如果满足一组条件,则输出带有位置(行、列)的那段文档。位置信息是让我在这里绊倒的原因。需要明确的是,我不需要构建对象树。我只是想在原始文档中查找某些数据及其位置(想想拼写检查器,例如:'word "foo" at line x, column y, is拼写错误)'

作为一个例子,我想要这样的东西(使用 ElementTree 的Target API):

import xml.etree.ElementTree as ET

class EchoTarget:
    def start(self, tag, attrib):
        if somecondition():
            print "start", tag, attrib, self.getpos()
    def end(self, tag):
        if somecondition():
            print "end", tag, self.getpos()
    def data(self, data):
        if somecondition():
            print "data", repr(data), self.getpos()

target = EchoTarget()
parser = ET.XMLParser(target=target)
parser.feed("<p>some text</p>")
parser.close() 

但是,据我所知,getpos() 方法(或类似的方法)并不存在。当然,那是使用 XML 解析器。我想解析可能格式错误的 HTML。

有趣的是,Python 标准库中的 HTMLParser 类确实提供了对获取位置信息的支持(使用 getpos() 方法),但它在处理格式错误的 HTML 时很糟糕,并且已作为可能的解决方案被淘汰。我需要在不破坏解析器的情况下解析真实单词中存在的 HTML。

我知道有两个 HTML 解析器可以很好地解析格式错误的 HTML,即 lxml 和 html5lib。事实上,我更愿意使用其中任何一个,而不是 Python 中可用的任何其他选项。

但是,据我所知,html5lib 不提供事件 API,并且需要将文档解析为树对象。然后我将不得不遍历树。当然,到那时,与源文档没有关联,所有位置信息都丢失了。所以,html5lib 出来了,这很可惜,因为它似乎是处理格式错误的 HTML 的最佳解析器。

lxml 库提供了一个 Target API,它主要反映了 ElementTree 的,但同样,我不知道有任何方法可以访问每个事件的位置信息。看一眼源代码也没有提供任何提示。

lxml 还为 SAX 事件提供了一个 API。有趣的是,Python 的标准库提到 SAX 支持 Locator Objects,但几乎没有提供有关如何使用它们的文档。这个SO Question 提供了一些信息(使用 SAX 解析器时),但我看不出这与 lxml 提供的对 SAX 事件的有限支持有何关系。

最后,在有人建议Beautiful Soup 之前,我要指出,正如主页上所述,“Beautiful Soup 位于流行的 Python 解析器(如 lxml 和 html5lib)之上”。它给我的只是一个从中提取数据的对象,与原始源文档没有任何联系。与 html5lib 一样,当我访问数据时,所有位置信息都会丢失。我想要/需要直接访问解析器。

为了扩展我在开头提到的拼写检查示例,我只想检查文档文本中单词的拼写(而不是标签名称或属性),并且可能希望跳过检查特定标签的内容(像脚本或代码标签)。因此,我需要一个真正的 HTML 解析器。但是,在报告拼写错误时,我只对原始源文档中拼写错误的位置感兴趣,不需要构建树对象。需要明确的是,这只是一种潜在用途的示例。我可能会将它用于完全不同的东西,但需求基本相同。事实上,我曾经使用 HTMLParser 构建了一些非常相似的东西,但从未使用它,因为错误处理不适用于该用例。那是几年前的事了,我似乎在某个地方丢失了那个文件。这次我想改用 lxml 或 html5lib。

那么,我有什么遗漏吗?我很难相信这些解析器(除了大部分无用的 HTMLParser 之外)没有任何方法可以访问位置信息。但如果他们这样做,则必须是无证的,这对我来说似乎很奇怪。

【问题讨论】:

【参考方案1】:

有趣的是,Python 标准库中的 HTMLParser 类确实提供了对获取位置信息的支持(使用 getpos() 方法),但它在处理格式错误的 HTML 时很糟糕,并且已作为可能的解决方案被淘汰。

我之前使用的一种技术是使用 BeautilfulSoup.prettify() 来修复格式错误的 html,然后使用 HTMLParser 对其进行解析。

【讨论】:

【参考方案2】:

在对html5lib的源代码进行了一些额外的研究和更仔细的审查后,我发现html5lib.tokenizer.HTMLTokenizer确实保留了部分位置信息。 “部分”是指它知道给定标记的最后一个字符的行和列。不幸的是,它没有保留标记开始的位置(我想它可以被推断出来,但这感觉就像反向重新实现大部分标记器 - 不,使用前一个的结束位置不会如果令牌之间有空格,则工作)。

无论如何,我能够包装 HTMLTokenizer 并创建一个主要复制 API 的 HTMLParser 克隆。你可以在这里找到我的作品:https://gist.github.com/waylan/7d5b7552078f1abc6fac。

但是,由于分词器只是 html5lib 实现的解析过程的一部分,我们失去了 html5lib 的优点。例如,在该过程的那个阶段没有进行规范化,因此您获得的是原始(可能无效)令牌而不是规范化文档。正如那里的 cmets 所述,它并不完美,我怀疑它是否有用。

事实上,我还发现 Python 标准库中包含的 HTMLParser 在 Python 3.3 中是 updated 并且不再因无效输入而严重崩溃。据我所知,它更好(对于我的用例),因为它确实提供了实际有用的位置信息(因为它总是有)。在所有其他方面,我的 html5lib 包装器没有好坏之分(当然,除了它可能已经接受了更多的测试,因此更稳定)。不幸的是,该更新尚未向后移植到 Python 2 或更早的 Python 3 版本。不过,我不认为我自己会那么难。

无论如何,我决定在标准库中使用 HTMLParser 并拒绝我自己的 html5lib 包装器。您可以看到早期的努力 here 似乎只需最少的测试即可正常工作。


根据 Beautiful Soup docs 的消息,HTMLParser 已更新为支持 Python 2.7.3 和 3.2.2 中的无效输入,该版本早于 3.3。

【讨论】:

【参考方案3】:

只是一个答案——html5lib 不提供流式 API,因为在按规范解析 HTML 时通常不可能提供流式 API 而没有缓冲或致命错误(例如,考虑输入 &lt;table&gt;xxx)。但是,最好为 html5lib 提供一个流式 API,它只对那些阻止流式传输的解析错误使用致命错误。实施起来并不容易,也不是很困难。

在 html5lib 中将位置信息获取到树中应该不会做太多工作(事实上,解析错误有位置信息表明它是可以获取的!),并且有几个错误,@987654321 @ 和 one specific to lxml。

请注意,不能单独使用 html5lib 标记器来实现这一点 - 标记器的状态会在不同点通过树构造步骤进行更改。因此,您必须实现一个最小的树构造函数(至少必须维护一堆开放元素,尽管我认为仅此而已)才能保持标记器正确。一旦你想开始基于当前元素进行过滤,你基本上需要整个树构建步骤,所以你又回到了上面的流 API 问题。

【讨论】:

以上是关于在 Python 中解析 HTML 时获取位置信息的主要内容,如果未能解决你的问题,请参考以下文章

python的HTML解析器,可以跟踪标签在HTML文档中的位置

使用python根据ip获取目标地理位置信息

python获取原图GPS位置信息,轻松得到你的活动轨迹

python3 获取title的编写

[iOS]获取地理位置信息

Python爬虫解析htm时lxml的HtmlElement对象获取和设置inner html方法