为了账号安全,请及时绑定邮箱和手机立即绑定

对大型XML文件使用Python Iterparse

/ 猿问

对大型XML文件使用Python Iterparse

侃侃尔雅 2019-09-21 14:33:22

我需要用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”打交道之后,我需要做一些清理空孩子的事情。在处理完数据以进行适当清理之后,谁能提出一些建议以解决我的问题?


查看完整描述

3 回答

?
千万里不及你

尝试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('''\

      <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()


查看完整回答
反对 回复 2019-09-21
?
隔江千里

iterparse()让您在构建树时做些事情,这意味着除非您删除不再需要的树,否则最终还是会剩下整个树。


欲了解更多信息:阅读这个由最初的ElementTree实现的作者(但它也适用限于lxml)


查看完整回答
反对 回复 2019-09-21
?
万千封印

以我的经验,有或没有element.clear(请参阅F. Lundh和L. Daly)的iterparse 不能总是处理非常大的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', '\\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>

output.txt的:


Description 1ä

Description 2ü


查看完整回答
反对 回复 2019-09-21

添加回答

回复

举报

0/150
提交
取消
意见反馈 邀请有奖 帮助中心 APP下载
官方微信