【问题标题】:ElementTree iterparse strategyElementTree 迭代解析策略
【发布时间】:2012-10-09 04:51:47
【问题描述】:

我必须处理足够大(最大 1GB)的 xml 文档并用 python 解析它们。我正在使用iterparse() 函数(SAX 样式解析)。

我关心的是以下,假设你有一个这样的 xml

<?xml version="1.0" encoding="UTF-8" ?>
<families>
  <family>
    <name>Simpson</name>
    <members>
        <name>Homer</name>
        <name>Marge</name>
        <name>Bart</name>
    </members>
  </family>
  <family>
    <name>Griffin</name>
    <members>
        <name>Peter</name>
        <name>Brian</name>
        <name>Meg</name>
    </members>
  </family>
</families>

问题当然是要知道我何时获得姓氏(如辛普森一家)以及何时获得该家庭成员之一的姓名(例如荷马)

到目前为止,我一直在做的是使用“开关”,它会告诉我我是否在“成员”标签内,代码看起来像这样

import xml.etree.cElementTree as ET

__author__ = 'moriano'

file_path = "test.xml"
context = ET.iterparse(file_path, events=("start", "end"))

# turn it into an iterator
context = iter(context)
on_members_tag = False
for event, elem in context:
    tag = elem.tag
    value = elem.text
    if value :
        value = value.encode('utf-8').strip()

    if event == 'start' :
        if tag == "members" :
            on_members_tag = True

        elif tag == 'name' :
            if on_members_tag :
                print "The member of the family is %s" % value
            else :
                print "The family is %s " % value

    if event == 'end' and tag =='members' :
        on_members_tag = False
    elem.clear()

这很好用,因为输出是

The family is Simpson 
The member of the family is Homer
The member of the family is Marge
The member of the family is Bart
The family is Griffin 
The member of the family is Peter
The member of the family is Brian
The member of the family is Meg

我担心的是,对于这个(简单的)示例,我必须创建一个额外的变量来知道我在哪个标签中(on_members_tag),想象一下我必须处理的真正的 xml 示例,它们有更多的嵌套标签。

还请注意,这是一个非常简化的示例,因此您可以假设我可能面临一个带有更多标签、更多内部标签的 xml,并试图获取不同的标签名称、属性等。

所以问题是。我在这里做一些非常愚蠢的事情吗?我觉得必须有一个更优雅的解决方案。

【问题讨论】:

  • 您将如何处理这些数据?构建一个 Python 数据结构来保存它,或者在迭代时存储在 db 中,或者其他什么?
  • @JanneKarila : 数据可以放在python结构上,保存到数据库或转储到文件中,这取决于过程,在这种情况下你可以假设它会被写入数据库

标签: python xml sax elementtree iterparse


【解决方案1】:

这是一种可能的方法:我们维护一个路径列表并向后查看以找到父节点。

path = []
for event, elem in ET.iterparse(file_path, events=("start", "end")):
    if event == 'start':
        path.append(elem.tag)
    elif event == 'end':
        # process the tag
        if elem.tag == 'name':
            if 'members' in path:
                print 'member'
            else:
                print 'nonmember'
        path.pop()

【讨论】:

  • 简单、优雅、能胜任。非常感谢:)
  • 这种方法有标准名称吗?我相信这种方法用于许多此类问题。如果你能说出它的名字,我可以深入挖掘并理解这一点。
  • 虽然我喜欢这个简洁的解决方案,但我认为值得注意的是它可能不如使用布尔标志的原始方法性能低,因为引入了列表中的搜索。即使对于通常的文档,最大深度是相当合理的(10-20 层?),它可能对于非常嵌套的树会变得不好
  • @TheGodfather 好点。如果性能是一个问题,您可以将path 列表替换为collections.Counter() 对象,然后使用path[elem.tag] += 1path[elem.tag] -= 1 而不是appendpop。这具有 O(1) 分期查找,并且计数器大小与 unique 标记名称的数量成正比,这使其节省空间。
【解决方案2】:

pulldom 非常适合这个。你得到一个萨克斯流。您可以遍历流,当您找到感兴趣的节点时,将该节点加载到 dom 片段中。

import xml.dom.pulldom as pulldom
import xpath # from http://code.google.com/p/py-dom-xpath/

events = pulldom.parse('families.xml')
for event, node in events:
    if event == 'START_ELEMENT' and node.tagName=='family':
        events.expandNode(node) # node now contains a dom fragment
        family_name = xpath.findvalue('name', node)
        members = xpath.findvalues('members/name', node)
        print('family name: {0}, members: {1}'.format(family_name, members))

输出:

family name: Simpson, members: [u'Hommer', u'Marge', u'Bart']
family name: Griffin, members: [u'Peter', u'Brian', u'Meg']

【讨论】:

  • 这是一个非常好的解决方案,但是我不能把它作为一个公认的答案给你(我更喜欢 nneonneo 的答案),但是,它绝对看起来是一个优雅的解决方案。谢谢!
  • 很好的答案。使用非常简单。允许解析 46 GB xml 文件
  • 这个不幸的问题是它非常慢。快速比较 cElementTree.parse 和 pulldom.parse 方法给了我 1 分钟的 cElementTree 和 10 分钟的 pulldom.parse。对于大文件来说,时间增加 10 倍是疯狂的。
猜你喜欢
  • 2017-09-29
  • 1970-01-01
  • 1970-01-01
  • 2021-02-08
  • 1970-01-01
  • 2014-05-14
  • 2021-11-16
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多