【问题标题】:Using Python Iterparse For Large XML Files使用 Python Iterparse 处理大型 XML 文件
【发布时间】:2011-11-02 12:33:42
【问题描述】:

我需要用 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”之后,我需要做一些事情来清理空的孩子。任何人都可以就处理我的数据以正确清理后可能会做什么提供一些建议吗?

【问题讨论】:

  • 作为一个附录,我的描述打印输出只是一个简单的例子,我实际上必须对处理的每个项目的内容做更多的工作,但我想简化它。跨度>
  • 在处理了单次迭代的元素后,您是否尝试调用 elem.clear() 来回收迭代元素的内存?

标签: python xml lxml large-files elementtree


【解决方案1】:

试试Liza Daly's 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 是戴利fast_iter 的修改版本。处理完一个元素后,它会更积极地移除不再需要的其他元素。

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

import lxml.etree as ET
import textwrap
import io

def setup_ABC():
    content = textwrap.dedent('''\
      <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()

【讨论】:

  • 整洁。然而,如果我们指定的元素 tag='item' 不存在并且 XML 非常大,则会有大量内存没有被释放。我假设树建立起来,并且由于没有触发结束事件,我们将整个 XMl 放在内存中。有什么解决办法吗?
  • @bioslime:我不知道。通常解析 XML 需要您先验知道 XML 的格式。
  • @bioslime:如果您知道存在 一些 标签并希望清除这些标签以节省内存,您可以使用 iterparse 遍历这些标签,然后调用 @987654322 @在回调函数里面搜索item标签。这样,您可以搜索未知标签,同时仍然节省一些内存。但是您仍然必须知道存在 some 标签。这是使用这种方法的an example
  • @unutbu:好的,我去看看。我确实知道格式,但在某些 XML 中,一个元素的所有出现都是&lt;item xsi:nil="true"/&gt; 而不是&lt;item&gt;&lt;/item&gt;。现在我做一个简单的预检查:打开文件,遍历每一行并检查&lt;item&gt; 是否在其中。如果是这样,请跳出循环。如果没有,我稍后会跳过for event, elem in context
  • @bioslime:您是否尝试过在没有预检查的情况下使用tag='item'iterparse 会以任何一种方式找到这些项目,因此fast_iter 将在 items 得到处理时清除元素。以这种方式处理所有 XML 可能比进行预检查更快,具体取决于命中与无效的比率。
【解决方案2】:

iterparse() 让您在构建树的同时做一些事情,这意味着除非您删除不再需要的东西,否则最终您仍然会得到整棵树。

更多信息:阅读原 ElementTree 实现作者的this(但它也适用于 lxml)

【讨论】:

    【解决方案3】:

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

    【讨论】:

      【解决方案4】:

      根据我的经验,使用或不使用 element.clear(请参阅 F. Lundh 和 L. Daly)的 iterparse 无法始终处理非常大的 XML 文件:它运行良好一段时间后,突然内存消耗达到顶峰,并且发生内存错误或系统崩溃。如果您遇到同样的问题,也许您可​​以使用相同的解决方案:expat 解析器。另请参阅F. Lundh 或使用 OP 的 XML sn-p 的以下示例(加上两个变音符号用于检查是否存在编码问题):

      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', '\\n')
                  outfile.write(data + '\n')
      
          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')
      

      input.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ü
      

      【讨论】:

        【解决方案5】:

        请注意,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()
        

        所以这是一个增量解析的问题,This link can give you detailed answer 的总结答案你可以参考上面

        【讨论】:

          【解决方案6】:

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

          【讨论】:

            猜你喜欢
            • 2013-05-19
            • 1970-01-01
            • 2014-05-23
            • 1970-01-01
            • 1970-01-01
            • 2012-04-09
            • 2016-09-02
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多