我想“动态”解析一个大的XML文件。我想使用python生成器来执行此操作。我尝试过“xml.etree.cElementTree”的“iterparse”(非常好),但仍然不是发电机。
其他建议?
答案 0 :(得分:15)
xml.etree.cElementTree
接近正确使用的发电机;默认情况下,您会在“结束”事件后收到每个元素,此时您可以处理它。如果在处理后不需要元素,则应该对元素使用element.clear();从而节省了记忆。
这是一个完整的例子,我的意思是,解析Rhythmbox(音乐播放器)库。我使用(c)ElementTree的iterparse,并且对于每个处理过的元素,我调用element.clear(),以便节省大量内存。 (顺便说一句,下面的代码是一些sax代码的继承者做同样的事情; cElementTree解决方案是一个缓解,因为1)代码简洁并表达我需要的东西,仅此而已2)它是3倍速,3)它使用更少的内存。)
import os
import xml.etree.cElementTree as ElementTree
NEEDED_KEYS= set(("title", "artist", "album", "track-number", "location", ))
def _lookup_string(string, strmap):
"""Look up @string in the string map,
and return the copy in the map.
If not found, update the map with the string.
"""
string = string or ""
try:
return strmap[string]
except KeyError:
strmap[string] = string
return string
def get_rhythmbox_songs(dbfile, typ="song", keys=NEEDED_KEYS):
"""Return a list of info dictionaries for all songs
in a Rhythmbox library database file, with dictionary
keys as given in @keys.
"""
rhythmbox_dbfile = os.path.expanduser(dbfile)
lSongs = []
strmap = {}
# Parse with iterparse; we get the elements when
# they are finished, and can remove them directly after use.
for event, entry in ElementTree.iterparse(rhythmbox_dbfile):
if not (entry.tag == ("entry") and entry.get("type") == typ):
continue
info = {}
for child in entry.getchildren():
if child.tag in keys:
tag = _lookup_string(child.tag, strmap)
text = _lookup_string(child.text, strmap)
info[tag] = text
lSongs.append(info)
entry.clear()
return lSongs
现在,我不理解你的期望,你有以下期望吗?
# take one
for event, entry in ElementTree.iterparse(rhythmbox_dbfile):
# parse some entries, then exit loop
# take two
for event, entry in ElementTree.iterparse(rhythmbox_dbfile):
# parse the rest of entries
每次调用iterparse都会得到一个新的迭代器对象,重新读取文件!如果你想要一个带有迭代器语义的持久对象,你必须在两个循环中引用相同的对象(未尝试的代码):
#setup
parseiter = iter(ElementTree.iterparse(rhythmbox_dbfile))
# take one
for event, entry in parseiter:
# parse some entries, then exit loop
# take two
for event, entry in parseiter:
# parse the rest of entries
我认为这可能令人困惑,因为不同的对象具有不同的语义。文件对象将始终具有内部状态并在文件中前进,但是您将对其进行迭代。 ElementTree iterparse对象显然不是。关键是要想到当你使用for循环时,for总是在迭代的东西上调用iter()。这是一个将ElementTree.iterparse与文件对象进行比较的实验:
>>> import xml.etree.cElementTree as ElementTree
>>> pth = "/home/ulrik/.local/share/rhythmbox/rhythmdb.xml"
>>> iterparse = ElementTree.iterparse(pth)
>>> iterparse
<iterparse object at 0x483a0890>
>>> iter(iterparse)
<generator object at 0x483a2f08>
>>> iter(iterparse)
<generator object at 0x483a6468>
>>> f = open(pth, "r")
>>> f
<open file '/home/ulrik/.local/share/rhythmbox/rhythmdb.xml', mode 'r' at 0x4809af98>
>>> iter(f)
<open file '/home/ulrik/.local/share/rhythmbox/rhythmdb.xml', mode 'r' at 0x4809af98>
>>> iter(f)
<open file '/home/ulrik/.local/share/rhythmbox/rhythmdb.xml', mode 'r' at 0x4809af98>
你看到的是iterparse对象上每次调用iter()都会返回一个新的生成器。但是,文件对象具有必须保留的内部操作系统状态,并且它是自己的迭代器。
答案 1 :(得分:6)
“动态”解析和文档树不是真正兼容的。 SAX样式的解析器通常用于此(例如,Python的标准xml.sax)。您基本上必须为各种事件(如startElement,endElement等)定义一个带有处理程序的类,解析器将在解析XML文件时调用这些方法。
答案 2 :(得分:4)
PullDom做你想要的。它从流中读取XML,如SAX,但随后为其中的一部分构建DOM。
“PullDOM是一个非常简单的API,用于以流(高效!)方式处理DOM对象,而不是单片树。”
答案 3 :(得分:0)
这可以通过elementtree和增量解析来实现: http://effbot.org/zone/element-iterparse.htm#incremental-parsing
import xml.etree.cElementTree as etree
for event, elem in etree.iterparse(source):
...
比萨克斯更容易使用。
答案 4 :(得分:0)
xmltodict有一种回调方法,可以逐行读取,但它不是Pythonic。我想要类似的东西,使用生成器从其XML转储中逐一读取stackoverflow帖子。
这是xml文件的结构:
<?xml version="1.0" encoding="utf-8"?>
<posts>
<row Id="1" ... />
<row Id="2" ... />
</posts>
这是我使用的代码。它结合了Pulldom进行流传输和xmltodict进行行解析。
def xml_to_dict_gen(file_path, tag='row'):
from xml.dom import pulldom
import xmltodict
doc = pulldom.parse(file_path)
for event, node in doc:
if event == pulldom.START_ELEMENT and node.tagName == tag:
doc.expandNode(node)
yield dict(xmltodict.parse(node.toxml()).get(tag))
for post in xml_to_dict_gen('Posts.xml'):
print(post)