修剪大型xml文件中的一些元素

时间:2016-03-17 10:47:05

标签: python xml

我有一个超过1GB的xml文件,我希望通过创建新的xml文件或重写现有的xml文件来删除父标记中不需要的子文件来减小文件的大小。如何通过python实现这一点,因为文件很大,简单的解析tree = ElementTree.parse(xmlfile)将不起作用。

XML file

在每个父标记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,并且可以添加到父级,留下所有其他兄弟姐妹。

2 个答案:

答案 0 :(得分:3)

我建议使用lxml,因为它在大多数方面比stdlib xml.ElementTree更有效。

您不应该尝试整个整个文档解析,因为它太大了,但应该迭代地接近源文档。

lxml页面Event driven parsing

有两种选择:

  • etree.iterparse
  • 使用自定义解析器,触发类似SAX的事件

我个人更喜欢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,元素也会保留在内存中。这是用的 在以下地方:

  • 在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”,并且可以添加到父级,留下所有其他兄弟。