使用Python高效去除XML元素

2 投票
1 回答
1251 浏览
提问于 2025-04-18 12:22

我正在尝试高效地编辑比较大的XML文件(通常在100到500MB之间,最大可达1GB),目的是删除所有不包含特定属性值的元素。我希望找到一种在速度上最有效的方法,同时又不想把大量数据加载到内存中,因为对于较大的文件来说,这会是个问题。

以示例XML为例,它的结构大致如下,父元素可以嵌套多层。

<root>
<parent>
    <child id="c1">
        <content />
    </child>
    <child id="c2">
        <content />
    </child>
</parent>
<parent>
    <parent>
        <child id="c3">
            <content />
        </child>
    </parent>
</parent>
</root>

根据上面的示例XML,我想删除所有ID不等于"c1"的子元素,最终结果应该是:

<root>
<parent>
    <child id="c1">
        <content />
    </child>
</parent>
<parent>
    <parent />
</parent>
</root>

到目前为止,我找到的最有效的方法是使用cElementTree的iterparse:

import xml.etree.cElementTree as ET

xml_source = 'xml file location'
xml_output = 'xml output file location'

context = ET.iterparse(xml_source, events=("start", "end"))
context = iter(context)

event, root = context.next()

for event, elem in context:
    if event == 'end' and elem.tag == 'child' and elem.attrib['id'] != 'c1':
        elem.clear()

ET.ElementTree(root).write(xml_output)

使用这种方法,处理一个100MB的测试文件大约需要10秒钟,请问有没有更有效的方法可以实现这个目标?

1 个回答

1

抱歉,我手头没有一个大的等效xml文件,所以你得自己测试这些建议… :-/

  1. 这个 context 有一个 root 属性,所以你可以只在(默认的)'结束'事件上使用 iterparse

    context = ET.iterparse(xml_source)
    
    for event, elem in context:
        if elem.tag == 'child' and elem.attrib['id'] != 'c1':
            elem.clear()
    
    ET.ElementTree(context.root).write(xml_output)    
    
  2. lxml.etree 替代 xml.etree

    import lxml.etree as ET
    
  3. lxml.etree.iterparse 有一个 tag 参数,可以只遍历特定的元素:

    context = ET.iterparse(xml_source, tag='child')
    
    for event, elem in context:
        if elem.attrib['id'] != 'c1':
            elem.clear()
    
  4. 最后一个建议,虽然不是关于速度的。 elem.clear() 并不会删除元素本身,而只是清空它的子元素、文本和尾部内容。所以你会得到空的 <child/> 元素:

    <root>
    <parent>
        <child id="c1">
            <content />
        </child>
        <child />
    </parent>
    <parent>
        <parent>
            <child />
        </parent>
    </parent>
    </root>
    

    使用 lxml,你可以用这个替代 elem.clear()

    for event, elem in context:
        if elem.attrib['id'] != 'c1':
            elem.getparent().remove(elem)
    

撰写回答