对于80 + GB XML,Python sax到lxml

时间:2012-03-21 17:08:22

标签: python sax lxml

如何使用sax读取XML文件并将其转换为lxml etree.iterparse元素?

为了提供问题的概述,我使用lxml构建了一个XML提取工具,用于XML提要,范围大小为25 - 500MB,需要每日摄取一次,但需要执行一次时间摄取60 - 100GB的文件。

我选择使用lxml基于规范,详细说明一个节点的大小不会超过4-8 GB,我认为这样可以将节点读入内存并在完成后清除。

如果代码在下面的概述

elements = etree.iterparse(
    self._source, events = ('end',)
)
for event, element in elements:
    finished = True
    if element.tag == 'Artist-Types':
        self.artist_types(element)

def artist_types(self, element):
    """
    Imports artist types

    :param list element: etree.Element
    :returns boolean:
    """
    self._log.info("Importing Artist types")
    count = 0
    for child in element:
        failed = False
        fields = self._getElementFields(child, (
            ('id', 'Id'),
            ('type_code', 'Type-Code'),
            ('created_date', 'Created-Date')
        ))
        if self._type is IMPORT_INC and has_artist_type(fields['id']):
            if update_artist_type(fields['id'], fields['type_code']):
                count = count + 1
            else:
                failed = True
        else:
            if create_artist_type(fields['type_code'],
                fields['created_date'], fields['id']):
                count = count + 1
            else:
                failed = True
        if failed:
            self._log.error("Failed to import artist type %s %s" %
                (fields['id'], fields['type_code'])
            )
    self._log.info("Imported %d Artist Types Records" % count)
    self._artist_type_count = count
    self._cleanup(element)
    del element

如果我可以添加任何类型的澄清,请告诉我。

3 个答案:

答案 0 :(得分:21)

iterparse是一个迭代解析器。它将发出Element个对象和事件,并在解析时逐步构建整个Element树,因此最终它将整个树都存储在内存中。

但是,很容易产生有限的记忆行为:删除你解析它们时不再需要的元素。

典型的巨型xml"工作负载是一个单个根元素,具有大量表示记录的子元素。我假设这是你正在使用的那种XML结构?

通常只需使用clear()清空您正在处理的元素即可。你的内存使用量会增长一点,但不是很多。如果你有一个非常庞大的文件,那么即使是空的Element对象也会消耗太多,在这种情况下你还必须删除以前看过的Element个对象。请注意,您无法安全地删除当前元素。 lxml.etree.iterparse documentation describes this technique

在这种情况下,您将在每次找到</record>时处理记录,然后您将删除所有以前的记录元素。

下面是使用无限长XML文档的示例。它将在解析时打印进程的内存使用情况。请注意,内存使用情况稳定,不会继续增长。

from lxml import etree
import resource

class InfiniteXML (object):
    def __init__(self):
        self._root = True
    def read(self, len=None):
        if self._root:
            self._root=False
            return "<?xml version='1.0' encoding='US-ASCII'?><records>\n"
        else:
            return """<record>\n\t<ancestor attribute="value">text value</ancestor>\n</record>\n"""

def parse(fp):
    context = etree.iterparse(fp, events=('end',))
    for action, elem in context:
        if elem.tag=='record':
            # processing goes here
            pass

        #memory usage
        print resource.getrusage(resource.RUSAGE_SELF).ru_maxrss

        # cleanup
        # first empty children from current element
            # This is not absolutely necessary if you are also deleting siblings,
            # but it will allow you to free memory earlier.
        elem.clear()
        # second, delete previous siblings (records)
        while elem.getprevious() is not None:
            del elem.getparent()[0]
        # make sure you have no references to Element objects outside the loop

parse(InfiniteXML())

答案 1 :(得分:3)

我在http://effbot.org/zone/element-iterparse.htm找到了这个有用的例子。大胆的重点是我的。

  

增量解析#

     

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

for event, elem in iterparse(source):
    if elem.tag == "record":
        ... process record elements ...
        elem.clear()
  

上述模式有一个缺点;它不会清除根元素,因此您最终会得到一个包含许多空子元素的元素。 如果您的文件很大,而不是大,这可能是个问题。要解决这个问题,你需要掌握根元素。最简单的方法是启用启动事件,并保存对变量中第一个元素的引用:

# get an iterable 
context = iterparse(source, events=("start", "end"))

# turn it into an iterator 
context = iter(context)

# get the root element 
event, root = context.next()

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

(将来的版本可以更容易地从循环中访问根元素)

答案 2 :(得分:-2)

这已经有几年了,而且我没有足够的声誉可以直接评论接受的答案,但我尝试使用它解析一个OSM,在那里我找到了一个国家的所有交叉点。我最初的问题是我的RAM用完了,所以我想我必须使用SAX解析器但是找到了这个答案。奇怪的是它没有正确解析,并且在阅读之前使用建议的清理以某种方式清除了elem节点(仍不确定这是怎么发生的)。从代码中删除了elem.clear(),现在它运行得非常好!