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

Way*_*lan 6 html python parsing lxml html5lib

我试图在Python中找到一种解析(可能是格式错误的)HTML的方法,如果满足一组条件,则输出该文档的位置(行,列).位置信息正在绊倒我.要清楚,我没有必要构建一个对象树.我只是想找到某些数据及其在原始文档中的位置(想想一个拼写检查器,例如:'word'foo"在第x行,第y列,拼写错误)'

作为一个例子,我想要这样的东西(使用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() 
Run Code Online (Sandbox Code Playgroud)

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

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

我知道两个HTML解析器可以很好地解析格式错误的HTML,即lxmlhtml5lib.事实上,我宁愿使用其中任何一个而不是Python中的任何其他选项.

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

lxml库提供了一个主要镜像ElementTree的Target API,但同样,我不知道有任何方法可以访问每个事件的位置信息.浏览源代码也没有提示.

lxml还为SAX事件提供API.有趣的是,Python的标准库提到SAX支持定位器对象,但很少提供有关如何使用它们的文档.这个SO问题提供了一些信息(当使用SAX Parser时),但我没有看到它与lxml提供的对SAX事件的有限支持有何关系.

最后,在有人推荐Beautiful Soup之前,我会指出,正如主页上所说,"Beautiful Soup位于流行的Python解析器之上,如lxml和html5lib".它给我的全部内容是从没有连接到原始源文档的数据中提取数据.与html5lib一样,当我访问数据时,所有位置信息都会丢失.我希望/需要直接对解析器进行原始访问.

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

那么,有什么我想念的吗?我很难相信这些解析器(除了大多数无用的HTMLParser之外)都没有办法访问位置信息.但是,如果他们这样做,它必须是无证的,这对我来说似乎很奇怪.

Way*_*lan 4

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

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

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

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

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


根据 Beautiful Soup文档,HTMLParser 已更新为支持 Python 2.7.3 和 3.2.2(早于 3.3 的版本)中的无效输入。