使用 Python Iterparse 处理大型 XML 文件

2025-02-27 09:05:00
admin
原创
54
摘要:问题描述:我需要用 Python 编写一个解析器,可以在没有太多内存(只有 2 GB)的计算机上处​​理一些非常大的文件(> 2 GB)。我想在 lxml 中使用 iterparse 来做到这一点。我的文件格式如下:<item> <title>Item 1</title...

问题描述:

我需要用 Python 编写一个解析器,可以在没有太多内存(只有 2 GB)的计算机上处​​理一些非常大的文件(> 2 GB)。我想在 lxml 中使用 iterparse 来做到这一点。

我的文件格式如下:

<item>
  <title>Item 1</title>
  <desc>Description 1</desc>
</item>
<item>
  <title>Item 2</title>
  <desc>Description 2</desc>
</item>

到目前为止我的解决方案是:

from lxml import etree

context = etree.iterparse( MYFILE, tag='item' )

for event, elem in context :
      print elem.xpath( 'description/text( )' )

del context

但不幸的是,这个解决方案仍然占用了大量内存。我认为问题在于,在处理完每个“ITEM”后,我需要做一些事情来清理空子项。有人能给我一些建议吗?在处理完数据后,我可以做些什么来正确清理?


解决方案 1:

尝试一下Liza Daly 的 fast_iter。处理完一个元素后,elem它会调用elem.clear()删除后代,并删除前面的兄弟元素。

