如何使用 Python 对大 XML 文件执行查询?

Posted

技术标签:

【中文标题】如何使用 Python 对大 XML 文件执行查询?【英文标题】:How to perform a query on Big XML file using Python? 【发布时间】:2016-05-02 19:26:43 【问题描述】:

我有一个 7 GB 的 XML 文件,它是关于一家公司的所有交易的,我只想过滤去年(2015 年)的记录。 一个文件的结构是:

<Customer>
<Name>A</Name>
<Year>2015<Year>
</Customer>

我也有它的 DTD 文件。 我不知道如何将这些数据过滤到文本文件中。 有没有这方面可以使用的教程或库。

欢迎!

【问题讨论】:

Prune some elements from large xml file的可能重复 您可以使用多少内存来处理这个 XML 文件?根据这个数字,可能还有其他选项可用吗? 【参考方案1】:

由于您的数据很大,我假设您已经决定无法将全部数据加载到内存中。这将是使用 DOM 样式(文档对象模型)解析器的方法。而且您实际上已将您的问题标记为“SAX”(XML 的简单 API),这进一步意味着您知道您需要一种非内存方法。

想到了两种方法:

使用 grep

有时对于 XML,使用纯文本处理工具会很有用。 grep 将允许您将 XML 文档作为纯文本过滤并找到 2015 的出现:

$ grep -B 2 -A 1 "<Year>2015</Year>"

-B-A 选项指示 grep 在匹配项周围打印一些上下文。

然而,这种方法只有在您的 XML 也是精确结构化的纯文本时才有效,而它(作为 XML)绝对不需要。也就是说,您的 XML 可以有空格的任意组合(或根本没有空格)并且在语义上仍然相同,但 grep 方法取决于精确的空格排列。

SAX

因此,更可靠的非内存方法是使用 SAX。 SAX 实现在概念上非常简单,但编写起来有点乏味。本质上,您必须重写一个类,该类提供在源 XML 文档中发生某些“事件”时调用的方法。在标准库的xml.sax.handler模块中,这个类是ContentHandler。这些方法包括:

开始元素 结束元素 字符

然后,您的覆盖方法决定如何处理这些事件。在startElement(name, attrs) 的典型实现中,您可以测试name 参数以确定元素的标记名称是什么。然后,您可能会维护已输入的元素堆栈。当endElement(name) 发生时,您可能会从该堆栈中弹出顶部元素,并可能对已完成的元素进行一些处理。 characters(content) 在源文档中遇到字符数据时发生。在这种方法中,您可以考虑构建一个字符数据字符串,然后在遇到endElement 时对其进行处理。

因此,对于您的特定任务,这样的事情可能会起作用:

from xml.sax import parse
from xml.sax.handler import ContentHandler

class filter2015(ContentHandler):
    def __init__(self):
        self.elements = []          # stack of elements
        self.char_data = u''        # string buffer
        self.current_customer = u'' # name of customer
        self.current_year = u''

    def startElement(self, name, attrs):
        if name == u'Name':
            self.elements.append(u'Name')
        if name == u'Year':
            self.elements.append(u'Year')

    def characters(self, chars):
        if len(self.elements) > 0 and self.elements[-1] in [u'Name', u'Year']:
            self.char_data += chars

    def endElement(self, name):
        self.elements.pop() if len(self.elements) > 0 else None

        if name == u'Name':
            self.current_customer = self.char_data
            self.char_data = ''
        if name == u'Year':
            self.current_year = self.char_data
            self.char_data = ''

        if name == 'Customer':
            # wait to check the year until the Customer is closed
            if self.current_year == u'2015':
                print 'Found:', self.current_customer

            # clear the buffers now that the Customer is finished
            self.current_year = u''
            self.current_customer = u''
            self.char_data = u''

source = open('test.xml')
parse(source, filter2015())

【讨论】:

很好的答案,:) 现在如果我想将结果保存到 csv 文件,它是否会溢出内存,因为仍然需要写操作?! 是否可以将 XPath 与 SAX 一起使用?! 我建议编写大型 CSV 文件应该是另一个问题的主题(可能已经回答)。基本上,小心使用flush 可能是您所需要的。 您是否可以将 XPath 与 SAX 一起使用只是您正在使用的 XPath 处理器的一个特性。 Java/C# Saxon XSLT 处理器实现 XPath 1.0 和 2.0(作为符合 XSLT 2.0 实现)并使用 SAX API 编写。 根据上面的代码,我有一个问题,如果有两个名为[year]的标签怎么办。如何检索这两个标签。【参考方案2】:

看看这个问题。它会让你作为生成器与之交互:

python: is there an XML parser implemented as a generator?

您想使用生成器,这样您就不会先将整个文档加载到内存中。

具体来说:

import xml.etree.cElementTree as ET

for event, element in ET.iterparse('huge.xml'):
    if event == 'end' and element.tag == 'ticket':
        #process ticket...

来源:http://enginerds.craftsy.com/blog/2014/04/parsing-large-xml-files-in-python-without-a-billion-gigs-of-ram.html

【讨论】:

这不是一个好的解决方案,因为我也有DTD文件,它描述了一些字段类型,特别是解析CDATA很重要,我得到以下错误:------- ----------- 名称文件“”,行未知 ParseError:未定义实体 ö:第 47 行,第 18 列 -------------- 这个ElementTree的使用原理和SAX基本一样,只是API更简洁。我实际上可能更喜欢这个而不是我的 SAX 答案:-)

以上是关于如何使用 Python 对大 XML 文件执行查询?的主要内容,如果未能解决你的问题,请参考以下文章

对大文件有效的轻量级 XML 解析器?

如何在 Python 中对大文本文件流进行过滤和排序

如何使用 python 从 .sql (mysqldump) 文件中执行“创建表查询”

如何在shell 中将sql语句执行结果写入日志中

mybatis如何通过接口查找对应的mapper.xml及方法执行详解

MapReuce中对大数据处理最合适的数据格式是什么?