2012-03-24 89 views
10

我必须解析一个1Gb XML文件,并在下面的结构中提取标签“作者”和“内容”中的文本:使用lxml和iterparse()来解析一个大的(+ 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> 

到目前为止,我已经试过两件事情:1)读取整个文件,并通过它与.find(xmltag会)和ii)与解析lxml的xml文件和iterparse()。 第一个选项我已经得到它的工作,但它非常缓慢。第二种选择我没有设法实现它。

这里是什么,我有一部分:

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

的,其结果只能是空白,在他们没有文字。

我一定在做错事,但我无法把握。另外,如果它不够明显,我对Python非常陌生,这是我第一次使用lxml。请帮忙!

+1

那么'BlogPost'标签似乎并不包含任何文本。 – 2012-03-24 22:30:59

+0

是的。什么是获取开放和结束BlogPost标签之间的所有内容的方法? – mvime 2012-03-24 22:52:00

+0

如果您只需要'BlogPost'标签内的所有信息,请遵循andrew的建议。如果你想要HTML格式,请将'lxml.etree.tostring()'应用于它们。 – 2012-03-24 22:56:53

回答

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

最终明确将阻止你使用太多内存。

[更新:]来获得 “......之间的一切作为一个字符串” 我想你想的一个:

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

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

甚或:

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

这与我想要的非常相似,我必须自定义它,但它很棒。谢谢! – mvime 2012-03-24 23:02:40

+0

有没有办法让开始和结束“BlogPost”标签之间的所有内容都以字符串的形式出现? – mvime 2012-03-25 00:58:08

+1

@mvime,作为什么样的字符串?在HTML格式?然后在上面看到我的注释,'lxml.etree.tostring()'方法就是这样。您可以使用切片符号将开启和关闭标记关闭(请参见[本表])(http://docs.python.org/library/stdtypes.html#sequence-types-str-unicode-list-tuple-bytearray-buffer -xrange)) – 2012-03-25 10:13:43

4

我喜欢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 
+0

mm我简化了一下树,当我尝试它时,它似乎不起作用。例如,标签BlogPost不是简单的'',而是'',所有者和状态的值从一个条目变为另一个条目。 – mvime 2012-03-24 22:50:36

+1

其他属性不会影响这个;只有树结构很重要。要捕获所有'BlogPost'元素,您还可以使用'在tree.xpath('// BlogPost')中发布:...' – 2012-03-24 22:58:48

+1

谢谢!我还没有投票,但你帮助我理解它是如何工作的。尽管我理解得更好,但我已经开始工作的答案是安德鲁的。 – mvime 2012-03-24 23:01:57

7

对于未来的搜索者:这里的最佳答案建议在每次迭代时清除元素,但是这仍然会使您不断增加设置将慢慢在内存中建立空的元素:

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

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

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

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