如何迭代解析 Python 中的大型 XML 文件?
How to iteratively parse a large XML file in Python?
我需要处理一个大约 8Gb 的大 .XML 文件。
文件结构(简化)类似于以下内容:
<TopLevelElement>
<SomeElementList>
<Element>zzz</Element>
....and so on for thousands of rows
</SomeElementList>
<Records>
<RecordType1>
<RecordItem id="aaaa">
<SomeData>
<SomeMoreData NameType="xxx">
<NameComponent1>zzz</NameComponent1>
....
<AnotherNameComponent>zzzz</AnotherNameComponent>
</SomeMoreData>
</SomeData>
</RecordItem>
..... hundreds of thousands of items, some are quite large.
</RecordType1>
<RecordType2>
<RecordItem id="cccc">
...hundreds of thousands of RecordType2 elements, slightly different from RecordItems in RecordType1
</RecordItem>
</RecordType2>
</Records>
</TopLevelElement>
我需要提取 RecordType1 和 RecordType2 元素中的一些子元素。有条件决定哪些记录项需要处理哪些字段
需要提取。单个RecordItems不超过120k(有些有大量的文本数据,我不需要)。
这是代码。函数 get_all_records 接收以下输入:a) XML 文件的路径; b) 记录类别('RecordType1'或'RecordType2'); c) 选择什么名称组件
from xml.etree import cElementTree as ET
def get_all_records(xml_file_path, record_category, name_types, name_components):
context = ET.iterparse(xml_file_path, events=("start", "end"))
context = iter(context)
event, root = next(context)
all_records = []
for event, elem in context:
if event == 'end' and elem.tag == record_category and elem.attrib['action'] != 'del':
record_contents = get_record(elem, name_types=name_types, name_components=name_components, record_id=elem.attrib['id'])
if record_contents:
all_records += record_contents
root.clear()
return all_records
我已经对记录的数量进行了试验,代码在大约一分钟内很好地处理了 100k RecordItems(只有 Type1,到达 Type2 花费的时间太长了)。
尝试处理更多的记录(我拿了一百万),最终导致 ElementTree.py 中的 MemoryError。
所以我猜尽管 root.clear() 语句没有释放内存。
一个理想的解决方案是 RecordItems 一次读取一个,处理,然后从内存中丢弃,但我不知道该怎么做。
从 XML 的角度来看,两个额外的元素层(TopLevelElement 和 Records)似乎使任务复杂化。
我是 XML 和相应的 Python 库的新手,所以非常感谢详细的解释!
遍历巨大的 XML 文件总是很痛苦。
我将从头到尾检查所有过程,建议保持低内存但最大化解析速度的最佳做法。
首先不需要将ET.iterparse存储为变量。就像
一样遍历它
for event, elem in ET.iterparse(xml_file, events=("start", "end")):
这个迭代器是为,好吧...,迭代而创建的,除了当前标记之外,没有在内存中存储任何其他内容。此外,您不需要 root.clear()
使用这种新方法,只要您的硬盘 space 允许它存储巨大的 XML 文件,您就可以使用它。
您的代码应如下所示:
from xml.etree import cElementTree as ET
def get_all_records(xml_file_path, record_category, name_types, name_components):
all_records = []
for event, elem in ET.iterparse(xml_file_path, events=("start", "end")):
if event == 'end' and elem.tag == record_category and elem.attrib['action'] != 'del':
record_contents = get_record(elem, name_types=name_types, name_components=name_components, record_id=elem.attrib['id'])
if record_contents:
all_records += record_contents
return all_records
另外,请仔细考虑您需要存储all_records
的整个列表的原因。如果它仅用于在过程结束时写入 CSV 文件 - 这个原因还不够好,并且在扩展到更大的 XML 文件时可能会导致内存问题。
确保在该行发生时将每个新行写入 CSV,将内存问题转化为 none 问题。
P.S.
如果您需要在找到主标签之前存储多个标签,以便在您查看 XML 文件时解析此历史信息 - 只需将其存储在一些新变量中。每当 XML 文件中的未来数据使您返回到您知道已经出现的特定标签时,这就会派上用场。
我需要处理一个大约 8Gb 的大 .XML 文件。 文件结构(简化)类似于以下内容:
<TopLevelElement>
<SomeElementList>
<Element>zzz</Element>
....and so on for thousands of rows
</SomeElementList>
<Records>
<RecordType1>
<RecordItem id="aaaa">
<SomeData>
<SomeMoreData NameType="xxx">
<NameComponent1>zzz</NameComponent1>
....
<AnotherNameComponent>zzzz</AnotherNameComponent>
</SomeMoreData>
</SomeData>
</RecordItem>
..... hundreds of thousands of items, some are quite large.
</RecordType1>
<RecordType2>
<RecordItem id="cccc">
...hundreds of thousands of RecordType2 elements, slightly different from RecordItems in RecordType1
</RecordItem>
</RecordType2>
</Records>
</TopLevelElement>
我需要提取 RecordType1 和 RecordType2 元素中的一些子元素。有条件决定哪些记录项需要处理哪些字段 需要提取。单个RecordItems不超过120k(有些有大量的文本数据,我不需要)。
这是代码。函数 get_all_records 接收以下输入:a) XML 文件的路径; b) 记录类别('RecordType1'或'RecordType2'); c) 选择什么名称组件
from xml.etree import cElementTree as ET
def get_all_records(xml_file_path, record_category, name_types, name_components):
context = ET.iterparse(xml_file_path, events=("start", "end"))
context = iter(context)
event, root = next(context)
all_records = []
for event, elem in context:
if event == 'end' and elem.tag == record_category and elem.attrib['action'] != 'del':
record_contents = get_record(elem, name_types=name_types, name_components=name_components, record_id=elem.attrib['id'])
if record_contents:
all_records += record_contents
root.clear()
return all_records
我已经对记录的数量进行了试验,代码在大约一分钟内很好地处理了 100k RecordItems(只有 Type1,到达 Type2 花费的时间太长了)。 尝试处理更多的记录(我拿了一百万),最终导致 ElementTree.py 中的 MemoryError。 所以我猜尽管 root.clear() 语句没有释放内存。
一个理想的解决方案是 RecordItems 一次读取一个,处理,然后从内存中丢弃,但我不知道该怎么做。 从 XML 的角度来看,两个额外的元素层(TopLevelElement 和 Records)似乎使任务复杂化。 我是 XML 和相应的 Python 库的新手,所以非常感谢详细的解释!
遍历巨大的 XML 文件总是很痛苦。
我将从头到尾检查所有过程,建议保持低内存但最大化解析速度的最佳做法。
首先不需要将ET.iterparse存储为变量。就像
一样遍历它for event, elem in ET.iterparse(xml_file, events=("start", "end")):
这个迭代器是为,好吧...,迭代而创建的,除了当前标记之外,没有在内存中存储任何其他内容。此外,您不需要 root.clear()
使用这种新方法,只要您的硬盘 space 允许它存储巨大的 XML 文件,您就可以使用它。
您的代码应如下所示:
from xml.etree import cElementTree as ET
def get_all_records(xml_file_path, record_category, name_types, name_components):
all_records = []
for event, elem in ET.iterparse(xml_file_path, events=("start", "end")):
if event == 'end' and elem.tag == record_category and elem.attrib['action'] != 'del':
record_contents = get_record(elem, name_types=name_types, name_components=name_components, record_id=elem.attrib['id'])
if record_contents:
all_records += record_contents
return all_records
另外,请仔细考虑您需要存储all_records
的整个列表的原因。如果它仅用于在过程结束时写入 CSV 文件 - 这个原因还不够好,并且在扩展到更大的 XML 文件时可能会导致内存问题。
确保在该行发生时将每个新行写入 CSV,将内存问题转化为 none 问题。
P.S.
如果您需要在找到主标签之前存储多个标签,以便在您查看 XML 文件时解析此历史信息 - 只需将其存储在一些新变量中。每当 XML 文件中的未来数据使您返回到您知道已经出现的特定标签时,这就会派上用场。