lxml iterparse 清理后仍占用内存

3 投票
2 回答
2554 浏览
提问于 2025-04-28 04:59

我正在尝试解析XML文件。第一次使用iterparse的时候一切正常,但第二次开始占用内存。如果我去掉第一次的iterparse,结果也没有变化。XML文件是有效的。

def clear_element(e):
    e.clear()
    while e.getprevious() is not None:
        del e.getparent()[0]

def import_xml(request):
    f = 'file.xml'
    offers = etree.iterparse(f, events=('end',), tag='offer')
    for event, offer in offers:
        # processing
        # works correctly
        clear_element(offer)

    categories = etree.iterparse(f, events=('end',), tag='category')
    for event, category in categories:
        # using memory
        clear_element(category)

XML内容:

<shop>
    <categories>
        <category>name</category>
        <category>name</category>
        <category>name</category>
          ~ 1000 categories
    </categories>
    <offers>
        <offer>
           <inner_tag>data</inner_tag>
           <inner_tag>data</inner_tag>
        </offer>
        <offer>
           <inner_tag>data</inner_tag>
           <inner_tag>data</inner_tag>
        </offer>
          ~ 450000 offers
    </offers>
</shop>
暂无标签

2 个回答

0

我之前也在和 iterparse 斗争了一段时间,现在终于觉得自己知道怎么正确使用它了,所以我来分享一下我的经验:

  1. 确保使用 cElementTree 的实现版本。

  2. 在处理过程中,记得清理掉那些你不需要的元素。如果你的 XML 结构很复杂,层次很深,这一点尤其重要。

假设你的 XML 里有这样的额外节点:

<offers>
    <offer>
       <inner_tag>data</inner_tag>
              <i2>
                    <i3>1000 characters of something</i3>                       
             </i2>
       <inner_tag>data</inner_tag>
    </offer>
</offers>

那么你的代码应该像这样:

def import_xml(request):
f = 'file.xml'
elements = etree.iterparse(f, events=('end',))
for event, element in elements:
    if element.tag == 'offer':
        # handle offer ...
    elif element.tag == 'category':
        # handle category ...
    elif element.tag != 'i2':
        continue
    element.clear()

这样一来,你就可以在处理 <offers> 中的其他元素时,完全忽略掉 <i2> 节点及其内容。

不过我发现 element.getparent().remove(element) 在我的代码中不管用(会报 AttributeError)。

3

你在解析文件的时候做了两次。第一次你保留了所有的 category 标签,丢掉了 offer 标签,这样处理1000个 category 标签其实占用的内存不多。

但是第二次你只丢掉了 category 标签,却保留了所有的450000个 offer 标签,这就是为什么构建树的时候会需要很多内存。

在这种情况下,最好不要在 iterparse 中使用 tag 参数,而是直接检查标签名,同时丢掉所有不需要的标签:

def import_xml(request):
    f = 'file.xml'
    elements = etree.iterparse(f, events=('end',))
    for event, element in elements:
        if element.tag == 'offer':
            # handle offer ...
        elif element.tag == 'category':
            # handle category ...
        else:
            continue
        element.clear()
        element.getparent().remove(element)

注意:仅仅调用 element.clear() 而不把它从父元素中删除,仍然会让清空的元素留在内存中,作为构建树的一部分。可能其实不需要使用 clear...

撰写回答