如何为SAX解析器构建正确的生成器函数

-4 投票
2 回答
1011 浏览
提问于 2025-04-16 06:24

我有一个35.5Mb的.XLSM文件。当实际可用的内容变多时,DOM解析器像element tree这样的工具会因为内存耗尽而运行很久,最后崩溃。

不过,当我使用SAX解析器时,ContentHandler似乎只能把行数据存到一个临时文件里。这让我有点烦,因为解析器和主应用程序本来可以有一个简单的协作关系,每解析一行就可以把它交给应用程序。

看起来下面的做法是行不通的。

def gen_rows_from_xlsx( someFile ):
    myHandler= HandlerForXLSX()
    p= xml.sax.makeParser()
    p.setContentHandler( myHandler, some_kind_of_buffer )
    for row in some_kind_of_buffer.rows():
        p.parse() # Just enough to get to the ContentHandler's "buffer.put()"
        yield row

定期,HandlerForXLSX会调用some_kind_of_buffer.put( row )把一行数据放进缓冲区。这一行数据应该通过some_kind_of_buffer.rows()来获取。

如果SAX解析器和gen_rows_from_xslx()之间能有一个简单的协作关系,那就太好了。

我是不是忽略了什么生成器函数的技巧,可以让我把SAX当作某种协作程序来用?

难道唯一的办法就是创建一个SAX解析线程,然后用Queue来获取解析器生成的行数据吗?

还是说更简单的办法是直接在SAX解析器里创建一个临时文件,然后通过生成器来输出这些对象?

相关链接: 懒惰的SAX XML解析器,支持暂停/恢复

2 个回答

1

看起来你可以用 IncrementalParser 接口来实现这个功能?大概可以这样做:

def gen_rows_from_xlsx(someFile):
    buf = collections.deque()
    myHandler = HandlerForXLSX(buf)
    p = xml.sax.make_parser()
    p.setContentHandler(myHandler)
    with open(someFile) as f:
        while True:
            d = f.read(BLOCKSIZE)
            if not d: break
            p.feed(d)
            while buf: yield buf.popleft()
    p.close()

如果你想用 parse 来实现这个,你就得在多个堆栈帧之间进行 yield,但这在 Python 中是根本不支持的。

5

我有一个35.5Mb的.XLSM文件。当里面实际可用的内容展开后,DOM解析器像element tree一样会消耗大量内存,导致运行时间变得非常长。

我不太明白这个意思。你应该使用的东西有:

import xml.etree.cElementTree as ET

ET.iterparse(sourcefile) # sourcefile being a cStringIO.StringIO instance holding your worksheet XML document

element.clear() # leave only scorched earth behind you

这篇文章展示了如何使用iterparseclear

举个例子:把一个100Mb的XLSX文件(里面有两个工作表,每个大约有16000行和200列)加载到xlrd对象模型中:

耗时大约4分钟(在一台老旧的笔记本电脑上运行,配置是2 GHz单核,操作系统是Windows XP,使用的是Python 2.7)。内存使用量最高大约是300Mb,大部分是输出数据,而不是元素树。

撰写回答