高效遍历XML元素的方法

22 投票
4 回答
54640 浏览
提问于 2025-04-16 09:59

我有一个这样的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 个回答

5

使用 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自带的,它的 iterparselxml.etreeiterparse 更快,具体可以参考lxml的文档

"""对于需要高效解析大文件的应用,并且几乎不进行序列化的情况,cET是最佳选择。此外,对于那些从大XML数据集中提取少量数据或汇总信息的iterparse应用,cET也很合适,因为这些数据可能无法完全放入内存。不过,如果考虑到往返性能,lxml通常会快上好几倍。所以,当输入文档的大小与输出文档差不多时,lxml显然是更好的选择。"""

13

那你觉得 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
27

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可能比cElementTreeiterparse更快。(见表1)

撰写回答