在Python中快速解析大型XML文档的最佳方法是什么?

78 投票
8 回答
98202 浏览
提问于 2025-04-11 19:14

我现在正在运行一段代码,这段代码是根据《Python Cookbook》第12.5章写的:

from xml.parsers import expat

class Element(object):
    def __init__(self, name, attributes):
        self.name = name
        self.attributes = attributes
        self.cdata = ''
        self.children = []
    def addChild(self, element):
        self.children.append(element)
    def getAttribute(self,key):
        return self.attributes.get(key)
    def getData(self):
        return self.cdata
    def getElements(self, name=''):
        if name:
            return [c for c in self.children if c.name == name]
        else:
            return list(self.children)

class Xml2Obj(object):
    def __init__(self):
        self.root = None
        self.nodeStack = []
    def StartElement(self, name, attributes):
        element = Element(name.encode(), attributes)
        if self.nodeStack:
            parent = self.nodeStack[-1]
            parent.addChild(element)
        else:
            self.root = element
        self.nodeStack.append(element)
    def EndElement(self, name):
        self.nodeStack.pop()
    def CharacterData(self,data):
        if data.strip():
            data = data.encode()
            element = self.nodeStack[-1]
            element.cdata += data
    def Parse(self, filename):
        Parser = expat.ParserCreate()
        Parser.StartElementHandler = self.StartElement
        Parser.EndElementHandler = self.EndElement
        Parser.CharacterDataHandler = self.CharacterData
        ParserStatus = Parser.Parse(open(filename).read(),1)
        return self.root

我正在处理大约1GB大小的XML文档。有没有人知道更快的方法来解析这些文档?

8 个回答

11

我推荐你使用 lxml,这是一个针对libxml2库的Python绑定,速度非常快。

根据我的经验,libxml2和expat的性能非常相似。不过我更喜欢libxml2(以及Python的lxml),因为它似乎更新和测试得更积极。而且libxml2的功能更多。

lxml在大多数情况下和 xml.etree.ElementTree 是兼容的。而且它的网站上有很好的文档。

17

你有没有试过 cElementTree 这个模块?

cElementTree 是从 Python 2.5 版本开始就自带的,路径是 xml.etree.cElementTree。你可以参考一下这个 基准测试

需要注意的是,从 Python 3.3 开始,cElementTree 就成了默认的实现,所以如果你用的是 Python 3.3 及以上版本,就不需要再做这个更改了。

已移除无效的 ImageShack 链接

80

看起来你的程序不需要处理DOM(文档对象模型)的功能。我建议你使用(c)ElementTree库。如果你使用cElementTree模块的iterparse函数,就可以逐步处理XML文件,并在事件发生时进行相应的操作。

不过,Fredrik提到的关于使用cElementTree的iterparse函数的建议是:

在解析大文件时,你可以在处理完元素后立即将其删除:

for event, elem in iterparse(source):
    if elem.tag == "record":
        ... process record elements ...
        elem.clear()

不过,上面的做法有一个缺点;它不会清除根元素,所以你最终会得到一个包含很多空子元素的单一元素。如果你的文件非常大,而不仅仅是大,这可能会成为一个问题。为了解决这个问题,你需要获取根元素。最简单的方法是启用开始事件,并将第一个元素的引用保存在一个变量中:

# get an iterable
context = iterparse(source, events=("start", "end"))

# turn it into an iterator
context = iter(context)

# get the root element
event, root = context.next()

for event, elem in context:
    if event == "end" and elem.tag == "record":
        ... process record elements ...
        root.clear()

lxml.iterparse()不允许这样做。

之前的方法在Python 3.7上不适用,可以考虑以下方式来获取第一个元素。

import xml.etree.ElementTree as ET

# Get an iterable.
context = ET.iterparse(source, events=("start", "end"))
    
for index, (event, elem) in enumerate(context):
    # Get the root element.
    if index == 0:
        root = elem
    if event == "end" and elem.tag == "record":
        # ... process record elements ...
        root.clear()

撰写回答