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

m2xkgtsf  于 2022-12-17  发布在  Python
关注(0)|答案(8)|浏览(154)

我目前正在运行基于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文档。有人知道解析这些文档的更快方法吗?

7y4bm7vi

7y4bm7vi1#

在我看来,您的程序似乎不需要任何DOM功能。我支持使用(c)ElementTree库。如果您使用cElementTree模块的iterparse函数,则可以遍历XML并在事件发生时处理它们。
但是请注意,Fredrik关于使用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上不起作用,考虑下面的方法来获取第一个元素。

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()
gmxoilav

gmxoilav2#

你试过cElementTree模块吗?
cElementTree包含在Python 2.5和更高版本中,作为xml.etree.cElementTree。
注意,因为Python 3.3 cElementTree被用作默认实现,所以Python 3.3+版本不需要这个改变。

  • 删除了无效的ImageShack链接 *
1rhkuytd

1rhkuytd3#

我建议你使用lxml,它是一个绑定libxml2库的python,速度非常快。
根据我的经验,libxml2和expat的性能非常相似,但我更喜欢libxml2(和lxml for python),因为它的开发和测试似乎更活跃,而且libxml2有更多的特性。
lxml大部分是与xml.etree.ElementTree兼容的API,并且在它的网站上有很好的文档。

t0ybt7op

t0ybt7op4#

注册回调极大地降低了解析的速度。[EDIT]这是因为(快速)C代码必须调用Python解释器,而Python解释器的速度没有C快。基本上,你使用C代码读取文件(快速),然后在Python中构建DOM(慢速)。[/EDIT]
尝试使用xml.etree.ElementTree,它是100%用C实现的,可以解析XML,而不需要对Python代码进行任何回调。
文档经过解析后,您可以对其进行筛选以获得所需的内容。
如果这样做仍然太慢并且不需要DOM,另一种选择是将文件读入字符串,然后使用简单的字符串操作来处理它。

wgmfuz8q

wgmfuz8q5#

如果您的应用程序对性能敏感,并且可能遇到大文件(就像你说的,〉1GB),那么我强烈建议不要使用您在问题中显示的代码,原因很简单,* 它会将整个文档加载到RAM中 *。(如果可能的话),以避免在RAM中一次保存整个文档树。由于不知道应用程序的需求是什么,我无法适当地建议任何特定的方法,而不是尝试使用“基于事件”设计的一般建议。

3ks5zfa0

3ks5zfa06#

如果不需要将整个树存储在内存中,expat ParseFile就可以很好地工作,因为存储大文件迟早会消耗内存:

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

它将文件读入块中,并将它们提供给解析器,而不会导致RAM爆炸。
文件:https://docs.python.org/2/library/pyexpat.html#xml.parsers.expat.xmlparser.ParseFile

w1jd8yoj

w1jd8yoj7#

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

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]

所以element.clear是不够的,还要去除与前面元素的任何链接。

mrzz3bfm

mrzz3bfm8#

在Python3中,您应该更改语法
而不是这个

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

试试这个(如Iterparse object has no attribute next中的建议)

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

而且这一行是不必要的

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

相关问题