高效遍历XML元素的方法
我有一个这样的xml文件:
<a>
<b>hello</b>
<b>world</b>
</a>
<x>
<y></y>
</x>
<a>
<b>first</b>
<b>second</b>
<b>third</b>
</a>
我需要遍历所有的 <a>
和 <b>
标签,但我不知道文档里有多少个。所以我用 xpath
来处理这个问题:
from lxml import etree
doc = etree.fromstring(xml)
atags = doc.xpath('//a')
for a in atags:
btags = a.xpath('b')
for b in btags:
print b
这个方法是有效的,但我的文件比较大,使用 cProfile
监测后发现 xpath
的开销很大。
我在想,是否有更高效的方法来遍历不确定数量的xml元素呢?
4 个回答
使用 iterparse:
import lxml.etree as ET
for event, elem in ET.iterparse(filelike_object):
if elem.tag == "a":
process_a(elem)
for child in elem:
process_child(child)
elem.clear() # destroy all child elements
elif elem.tag != "b":
elem.clear()
需要注意的是,这种方法并不能完全节省内存,但我已经用这种技巧处理过超过1GB的XML数据流。
试试 import xml.etree.cElementTree as ET
... 这个模块是Python自带的,它的 iterparse
比 lxml.etree
的 iterparse
更快,具体可以参考lxml的文档:
"""对于需要高效解析大文件的应用,并且几乎不进行序列化的情况,cET是最佳选择。此外,对于那些从大XML数据集中提取少量数据或汇总信息的iterparse应用,cET也很合适,因为这些数据可能无法完全放入内存。不过,如果考虑到往返性能,lxml通常会快上好几倍。所以,当输入文档的大小与输出文档差不多时,lxml显然是更好的选择。"""
那你觉得 iter 怎么样呢?
>>> for tags in root.iter('b'): # root is the ElementTree object
... print tags.tag, tags.text
...
b hello
b world
b first
b second
b third
XPath应该是很快的。你可以把XPath的调用次数减少到一次:
doc = etree.fromstring(xml)
btags = doc.xpath('//a/b')
for b in btags:
print b.text
如果这样还不够快,你可以试试Liza Daly的fast_iter。这个方法的好处是,不需要先用etree.fromstring
处理整个XML,而且在访问完子节点后,父节点会被丢弃。这两点都能帮助减少内存的使用。下面是一个修改过的fast_iter
版本,它更积极地去掉那些不再需要的其他元素。
def fast_iter(context, func, *args, **kwargs):
"""
fast_iter is useful if you need to free memory while iterating through a
very large XML file.
http://lxml.de/parsing.html#modifying-the-tree
Based on Liza Daly's fast_iter
http://www.ibm.com/developerworks/xml/library/x-hiperfparse/
See also http://effbot.org/zone/element-iterparse.htm
"""
for event, elem in context:
func(elem, *args, **kwargs)
# It's safe to call clear() here because no descendants will be
# accessed
elem.clear()
# Also eliminate now-empty references from the root node to elem
for ancestor in elem.xpath('ancestor-or-self::*'):
while ancestor.getprevious() is not None:
del ancestor.getparent()[0]
del context
def process_element(elt):
print(elt.text)
context=etree.iterparse(io.BytesIO(xml), events=('end',), tag='b')
fast_iter(context, process_element)
Liza Daly的文章关于解析大型XML文件也许对你有帮助。根据文章,使用fast_iter
的lxml可能比cElementTree
的iterparse
更快。(见表1)