我有一个超过1GB的xml文件,我希望通过创建新的xml文件或重写现有的xml文件来删除父标记中不需要的子文件来减小文件的大小。如何通过python实现这一点,因为文件很大,简单的解析tree = ElementTree.parse(xmlfile)
将不起作用。
在每个父标记TasksReportNode
的文件中,我希望只有具有值{0的TableRow
属性的子RowCount
,并拒绝该父标记的所有其他子标记(表行)。
示例XML代码:
<TasksReportNode Name="Task15">
<TableData NumRows="97" NumColumns="15">
<TableRow RowCount="0">
<TableColumn Name="Task"><![CDATA[ Task15 [GET - /PULSEV31/appView/projectFeedHidden.jsp - 200]]]></TableColumn>
<TableColumn Name="Status"><![CDATA[Success]]></TableColumn>
<TableColumn Name="Successful"><![CDATA[96]]></TableColumn>
<TableColumn Name="Failed"><![CDATA[0]]></TableColumn>
<TableColumn Name="Timedout"><![CDATA[0]]></TableColumn>
<TableColumn Name="Total"><![CDATA[96]]></TableColumn>
<TableColumn Name="Min(ms)"><![CDATA[15]]></TableColumn>
<TableColumn Name="Avg(ms)"><![CDATA[24.20]]></TableColumn>
<TableColumn Name="Avg-90%(ms)"><![CDATA[54.55]]></TableColumn>
<TableColumn Name="90%ile(ms)"><![CDATA[89.98]]></TableColumn>
<TableColumn Name="95%ile(ms)"><![CDATA[95.24]]></TableColumn>
<TableColumn Name="99%ile(ms)"><![CDATA[99.45]]></TableColumn>
<TableColumn Name="Max(ms)"><![CDATA[94]]></TableColumn>
<TableColumn Name="Std. Dev."><![CDATA[15.74]]></TableColumn>
<TableColumn Name="Bytes Recd(KB)"><![CDATA[192]]></TableColumn>
</TableRow>
<TableRow RowCount="1">
<TableColumn Name="Task"><![CDATA[ VirtualUser1]]></TableColumn>
<TableColumn Name="Status"><![CDATA[Success]]></TableColumn>
<TableColumn Name="Successful"><![CDATA[1]]></TableColumn>
<TableColumn Name="Failed"><![CDATA[0]]></TableColumn>
<TableColumn Name="Timedout"><![CDATA[0]]></TableColumn>
<TableColumn Name="Total"><![CDATA[1]]></TableColumn>
<TableColumn Name="Min(ms)"><![CDATA[934]]></TableColumn>
<TableColumn Name="Avg(ms)"><![CDATA[934.00]]></TableColumn>
<TableColumn Name="Avg-90%(ms)"><![CDATA[950.00]]></TableColumn>
<TableColumn Name="90%ile(ms)"><![CDATA[1,000.50]]></TableColumn>
<TableColumn Name="95%ile(ms)"><![CDATA[1,000.50]]></TableColumn>
<TableColumn Name="99%ile(ms)"><![CDATA[1,000.50]]></TableColumn>
<TableColumn Name="Max(ms)"><![CDATA[934]]></TableColumn>
<TableColumn Name="Std. Dev."><![CDATA[0.00]]></TableColumn>
<TableColumn Name="Bytes Recd(KB)"><![CDATA[0]]></TableColumn>
</TableData>
<TableData NumRows="1" NumColumns="2">
<TableRow RowCount="0">
<TableColumn Name="Response Time Interval (ms)"><![CDATA[0 - 99]]></TableColumn>
<TableColumn Name="Frequency"><![CDATA[96]]></TableColumn>
</TableRow>
</TableData>
</TasksReportNode>
<TasksReportNode Name="Task16">
<TableData NumRows="97" NumColumns="15">
<TableRow RowCount="0">
<TableColumn Name="Task"><![CDATA[ Task16 [GET - /PULSEV31/appView/projectCommentHidden.jsp - 200]]]></TableColumn>
<TableColumn Name="Status"><![CDATA[Success]]></TableColumn>
<TableColumn Name="Successful"><![CDATA[96]]></TableColumn>
<TableColumn Name="Failed"><![CDATA[0]]></TableColumn>
<TableColumn Name="Timedout"><![CDATA[0]]></TableColumn>
<TableColumn Name="Total"><![CDATA[96]]></TableColumn>
<TableColumn Name="Min(ms)"><![CDATA[15]]></TableColumn>
<TableColumn Name="Avg(ms)"><![CDATA[22.73]]></TableColumn>
<TableColumn Name="Avg-90%(ms)"><![CDATA[54.55]]></TableColumn>
<TableColumn Name="90%ile(ms)"><![CDATA[90.93]]></TableColumn>
<TableColumn Name="95%ile(ms)"><![CDATA[96.25]]></TableColumn>
<TableColumn Name="99%ile(ms)"><![CDATA[100.50]]></TableColumn>
<TableColumn Name="Max(ms)"><![CDATA[109]]></TableColumn>
<TableColumn Name="Std. Dev."><![CDATA[14.76]]></TableColumn>
<TableColumn Name="Bytes Recd(KB)"><![CDATA[192]]></TableColumn>
</TableRow>
</TableData>
</TasksReportNode>
以下是我的尝试:
xmL = 'F:\\Reports\\Logs\\Result_TG1_V16.xml'
context = etree.iterparse(xmL, events=("start", "end"),)
for event, element in context:
if element.tag == 'TasksReportNode':
for child1 in element:
for child2 in child1:
if child2.get("RowCount") == "0":
for child3 in child2:
print(child3.tag, child3.text)
element.clear() # discard the element
del context
现在我们拥有值为'0'的所有RowCount
,并且可以添加到父级,留下所有其他兄弟姐妹。
答案 0 :(得分:3)
我建议使用lxml
,因为它在大多数方面比stdlib xml.ElementTree
更有效。
您不应该尝试整个整个文档解析,因为它太大了,但应该迭代地接近源文档。
在lxml
页面Event driven parsing
有两种选择:
etree.iterparse
我个人更喜欢etree.iterparse
,因为它以更方便的方式为您提供已解析的元素。但是你不要忘记对已处理的部件进行清理工作,否则与一次解析整个文档相比,你不会节省任何内存。
编辑:添加了真实的例子
示例比大量理论更好。这是我的尝试:
from lxml import etree
# fname = "large.xml" # 78 MB
fname = "verylarge.xml" # 773 MB
toremove = []
for event, element in etree.iterparse(fname):
if element.tag == "TableRow":
if element.attrib["RowCount"] != "0":
element.clear()
# removing current element causes segmentation fault
# element.getparent().remove(element)
toremove.append(element)
if element.tag == "TableData":
for rowelm in toremove:
element.remove(rowelm)
toremove = []
# last processed element is the root one
with open("out.xml", "w") as f:
f.write(etree.tostring(element))
为了测试性能,我拿了你的大样本文件(73 MB),重复内部部分10次,得到了 773 MB大型XML文件并进行处理。
处理耗时24秒(带有4 GB RAM的zenbook core i7),生成的文件大小为4.7 MB。
iterparse
仅提供&#34;结束&#34;事件,在完全解析某个元素时触发。
此解决方案使用的事实是,即使使用iterparse
,元素也会保留在内存中。这是用的
在以下地方:
element.clear()
)并被删除
(element.remove(rowelm)
)。 clear()
清除元素的内部内容,但是元素
还存在。 remove()
适用于父元素,并从中删除内部部分。element
是根目录。它还在记忆中,
所以我可以将它作为字符串写入文件。要在remove()
元素时必须要小心。试图从父母那里删除元素
当前迭代元素导致分段错误的那一刻。出于这个原因,代码
等待"TableRow"
元素remove()
,直到我们完成对父TableData
元素的解析。
变量toremove
用于收集所有"TableRow"
个元素,并在父级时使用
"TableData"
元素已完全解析。请注意,remove()
仅适用于真实元素
父母,所以我们一定会在适当的时候做。
对于更大的文件,此解决方案将受到生成的XML文档大小的限制 保留在内存中,直到完成源XML的修剪。
对于这样的场景,我们必须在解析和删除期间使用写出输出
内存中已经处理过的所有元素。在实践中,你必须写出来
&#34;打开XML元素&#34;部分(如"<TaskReportSummary att="a" otheratt="bb"
)当&#34;开始&#34;事件
会出现,并在&#34; end&#34;中写入clossing XML元素部分"/>"
事件
答案 1 :(得分:-1)
以下是我的尝试:
xmL = 'F:\\Reports\\Logs\\Result_TG1_V16.xml'
context = etree.iterparse(xmL, events=("start", "end"),)
for event, element in context:
if element.tag == 'TasksReportNode':
for child1 in element:
for child2 in child1:
if child2.get("RowCount") == "0":
for child3 in child2:
print(child3.tag, child3.text)
element.clear() # discard the element
del context
现在我们将所有 RowCount 的值设置为“0”,并且可以添加到父级,留下所有其他兄弟。