xml.etree.ElementTree.iterparse()不适用于大型XML文档?

时间:2014-04-12 00:05:56

标签: python-3.x xml.etree

我正在尝试通过XML文档iterparse()进行增量解析,该文档(设计为)太大而无法放入内存中。我发现甚至在doc排气过程存储器上进行无操作传递并导致我的系统开始交换。

期望xml.etree.ElementTree.iterparse()在独立于XML文档大小的常量内存中运行是错误的吗?如果是这样,对于任意长的XML文档进行增量解析的推荐包是什么?如果没有,WTF错误的代码?

这是代码: 请注意,我只请求'start'事件(因此解析器在返回文档根元素的结束标记之前不会尝试缓冲所有的body元素(在我的例子中是< osm>)。我明确地{{1}循环变量强制它们被释放。

认为垃圾收集器可能没有机会运行,因为循环不会产生,我每隔百万次迭代就添加了对del()gc.collect()的显式调用。但它没有帮助。

time.sleep()

以下是doc的示例。它是完善的OSM数据。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import xml.etree.ElementTree as ET
import pprint

import gc
import time
import os
import psutil

def gcStats(myProc):
    # return human readable gc.stats for 3 generations

    extmem = myProc.memory_info_ex()

    a =  "extmem: rss {:12n}, vms {:12n}, shared{:12n}, text{:12n}, lib {:12n}, data{:12n}, dirty{:12n}".format(
                    extmem.rss, extmem.vms, extmem.shared, extmem.text, extmem.lib, extmem.data, extmem.dirty)

    return a + "\tgc enabled {}, sumCount {:n}, lenGarbage {:n}".format( gc.isenabled(), sum(gc.get_count()), len(gc.garbage)) 

# the misbehaving code:    
def count_tags(filename):
    retVal = {}
    iterCount = 0
    sleepTime = 2.0
    myProc = psutil.Process()

    print("Starting: gc.isenabled() == {}\n{}".format(gc.isenabled(), gcStats( myProc)))

    for event, element in ET.iterparse(filename, ('start',)):
        assert event == 'start'
        if iterCount % 1000000 == 0:
            print('{} iterations, sleeping {} sec...'.format(iterCount, sleepTime))
            time.sleep( sleepTime)
            print('{}\nNow starting gc pass...'.format( gcStats( myProc)))
            gcr = gc.collect()
            print('gc returned {}'.format( gcr))
        iterCount += 1
        del element
        del event
    return retVal


if __name__ == "__main__":
    tags = count_tags('/home/bobhy/MOOC_Data/' + 'chicago.osm')

。 。 。等等1.8 GB。 。

<?xml version='1.0' encoding='UTF-8'?>
<osm version="0.6" generator="Osmosis 0.43.1">
  <bounds minlon="-88.50500" minlat="41.33900" maxlon="-87.06600" maxlat="42.29700" origin="http://www.openstreetmap.org/api/0.6"/>
  <node id="219850" version="54" timestamp="2011-04-06T05:17:15Z" uid="207745" user="NE2" changeset="7781188" lat="41.7585879" lon="-87.9101245">
    <tag k="exit_to" v="Joliet Road"/>
    <tag k="highway" v="motorway_junction"/>
    <tag k="ref" v="276C"/>
  </node>
  <node id="219851" version="47" timestamp="2011-04-06T05:18:47Z" uid="207745" user="NE2" changeset="7781188" lat="41.7593116" lon="-87.9076432">
    <tag k="exit_to" v="North I-294 ; Tri-State Tollway;  Wisconsin"/>
    <tag k="highway" v="motorway_junction"/>
    <tag k="ref" v="277A"/>
  </node>
  <node id="219871" version="1" timestamp="2006-04-15T00:34:03Z" uid="229" user="LA2" changeset="3725" lat="41.932278" lon="-87.9179332"/>
  <node id="700724" version="14" timestamp="2009-04-13T11:21:51Z" uid="18480" user="nickvet419" changeset="485405" lat="41.7120272" lon="-88.0158606"/>

这是输出:

  <relation id="3366425" version="1" timestamp="2013-12-07T21:37:35Z" uid="239998" user="Sundance" changeset="19330301">
    <member type="way" ref="250651738" role="outer"/>
    <member type="way" ref="250651748" role="inner"/>
    <tag k="type" v="multipolygon"/>
  </relation>
  <relation id="3378994" version="1" timestamp="2013-12-14T22:24:26Z" uid="371121" user="AndrewSnow" changeset="19456337">
    <member type="way" ref="251850076" role="outer"/>
    <member type="way" ref="251850073" role="inner"/>
    <member type="way" ref="251850074" role="inner"/>
    <member type="way" ref="251850075" role="inner"/>
    <tag k="type" v="multipolygon"/>
  </relation>
  <relation id="3382796" version="1" timestamp="2013-12-17T03:21:18Z" uid="567034" user="Umbugbene" changeset="19492258">
    <member type="way" ref="252225400" role="outer"/>
    <member type="way" ref="252225404" role="inner"/>
    <tag k="type" v="multipolygon"/>
  </relation>
</osm>

我解释输出显示进程虚拟内存增长大约1 000 B /迭代(即,每个XML标记解析)。我认为垃圾收集统计数据并没有显示分配对象的单调增加,因此我不知道内存增长的来源。确实启用了垃圾收集。

2 个答案:

答案 0 :(得分:2)

您需要通过调用方法element.clear()明确清除不再需要的元素,否则它仍会留在内存中。这意味着您可能还想收听'end'事件,并在到达封装元素的末尾时调用clear(),您知道它不再需要任何内容​​。

答案 1 :(得分:1)

仔细阅读iterparse()的文档让我相信上述内容是预期的行为。该文档说它返回一个完整的元素,对子访问没有限制,因此它必须在内存中保留(递增的)文档树。

由于我的问题不需要父元素或子元素访问,只是遇到每个标记的事件,我能够很好地解决我的问题xml.etree.ElementTree.XMLParser()