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

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

87772 次浏览

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

尝试使用 xml.etree。ElementTree 是100% 用 C 语言实现的,它可以在不回调 Python 代码的情况下解析 XML。

解析文档后,可以对其进行筛选以获得所需的内容。

如果还是太慢,你不需要 DOM,另一个选择是将文件读入一个字符串,然后使用简单的字符串运算来处理它。

我建议您使用 Lxml,它是用于 libxml2库的 Python 绑定,速度非常快。

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

Lxml 基本上与 元素树兼容,并且在其网站上有很好的文档。

你试过 cElementTree模块吗?

Python 2.5及更高版本包含 cElementTree,作为 xml.etree.cElementTree。

请注意,由于 Python 3.3 cElementTree被用作默认实现,所以 Python 3.3 + 版本不需要进行此更改。

移除了死亡的 ImageShack 链接

如果您的应用程序是性能敏感的,并且可能遇到大文件(如您所说,> 1 GB) ,那么我建议 很强烈不要使用您在问题中显示的代码,原因很简单: 它将整个文档加载到 RAM 中。我建议您重新考虑您的设计(如果可能的话) ,以避免一次性将整个文档树保存在 RAM 中。由于不知道您的应用程序的需求是什么,除了试图使用“基于事件的”设计的通用建议之外,我无法恰当地提出任何具体方法。

在我看来,您似乎不需要从您的程序中获得任何 DOM 功能。我赞成使用(c) ElementTree 库。如果使用 cElementTree 模块的 iterparse 函数,则可以按照自己的方式处理 xml 并在事件发生时处理它们。

然而,请注意 Fredriks 对使用 cElementTree 迭代解析函数迭代解析函数的建议:

要解析大型文件,可以在处理完元素后立即删除它们:

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

上面的模式有一个缺点; 它没有清除根元素,所以最终只有一个元素,其中有很多空的子元素。如果您的文件很大,而不仅仅是很大,这可能是一个问题。为了解决这个问题,您需要了解根元素。最简单的方法是启用 start 事件,并在变量中保存对第一个元素的引用:

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

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

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

我花了相当长的时间尝试这种方法,它似乎是最快和最少内存密集型的方法是使用 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是不够的,还要删除到以前元素的所有链接。

在 Python 3中,应该更改语法
而不是这样

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

试试这个(像 Iterparse 对象接下来没有属性中推荐的那样)

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

这句台词是多余的

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