对大型XML文件使用Python Iterparse

我需要用Python编写一个parsing器,它可以在没有太多内存(只有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> 

到目前为止我的解决scheme是:

 from lxml import etree context = etree.iterparse( MYFILE, tag='item' ) for event, elem in context : print elem.xpath( 'description/text( )' ) del context 

不幸的是,这个解决scheme仍然消耗了大量的内存。 我觉得问题是,在处理好每一个“项目”之后,我需要做些什么来清理空的孩子。 任何人都可以提供一些build议,我可以做什么后,处理我的数据妥善清理?

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

达利的文章是一个很好的阅读,尤其是如果你正在处理大型的XML文件。


编辑: fast_iter贴出的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() 

iterparse()可以让你在构build树的时候做一些东西,这意味着除非你删除了不再需要的东西,否则最后还是会iterparse()整棵树。

有关更多信息,请参阅原始ElementTree实现的作者(但它也适用于lxml)

你为什么不使用萨克斯的“callback”方法?

请注意,iterparse仍然像生成parse一样构build树,但在parsing时可以安全地重新排列或删除部分树。 例如,要parsing大文件,可以在处理完元素后立即清除元素:

for event, elem in iterparse(source): if elem.tag == "record": ... process record elements ... elem.clear()上述模式有一个缺点; 它没有清除根元素,所以你将会得到一个有很多空子元素的元素。 如果你的文件很大,而不是很大,这可能是一个问题。 要解决这个问题,你需要把你的手放在根元素上。 最简单的方法是启用启动事件,并保存对variables中第一个元素的引用:

得到一个迭代

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

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