如何使用XML SAX解析器读写大型XML文件?

10

我正在尝试使用SAX解析器从下面的示例XML文档中删除所有project1节点(以及它们的子元素)(原始文档约为30 GB)。可以将修改后的文件作为单独的文件,也可以在行内进行编辑。

sample.xml

<ROOT>
    <test src="http://dfs.com">Hi</test>
    <project1>This is old data<foo></foo></project1>
    <bar>
        <project1>ty</project1>
        <foo></foo>
    </bar>
</ROOT>

这是我的尝试。
解析器.py
from xml.sax.handler import ContentHandler
import xml.sax

class MyHandler(xml.sax.handler.ContentHandler):
    def __init__(self, out_file):
        self._charBuffer = []
        self._result = []
        self._out = open(out_file, 'w')

    def _createElement(self, name, attrs):
        attributes = attrs.items()
        if attributes:
            out = ''
            for key, value in attributes:
                out += ' {}={}'.format(key, value)
            return '<{}{}>'.format(name, out)
        return '<{}>'.format(name)


    def _getCharacterData(self):
        data = ''.join(self._charBuffer).strip()
        self._charBuffer = []
        self._out.write(data.strip()) #remove strip() if whitespace is important

    def parse(self, f):
        xml.sax.parse(f, self)

    def characters(self, data):
        self._charBuffer.append(data)

    def startElement(self, name, attrs):
        if not name == 'project1': 
            self._result.append({})
            self._out.write(self._createElement(name, attrs))

    def endElement(self, name):
        if not name == 'project1': self._result[-1][name] = self._getCharacterData()

MyHandler('out.xml').parse("sample.xml")

我不能去上班。


将数据处理为文本有什么问题吗?简单来说:检查标志,是否关闭,抓取行,是否为project1,提高标志,写入/追加或不写入,重复... 这只是一个策略概述。 - user5169597
但是这种方法会导致将整个文件加载到内存中。 - Avinash Raj
这是第一个任务,后面还有很多任务需要处理修改XML,例如修改特定元素的属性等。因此我认为最好使用基于SAX的答案。 - Avinash Raj
@cco 我看到使用iterparse的解决方案,它只执行解析工作,但是我没有找到关于连续解析和写入的任何解决方案。 - Avinash Raj
4
使用文本方式处理XML的问题是众所周知的——当XML出现完全合理的变化时,这种处理方法会导致脆弱的解决方案。请不要提出这样的建议。谢谢。 - kjhughes
显示剩余5条评论
1个回答

6
你可以使用xml.sax.saxutils.XMLFilterBase实现来过滤出你的project1节点。
不需要手工组装xml字符串,你可以使用xml.sax.saxutils.XMLGenerator
以下是Python3代码,如果需要Python2请调整super
from xml.sax import make_parser
from xml.sax.saxutils import XMLFilterBase, XMLGenerator


class Project1Filter(XMLFilterBase):
    """This decides which SAX events to forward to the ContentHandler

    We will not forward events when we are inside any elements with a
    name specified in the 'tags_names_to_exclude' parameter
    """

    def __init__(self, tag_names_to_exclude, parent=None):
        super().__init__(parent)

        # set of tag names to exclude
        self._tag_names_to_exclude = tag_names_to_exclude

        # _project_1_count keeps track of opened project1 elements
        self._project_1_count = 0

    def _forward_events(self):
        # will return True when we are not inside a project1 element
        return self._project_1_count == 0

    def startElement(self, name, attrs):
        if name in self._tag_names_to_exclude:
            self._project_1_count += 1

        if self._forward_events():
            super().startElement(name, attrs)

    def endElement(self, name):
        if self._forward_events():
            super().endElement(name)

        if name in self._tag_names_to_exclude:
            self._project_1_count -= 1

    def characters(self, content):
        if self._forward_events():
            super().characters(content)

    # override other content handler methods on XMLFilterBase as neccessary


def main():
    tag_names_to_exclude = {'project1', 'project2', 'project3'}
    reader = Project1Filter(tag_names_to_exclude, make_parser())

    with open('out-small.xml', 'w') as f:
        handler = XMLGenerator(f)
        reader.setContentHandler(handler)
        reader.parse('input.xml')


if __name__ == "__main__":
    main()

很好,即使有空行。想要检查时间成本。 - user5169597
大约700MB的文件慢了26秒。 - user5169597
嗨@Jeremy..你的解决方案对我有用..请问我如何对节点列表(例如project1project2project3)执行相同的操作? - Avinash Raj
如果名称在 ['project1','project2','project3'] 中:self._project_1_count += 1 对于 endElement 方法也是如此。 - user5169597
@AvinashRaj 我已经更新了代码,以排除一组标签名称。 - Jeremy Allen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接