【问题标题】:using lxml and iterparse() to parse a big (+- 1Gb) XML file使用 lxml 和 iterparse() 解析大 (+- 1Gb) XML 文件
【发布时间】:2012-04-09 00:20:39
【问题描述】:

我必须解析一个具有如下结构的 1Gb XML 文件,并提取标签“作者”和“内容”中的文本:

<Database>
    <BlogPost>
        <Date>MM/DD/YY</Date>
        <Author>Last Name, Name</Author>
        <Content>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas dictum dictum vehicula.</Content>
    </BlogPost>

    <BlogPost>
        <Date>MM/DD/YY</Date>
        <Author>Last Name, Name</Author>
        <Content>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas dictum dictum vehicula.</Content>
    </BlogPost>

    [...]

    <BlogPost>
        <Date>MM/DD/YY</Date>
        <Author>Last Name, Name</Author>
        <Content>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas dictum dictum vehicula.</Content>
    </BlogPost>
</Database>

到目前为止,我已经尝试了两件事:i) 读取整个文件并使用 .find(xmltag) 进行处理;ii) 使用 lxml 和 iterparse() 解析 xml 文件。 第一个选项我已经让它工作了,但它很慢。第二个选项我还没有成功。

这是我拥有的部分内容:

for event, element in etree.iterparse(path_to_file, tag="BlogPost"):
    if element.tag == "BlogPost":
        print element.text
    else:
        print 'Finished'

结果只有空格,里面没有文字。

我一定是做错了什么,但我无法理解。另外,如果还不够明显,我对 python 还是很陌生,这是我第一次使用 lxml。请帮忙!

【问题讨论】:

  • 好吧,BlogPost 标签中似乎没有任何文字。
  • 是的。获取开始和结束 BlogPost 标签之间的所有内容的方法是什么?
  • 如果您只需要 BlogPost 标签内的所有信息,请遵循 andrew 的建议。如果您希望它是 HTML 格式的,请将 lxml.etree.tostring() 应用于它们。

标签: python xml parsing lxml iterparse


【解决方案1】:

对于这样的事情我更喜欢XPath

In [1]: from lxml.etree import parse

In [2]: tree = parse('/tmp/database.xml')

In [3]: for post in tree.xpath('/Database/BlogPost'):
   ...:     print 'Author:', post.xpath('Author')[0].text
   ...:     print 'Content:', post.xpath('Content')[0].text
   ...: 
Author: Last Name, Name
Content: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas dictum dictum vehicula.
Author: Last Name, Name
Content: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas dictum dictum vehicula.
Author: Last Name, Name
Content: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas dictum dictum vehicula.

不过,我不确定它在处理大文件方面是否有所不同。对此的评论将不胜感激。

按照自己的方式做,

for event, element in etree.iterparse(path_to_file, tag="BlogPost"):
     for info in element.iter():
         if info.tag in ('Author', 'Content'):
             print info.tag, ':', info.text

【讨论】:

  • mm 我已经稍微简化了树,但当我尝试它时它似乎不起作用。例如,标签 BlogPost 不仅仅是 '' 而是 '' 并且 Owner 和 Status 的值从一个条目变为另一个条目。
  • 其他属性不会影响这一点;只有树结构很重要。要捕获所有BlogPost 元素,您还可以使用for post in tree.xpath('//BlogPost'): ...
  • 谢谢!我还不能投票,但你帮助我了解了它是如何工作的。我理解得更好并且已经开始工作的答案是安德鲁的。
  • 谢谢@andrew。你也有我的,主要是我不知道的clear() 方法。
  • 我最近做了一个比较,iterparseclear() 消耗的内存比XPath很多
【解决方案2】:
for event, element in etree.iterparse(path_to_file, tag="BlogPost"):
  for child in element:
    print(child.tag, child.text)
    element.clear()

最终清除将阻止您使用过多的内存。

[update:] 获取“...之间的所有内容作为字符串”我猜你想要一个:

for event, element in etree.iterparse(path_to_file, tag="BlogPost"):
  print(etree.tostring(element))
  element.clear()

for event, element in etree.iterparse(path_to_file, tag="BlogPost"):
  print(''.join([etree.tostring(child) for child in element]))
  element.clear()

或者甚至:

for event, element in etree.iterparse(path_to_file, tag="BlogPost"):
  print(''.join([child.text for child in element]))
  element.clear()

【讨论】:

  • 这很像我想要的,我必须对其进行一些自定义,但它很棒。谢谢!
  • 有没有办法将开始和结束“BlogPost”标签之间的所有内容作为字符串获取?
  • @mvime,作为什么样的字符串?以 HTML 格式?然后看我上面的评论,lxml.etree.tostring() 方法就是这样做的。您可以使用切片表示法切断开始和结束标记(请参阅this table
  • 后面的片段中element.close()应该是element.clear()吗?自从我写了这个我已经不记得了,但我觉得它看起来不对。
  • 我还必须解析 1.8 GB 的 xml 文件,并且还使用相同的 clear 函数来清除元素,但是 clear() 实际上并没有从内存中删除元素,最后你结束了拥有带有空元素的根,这也需要内存。所以我在使用“del”语句解析后删除了元素,这有助于我释放内存。阅读effbot.org/zone/element-iterparse.htm#incremental-parsing 以了解究竟发生了什么。
【解决方案3】:

对于未来的搜索者:这里的最佳答案建议在每次迭代时清除元素,但这仍然会给您留下越来越多的空元素集,这些元素将在内存中慢慢累积:

for event, element in etree.iterparse(path_to_file, tag="BlogPost"):
  for child in element:
    print(child.tag, child.text)
    element.clear()

^ 这不是一个可扩展的解决方案,尤其是当您的源文件变得越来越大时。更好的解决方案是获取 root 元素,并在每次加载完整记录时清除 that。这将使内存使用保持相当稳定(我会说低于 20MB)。

这是一个不需要寻找特定标签的解决方案。此函数将返回一个生成器,该生成器在根节点(例如 &lt;Database&gt;)下产生所有 第一个子 节点(例如 &lt;BlogPost&gt; 元素)。它通过记录根节点之后的第一个标签的开始,然后等待相应的结束标签,产生整个元素,然后清除根节点来做到这一点。

from lxml import etree

xmlfile = '/path/to/xml/file.xml'

def iterate_xml(xmlfile):
    doc = etree.iterparse(xmlfile, events=('start', 'end'))
    _, root = next(doc)
    start_tag = None
    for event, element in doc:
        if event == 'start' and start_tag is None:
            start_tag = element.tag
        if event == 'end' and element.tag == start_tag:
            yield element
            start_tag = None
            root.clear()

【讨论】:

  • 嗯,很喜欢这个主意。但是如果我需要支持多个文件结构,我怎么能在没有找到特定标签的情况下做到这一点呢?例如:假设有两种类型的 xml 文件,一种结构是source-&gt;jobs-&gt;job-&gt;...,另一种结构是jobs-&gt;job。我只想获取所有job。我该如何使用这个解决方案?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-03-28
  • 1970-01-01
  • 2013-05-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-01-29
相关资源
最近更新 更多