8

我想“即时”解析一个大的 XML 文件。我想使用 python 生成器来执行此操作。我已经尝试过“xml.etree.cElementTree”的“iterparse”(这非常好),但仍然不是生成器。

其他建议?

4

5 回答 5

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() 都会返回一个新的生成器。然而,文件对象有一个必须保存的内部操作系统状态和它自己的迭代器。

于 2009-10-03T12:40:25.940 回答
6

“动态”解析和文档树并不真正兼容。SAX 风格的解析器通常用于此目的(例如,Python 的标准xml.sax)。您基本上必须为各种事件(如 startElement、endElement 等)定义一个带有处理程序的类,并且解析器将在解析 XML 文件时调用这些方法。

于 2009-10-03T12:20:44.043 回答
5

PullDom做你想做的事。它从一个流(如 SAX)中读取 XML,然后为它的选定部分构建一个 DOM。

“PullDOM 是一个非常简单的 API,用于以流式(高效!)方式而不是单一树的方式处理 DOM 对象。”

于 2009-10-03T12:30:57.567 回答
0

这可以通过 elementtree 和增量解析实现: http ://effbot.org/zone/element-iterparse.htm#incremental-parsing

import xml.etree.cElementTree as etree
for event, elem in etree.iterparse(source):
    ...

比萨克斯更容易使用。

于 2012-01-01T14:10:47.280 回答
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)
于 2019-02-27T16:29:25.733 回答