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

时间:2008-11-27 16:47:54

标签: python xml performance parsing

我目前正在根据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

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

8 个答案:

答案 0 :(得分:58)

我看起来好像你不需要程序中的任何DOM功能。我会先使用(c)ElementTree库。如果使用cElementTree模块的iterparse函数,则可以通过xml工作并在事件发生时处理它们。

但请注意,Fredriks建议使用cElementTree iterparse function

  

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

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,请考虑以下方式获取第一个元素。

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

is_first = True

for event, elem in context:
    # get the root element
    if is_first:
        root = elm
        is_first = False
    if event == "end" and elem.tag == "record":
        ... process record elements ...
        root.clear()

答案 1 :(得分:15)

您是否尝试过cElementTree模块?

cElementTree包含在Python 2.5及更高版本中,作为xml.etree.cElementTree。请参阅benchmarks

删除了死亡的ImageShack链接

答案 2 :(得分:8)

我建议你使用lxml,它是libxml2库的python绑定,非常快。

根据我的经验,libxml2和expat具有非常相似的性能。但我更喜欢libxml2(和python的lxml),因为它似乎更积极地开发和测试。 libxml2还有更多功能。

lxml主要与xml.etree.ElementTree API兼容。其网站上有很好的文档。

答案 3 :(得分:5)

注册回调会极大地减慢解析速度。 [编辑]这是因为(快速)C代码必须调用python解释器,它不如C快。基本上,你使用C代码读取文件(快速),然后在Python中构建DOM (缓动)。[/编辑]

尝试使用xml.etree.ElementTree,它在C中实现100%,并且可以解析XML而不需要回调python代码。

解析完文档后,您可以对其进行过滤以获得所需内容。

如果仍然太慢并且您不需要DOM,则另一个选择是将文件读入字符串并使用简单的字符串操作来处理它。

答案 4 :(得分:4)

如果你的应用程序性能敏感并且可能遇到大文件(就像你说的那样,> 1GB)那么我强烈建议不要使用你在问题中显示的代码将整个文档加载到RAM中的简单原因。我鼓励您重新考虑您的设计(如果可能的话),以避免一次将整个文档树保存在RAM中。不知道您的应用程序的要求是什么,我不能正确地建议任何特定的方法,除了试图使用“基于事件”的设计的通用建议。

答案 5 :(得分:1)

如果你不需要将整个树存储在内存中,那么

expat ParseFile 可以很好地工作,这对于大文件来说,这迟早会破坏你的RAM:

import xml.parsers.expat
parser = xml.parsers.expat.ParserCreate()
parser.ParseFile(open('path.xml', 'r'))

它将文件读入块,并将它们提供给解析器而不会爆炸RAM。

Doc:https://docs.python.org/2/library/pyexpat.html#xml.parsers.expat.xmlparser.ParseFile

答案 6 :(得分:0)

显然PyRXP非常快。

他们声称这是最快的解析器 - 但是cElementTree不在他们的统计列表中。

答案 7 :(得分:0)

我花了很多时间进行尝试,看来最快,最不占用内存的方法是使用lxml和iterparse,但要确保释放不需要的内存。在我的示例中,解析arXiv转储:

BD

因此,from lxml import etree context = etree.iterparse('path/to/file', events=('end',), tag='Record') for event, element in context: record_id = element.findtext('.//{http://arxiv.org/OAI/arXiv/}id') created = element.findtext('.//{http://arxiv.org/OAI/arXiv/}created') print(record_id, created) # Free memory. element.clear() while element.getprevious() is not None: del element.getparent()[0] 是不够的,而且还删除了指向先前元素的任何链接。