Python XML 删除某些元素及其子元素,但保留特定元素及其子元素

2 投票
1 回答
2751 浏览
提问于 2025-04-17 17:38

我有一个非常大的 .xml 文件,我想从中提取出一小部分内容,制作一个新的 .xml 文件。我想指定一个属性(在我的例子中是 itemID),并给它几个特定的值,然后只保留那些包含这些 itemID 及其子元素的部分,其他的都去掉。

我的大 .xml 文件大概是这样的:

<?xml version='1.0' encoding='UTF-8'?>
<api version="2">
  <currentTime>2013-02-27 17:00:18</currentTime>
  <result>
    <rowset name="assets" key="itemID" columns="itemID,locationID,typeID,quantity,flag,singleton">
      <row itemID="1008551770576" locationID="31000559" typeID="17187" quantity="1" flag="0" singleton="1" rawQuantity="-1" />
      <row itemID="1008700753886" locationID="31000559" typeID="17187" quantity="1" flag="0" singleton="1" rawQuantity="-1" />
      <row itemID="1008700756994" locationID="31000559" typeID="17184" quantity="1" flag="0" singleton="1" rawQuantity="-1" />
      <row itemID="1008701224901" locationID="31000559" typeID="17186" quantity="1" flag="0" singleton="1" rawQuantity="-1" />
      <row itemID="1004072840841" locationID="31002238" typeID="17621" quantity="1" flag="0" singleton="1" rawQuantity="-1">
        <rowset name="contents" key="itemID" columns="itemID,typeID,quantity,flag,singleton">
          <row itemID="150571923" typeID="25863" quantity="2" flag="119" singleton="0" />
          <row itemID="188435728" typeID="3388" quantity="1" flag="119" singleton="0" />
          <row itemID="210122947" typeID="3419" quantity="4" flag="119" singleton="0" />
        </rowset>
      </row>
      <row itemID="1005279202146" locationID="31002238" typeID="17621" quantity="1" flag="0" singleton="1" rawQuantity="-1">
        <rowset name="contents" key="itemID" columns="itemID,typeID,quantity,flag,singleton">
          <row itemID="1004239962001" typeID="16275" quantity="49596" flag="4" singleton="0" />
          <row itemID="1005364142068" typeID="4246" quantity="156929" flag="4" singleton="0" />
          <row itemID="1005624252854" typeID="4247" quantity="93313" flag="4" singleton="0" />
        </rowset>
      </row>
      <row itemID="1004388226389" typeID="648" quantity="1" flag="0" singleton="1" rawQuantity="-1">
        <rowset name="contents" key="itemID" columns="itemID,typeID,quantity,flag,singleton">
          <row itemID="1004388228218" typeID="31119" quantity="1" flag="92" singleton="1" rawQuantity="-1" />
          <row itemID="1004388701243" typeID="31119" quantity="1" flag="94" singleton="1" rawQuantity="-1" />
          <row itemID="1004388701485" typeID="31119" quantity="1" flag="93" singleton="1" rawQuantity="-1" />
          <row itemID="1009147502645" typeID="51" quantity="1" flag="5" singleton="1" rawQuantity="-1" />
        </rowset>
      </row>
    </rowset>
  </result>
  <cachedUntil>2013-02-27 23:00:18</cachedUntil>
</api>

这个文件大约有九万行,大小大约是 9 兆。

注意,这里有 itemID,有些项目类型可以(但不一定)包含更多的子项目,而这些子项目也有自己的 itemID。我想提取几个特定的 itemID 及其子项目,其他的都不需要。

我使用了来自 这个回答 的代码,离我想要的结果很接近。它几乎完美,只是没有包括我所用的 itemID 的子项目。

我的代码看起来是这样的:

import lxml.etree as le

##Take this big .xml file and pull out just the parts we want then write those to a new .xml file##
with open(filename,'r') as f:
    doc=le.parse(f)
    for elem in doc.xpath('//*[attribute::itemID]'):
        if elem.attrib['itemID']=='1004072840841':
            elem.attrib.pop('itemID')
        else:
            parent=elem.getparent()
            parent.remove(elem)
    print(le.tostring(doc))

这是输出结果的样子:

