Python:如何处理大型 XML 文件,其中包含 1 个根目录中的大量子文件

Python: How to process large XML file with a lot of childs in 1 root

我有 XML 个数据结构类似于

的文件
<report>
  <table>
    <detail name="John" surname="Smith">
    <detail name="Michael" surname="Smith">
    <detail name="Nick" surname="Smith">
    ... {a lot of <detail> elements}
  </table>
</report>

我需要检查元素是否具有 'name'=='surname'.

属性

XML 文件大于 1 GB,我尝试 etree.parse(file).

时出错

如何使用 Python 和 LXML 逐个处理元素?

可以使用iterparse方法,该方法用于处理大型xml文件。但是,您的文件结构特别简单。使用 iterparse 会不必要地复杂。

我将在一个脚本中提供两个答案。我通过展示如何使用 lxml 解析 xml 中的行来直接回答你的问题,我提供了我认为使用正则表达式可能是更好答案的内容。

代码读取 xml 中的每一行并忽略那些不以 'try ... except. 开头的行。当脚本找到这样的一行时,它会将它从 lxml 传递给 etree 进行解析,然后显示该行的属性。之后它使用正则表达式解析出相同的属性并显示它们。

我强烈怀疑正则表达式会更快。

>>> from lxml import etree
>>> report = '''\
... <report>
...     <table>
...         <detail name="John" surname="Smith">
...         <detail name="Michael" surname="Smith">
...         <detail name="Nick" surname="Smith">
...     </table>
... </report>'''
>>> import re
>>> re.search(r'name="([^"]*)"\s+surname="([^"]*)', line).groups()
('John', 'Smith')
>>> for line in report.split('\n'):
...     if line.strip().startswith('<detail'):
...         tree = etree.fromstring(line.replace('>', '/>'))
...         tree.attrib['name'], tree.attrib['surname']
...         re.search(r'name="([^"]*)"\s+surname="([^"]*)', line).groups()
...         
('John', 'Smith')
('John', 'Smith')
('Michael', 'Smith')
('Michael', 'Smith')
('Nick', 'Smith')
('Nick', 'Smith')

基本上有三种标准的解析方法XML:

  • 在内存中构建 Document Object Model (DOM) - 你将整个文档加载到内存中并且可以沿着树任意行走
  • 编写推送 SAX 解析器 - 文档处理成为一系列事件(开始标记、文本、结束标记、评论、处理指令等),您可以订阅其中的几个.您注册回调和 运行 解析。文档一直读到最后,但解析器并未构建整个文档的内部表示。
  • 写一个拉动的StAX解析器——解析器流式传输不同的事件,你按顺序处理所有的事件,但可以随时停止(对于解析开头的XML-元数据很有用文档并停止处理)

lxml 是对libxml C 库的绑定,它是DOM 的实现,iterparse 方法似乎是StAX 方法的实现。 SAX 解析器内置于 python 本身:https://docs.python.org/3.6/library/xml.sax.html

对于您的情况,标准方法是使用 SAX 解析器。

考虑 iterparse,它允许您在构建树时处理元素。下面检查 name 属性是否等同于 surname 属性。使用 if 块进一步处理,如有条件地将值附加到列表:

import xml.etree.ElementTree as et

data = []
path = "/path/to/source.xml"

# get an iterable
context = et.iterparse(path, events=("start", "end"))

# turn it into an iterator
context = iter(context)

# get the root element
ev, root = next(context)

for ev, el in context:
    if ev == 'start' and el.tag == 'detail':
        print(el.attrib['name'] == el.attrib['surname'])
        data.append([el.attrib['name'], el.attrib['surname']])
        root.clear()

print(data)
# False
# False
# False

# [['John', 'Smith'], ['Michael', 'Smith'], ['Nick', 'Smith']]