def fast_iter(context, func, *args, **kwargs):
    """
    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(elem):
    print elem.xpath( 'description/text( )' )

context = etree.iterparse( MYFILE, tag='item' )
fast_iter(context,process_element)

Daly 的文章非常值得一读,特别是当您处理大型 XML 文件时。


编辑:fast_iter以上发布的是 Daly 的修改版本fast_iter。处理完一个元素后,它会更积极地删除不再需要的其他元素。

下面的脚本显示了行为上的差异。特别注意orig_fast_iter不会删除A1元素,而mod_fast_iter会删除元素,从而节省更多内存。

import lxml.etree as ET
import textwrap
import io

def setup_ABC():
    content = textwrap.dedent('''\n      <root>
        <A1>
          <B1></B1>
          <C>1<D1></D1></C>
          <E1></E1>
        </A1>
        <A2>
          <B2></B2>
          <C>2<D></D></C>
          <E2></E2>
        </A2>
      </root>
        ''')
    return content


def study_fast_iter():
    def orig_fast_iter(context, func, *args, **kwargs):
        for event, elem in context:
            print('Processing {e}'.format(e=ET.tostring(elem)))
            func(elem, *args, **kwargs)
            print('Clearing {e}'.format(e=ET.tostring(elem)))
            elem.clear()
            while elem.getprevious() is not None:
                print('Deleting {p}'.format(
                    p=(elem.getparent()[0]).tag))
                del elem.getparent()[0]
        del context

    def mod_fast_iter(context, func, *args, **kwargs):
        """
        http://www.ibm.com/developerworks/xml/library/x-hiperfparse/
        Author: Liza Daly
        See also http://effbot.org/zone/element-iterparse.htm
        """
        for event, elem in context:
            print('Processing {e}'.format(e=ET.tostring(elem)))
            func(elem, *args, **kwargs)
            # It's safe to call clear() here because no descendants will be
            # accessed
            print('Clearing {e}'.format(e=ET.tostring(elem)))
            elem.clear()
            # Also eliminate now-empty references from the root node to elem
            for ancestor in elem.xpath('ancestor-or-self::*'):
                print('Checking ancestor: {a}'.format(a=ancestor.tag))
                while ancestor.getprevious() is not None:
                    print(
                        'Deleting {p}'.format(p=(ancestor.getparent()[0]).tag))
                    del ancestor.getparent()[0]
        del context

    content = setup_ABC()
    context = ET.iterparse(io.BytesIO(content), events=('end', ), tag='C')
    orig_fast_iter(context, lambda elem: None)
    # Processing <C>1<D1/></C>
    # Clearing <C>1<D1/></C>
    # Deleting B1
    # Processing <C>2<D/></C>
    # Clearing <C>2<D/></C>
    # Deleting B2

    print('-' * 80)
    """
    The improved fast_iter deletes A1. The original fast_iter does not.
    """
    content = setup_ABC()
    context = ET.iterparse(io.BytesIO(content), events=('end', ), tag='C')
    mod_fast_iter(context, lambda elem: None)
    # Processing <C>1<D1/></C>
    # Clearing <C>1<D1/></C>
    # Checking ancestor: root
    # Checking ancestor: A1
    # Checking ancestor: C
    # Deleting B1
    # Processing <C>2<D/></C>
    # Clearing <C>2<D/></C>
    # Checking ancestor: root
    # Checking ancestor: A2
    # Deleting A1
    # Checking ancestor: C
    # Deleting B2

study_fast_iter()

解决方案 2:

iterparse()让您在构建树的同时执行一些操作,这意味着除非您删除不再需要的内容,否则最终您仍然会得到整棵树。

更多信息请阅读ElementTree 原始实现的作者撰写的文章(但也适用于 lxml)

解决方案 3:

为什么不使用sax的“回调”方法?

解决方案 4:

请注意,iterparse 仍会构建一棵树,就像 parse 一样,但您可以在解析时安全地重新排列或删除树的某些部分。例如,要解析大型文件,您可以在处理完元素后立即删除它们:

`for event, elem in iterparse(source):
if elem.tag == "record":
... process record elements ...
elem.clear()`
上述模式有一个缺点;它不会清除根元素,因此最终会得到一个带有大量空子元素的单个元素。如果您的文件很大,而不仅仅是很大,这可能是一个问题。要解决这个问题,您需要掌握根元素。最简单的方法是启用启动事件,并将对第一个元素的引用保存在变量中:

获得一个可迭代对象

context = iterparse(source, events=("start", "end"))

将其变成迭代器

context = iter(context)

获取根元素

event, root = context.next()

for event, elem in context:
    if event == "end" and elem.tag == "record":
        ... process record elements ...
        root.clear()

所以这是一个增量解析的问题,这个链接可以给你详细的答案,总结一下答案,你可以参考上面的内容

解决方案 5:

根据我的经验,无论是否使用 iterparse element.clear(参见F. Lundh和 L. Daly),都无法始终处理非常大的 XML 文件:一段时间内运行良好,突然内存消耗急剧上升,出现内存错误或系统崩溃。如果您遇到同样的问题,也许可以使用相同的解决方案:expat 解析器。另请参阅F. Lundh或以下使用 OP 的 XML 片段的示例(加上两个变音符以检查是否存在编码问题):

import xml.parsers.expat
from collections import deque

def iter_xml(inpath: str, outpath: str) -> None:
    def handle_cdata_end():
        nonlocal in_cdata
        in_cdata = False

    def handle_cdata_start():
        nonlocal in_cdata
        in_cdata = True

    def handle_data(data: str):
        nonlocal in_cdata
        if not in_cdata and open_tags and open_tags[-1] == 'desc':
            data = data.replace('\\', '\\\\').replace('
', '\\n')
            outfile.write(data + '
')

    def handle_endtag(tag: str):
        while open_tags:
            open_tag = open_tags.pop()
            if open_tag == tag:
                break

    def handle_starttag(tag: str, attrs: 'Dict[str, str]'):
        open_tags.append(tag)

    open_tags = deque()
    in_cdata = False
    parser = xml.parsers.expat.ParserCreate()
    parser.CharacterDataHandler = handle_data
    parser.EndCdataSectionHandler = handle_cdata_end
    parser.EndElementHandler = handle_endtag
    parser.StartCdataSectionHandler = handle_cdata_start
    parser.StartElementHandler = handle_starttag
    with open(inpath, 'rb') as infile:
        with open(outpath, 'w', encoding = 'utf-8') as outfile:
            parser.ParseFile(infile)

iter_xml('input.xml', 'output.txt')

输入.xml:

<root>
    <item>
    <title>Item 1</title>
    <desc>Description 1ä</desc>
    </item>
    <item>
    <title>Item 2</title>
    <desc>Description 2ü</desc>
    </item>
</root>

输出.txt:

Description 1ä
Description 2ü

解决方案 6:

root.clear() 方法的唯一问题是它返回 NoneTypes。这意味着您无法编辑使用 replace() 或 title() 等字符串方法解析的数据。也就是说,如果您只是按原样解析数据,这是一种最佳方法。

相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   3848  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   2709  
  本文介绍了以下10款项目管理软件工具:禅道项目管理软件、Freshdesk、ClickUp、nTask、Hubstaff、Plutio、Productive、Targa、Bonsai、Wrike。在当今快速变化的商业环境中,项目管理已成为企业成功的关键因素之一。然而,许多企业在项目管理过程中面临着诸多痛点,如任务分配不...
项目管理系统   35  
  本文介绍了以下10款项目管理软件工具:禅道项目管理软件、Monday、TeamGantt、Filestage、Chanty、Visor、Smartsheet、Productive、Quire、Planview。在当今快速变化的商业环境中,项目管理已成为企业成功的关键因素之一。然而,许多项目经理和团队在管理复杂项目时,常...
开源项目管理工具   28  
  本文介绍了以下10款项目管理软件工具:禅道项目管理软件、Smartsheet、GanttPRO、Backlog、Visor、ResourceGuru、Productive、Xebrio、Hive、Quire。在当今快节奏的商业环境中,项目管理已成为企业成功的关键因素之一。然而,许多企业在选择项目管理工具时常常面临困惑:...
项目管理系统   35  
热门文章
项目管理软件有哪些?
曾咪二维码

扫码咨询,免费领取项目管理大礼包!

云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用