<api version="2">
  <currentTime>2013-03-01 21:46:52</currentTime>
  <result>
    <rowset name="assets" key="itemID" columns="itemID,locationID,typeID,quantity,flag,singleton">
      <row locationID="31002238" typeID="17621" quantity="1" flag="0" singleton="1" rawQuantity="-1">
        <rowset name="contents" key="itemID" columns="itemID,typeID,quantity,flag,singleton">
          </rowset>
      </row>
      </rowset>
  </result>
  <cachedUntil>2013-03-02 03:46:53</cachedUntil>
</api>

我希望它看起来像这样:

<api version="2">
  <currentTime>2013-03-01 21:46:52</currentTime>
  <result>
    <rowset name="assets" key="itemID" columns="itemID,locationID,typeID,quantity,flag,singleton">
      <row locationID="31002238" typeID="17621" quantity="1" flag="0" singleton="1" rawQuantity="-1">
        <rowset name="contents" key="itemID" columns="itemID,typeID,quantity,flag,singleton">
          <row itemID="150571923" typeID="25863" quantity="2" flag="119" singleton="0" />
          <row itemID="188435728" typeID="3388" quantity="1" flag="119" singleton="0" />
          <row itemID="210122947" typeID="3419" quantity="4" flag="119" singleton="0" />
        </rowset>
      </row>
      </rowset>
  </result>
  <cachedUntil>2013-03-02 03:46:53</cachedUntil>
</api>

我对代码理解得不够深,不知道该怎么改才能把我搜索的 itemID 的子项目也包括进去。此外,理想情况下,我希望能输入多个 itemID,这样就能保留这些 itemID 及其子项目,其他的都去掉。这意味着需要保留 itemID=[number] 这一行属性,这样我在使用这个 xml 文件时就可以用 xPath 来引用特定的 itemID 及其子项目。

所以我主要想问的是,怎么才能在生成的 .xml 文件中包含我搜索的 itemID 的子项目。其次,我想知道怎么能同时处理多个 itemID(这样生成的 .xml 文件就只保留这些 itemID 及其子项目)。

更新:丑陋的解决方案

我发现 elem.attrib.pop('itemID') 这一部分是用来删除 itemID 的,因为我想保留多个 itemID 及其子项目,所以我需要保留这一部分,于是我把它去掉了。我试图找到一种方法来跳过我正在搜索的 itemID 行的子项目,最后我想到的办法是给每个子项目加一个标记属性,然后再搜索并删除所有没有这个属性的项目。由于我不需要这个标记属性,所以我就用它来实现这个目的(因为尝试引入新属性时遇到了键错误)。仅仅给子项目加标记是不够的,我还得给子项目的子项目也加标记。

这是我的丑陋解决方案:

with open(filename,'r') as f:
    doc=le.parse(f)
    for elem in doc.xpath('//*[attribute::itemID]'):
        if elem.attrib['itemID']=='1004072840841' or elem.attrib['itemID']=='1005279202146': # this or statement lets me get a resulting .xml file that has two itemIDs and their children
            elem.attrib['flag']='Keep'
            for child in elem.iterchildren():
                child.attrib['flag']='Keep'
                for c in child.iterchildren():
                    c.attrib['flag']='Keep'
        else:
            pass
    for e in doc.xpath('//*[attribute::flag]'):
        if e.attrib['flag']!='Keep':
            parent=e.getparent()
            parent.remove(e)
        else:
            pass
    print(le.tostring(doc))
    ##This part writes the pruned down .xml to a file##
    with open('test.xml', 'w') as t:
        for line in le.tostring(doc):
            t.write(line)
    t.close

这个丑陋的解决方案涉及大量的数据迭代,我怀疑这远不是最有效的方式,但它确实能工作。

1 个回答

3

你想要的内容不是很明确,不过这段代码可以产生你所说的输出结果:

from lxml import etree as ET

def filter_by_itemid(doc, idlist):
    rowset = doc.xpath("/api/result/rowset[@name='assets']")[0]
    for elem in rowset.getchildren():
        if int(elem.get("itemID")) not in idlist:
            rowset.remove(elem)
    return doc

doc = ET.parse("test.xml")
filter_by_itemid(doc, [1004072840841])

print(ET.tostring(doc))

撰写回答