合并具有相似父节点的子节点,xml,python

3 投票
2 回答
1940 浏览
提问于 2025-04-18 00:42

我有一个这样的xml文件:

<root>
    <article_date>09/09/2013
    <article_time>1
        <article_name>aaa1</article_name>
        <article_link>1aaaaaaa</article_link>
    </article_time>
    <article_time>0
        <article_name>aaa2</article_name>
        <article_link>2aaaaaaa</article_link>
    </article_time>
    <article_time>1
        <article_name>aaa3</article_name>
        <article_link>3aaaaaaa</article_link>
    </article_time>
    <article_time>0
        <article_name>aaa4</article_name>
        <article_link>4aaaaaaa</article_link>
    </article_time>
    <article_time>1
        <article_name>aaa5</article_name>
        <article_link>5aaaaaaa</article_link>
    </article_time>
    </article_date>
</root>

我想把它转换成下面这个文件:

<root>
    <article_date>09/09/2013
    <article_time>1
        <article_name>aaa1+aaa3+aaa5</article_name>
        <article_link>1aaaaaaa+3aaaaaaa+5aaaaaaa</article_link>
    </article_time>
    <article_time>0
        <article_name>aaa2+aaa4</article_name>
        <article_link>2aaaaaaa+4aaaaaaa</article_link>
    </article_time>
</root>

我该如何用python来实现这个转换呢?

我打算这样来完成这个任务:

  1. 遍历所有的标签
  2. 形成一个字典,字典的键可以是0或1,值是相应的内容
  3. 对于字典中的每个元素,找到所有的子节点,比如和,然后把它们添加进去

基于这个思路,我写了以下代码来实现这个功能(顺便说一下,我现在在把元素添加到字典这部分遇到了一些困难,但我会解决这个问题的):

def parse():
list_of_inique_timestamps=[]
text_to_merge=""
tree=et.parse("~/Documents/test1.xml")
root=tree.getroot()
for children in root:
    print children.tag, children.text
    for child in children:
        print (child.tag,int(child.text))
        if not child.text in list_of_inique_timestamps:
            list_of_inique_timestamps.append(child.text)
print list_of_inique_timestamps

2 个回答

1

我会尽量写多一点(根据我的时间和知识),但我把这个变成了社区维基,方便其他人来帮忙。

我建议使用 xmlBeautifulSoup 这两个库来处理这个问题。我会用BeautifulSoup,因为我现在不知道为什么xml用不了。

首先,我们来准备一下:

>>> import bs4
>>> soup = bs4.BeautifulSoup('''<root>
...     <article_date>09/09/2013
...     <article_time>1
...         <article_name>aaa1</article_name>
...         <article_link>1aaaaaaa</article_link>
...     </article_time>
...     <article_time>0
...         <article_name>aaa2</article_name>
...         <article_link>2aaaaaaa</article_link>
...     </article_time>
...     <article_time>1
...         <article_name>aaa3</article_name>
...         <article_link>3aaaaaaa</article_link>
...     </article_time>
...     <article_time>0
...         <article_name>aaa4</article_name>
...         <article_link>4aaaaaaa</article_link>
...     </article_time>
...     <article_time>1
...         <article_name>aaa5</article_name>
...         <article_link>5aaaaaaa</article_link>
...     </article_time>
... </root>''')

这段代码只是生成了你xml的内部表示。我们可以用 find_all 方法来获取所有的文章时间。

>>> children = soup.find_all('article_time')
>>> children
[<article_time>1
        <article_name>aaa1</article_name>
<article_link>1aaaaaaa</article_link>
</article_time>, <article_time>0
        <article_name>aaa2</article_name>
<article_link>2aaaaaaa</article_link>
</article_time>, <article_time>1
        <article_name>aaa3</article_name>
<article_link>3aaaaaaa</article_link>
</article_time>, <article_time>0
        <article_name>aaa4</article_name>
<article_link>4aaaaaaa</article_link>
</article_time>, <article_time>1
        <article_name>aaa5</article_name>
<article_link>5aaaaaaa</article_link>
</article_time>]

接下来,我们要定义一个关键字,用来判断哪些父节点是“相似”的。我们来写一个 key 函数,指定要查看每个子节点的哪个部分。我们先来了解一下每个子节点的结构。

>>> children[0].contents
[u'1\n        ', <article_name>aaa1</article_name>, u'\n', <article_link>1aaaaaaa</article_link>, u'\n']
>>> children[0].contents[0]
u'1\n        '
>>> int(children[0].contents[0])
1
>>> def key(child):
...     return int(child.contents[0])
...
>>> key(children[0])
1
>>> key(children[1])
0

好的。现在我们可以利用python的 itertools.groupby 函数,它会把所有具有相同关键字的子节点分在一起(我们需要先排序)。我们将使用刚刚定义的 key 函数来指定如何排序,以及什么定义了一个组。

>>> children = sorted(children, key=key)
>>> import itertools
>>> groups = itertools.groupby(children, key)

groups 是一个生成器——就像一个列表,但我们只能遍历它一次。让我们看看它的内容,虽然这样做会导致我们之后需要重新创建它。(生成器只能遍历一次,所以看数据的时候,我们就失去了它。幸运的是,重新创建它很简单)

>>> for k, g in groups:
...     print k, ':\t', list(g)
...
0 : [<article_time>0
        <article_name>aaa2</article_name>
<article_link>2aaaaaaa</article_link>
</article_time>, <article_time>0
        <article_name>aaa4</article_name>
<article_link>4aaaaaaa</article_link>
</article_time>]
1 : [<article_time>1
        <article_name>aaa1</article_name>
<article_link>1aaaaaaa</article_link>
</article_time>, <article_time>1
        <article_name>aaa3</article_name>
<article_link>3aaaaaaa</article_link>
</article_time>, <article_time>1
        <article_name>aaa5</article_name>
<article_link>5aaaaaaa</article_link>
</article_time>]

好的,k 指定了用来生成这个组的关键字,而 g 是与 k 匹配的 article_time 的序列。

抱歉,我现在只能写到这里。希望这些内容能帮你入门。

2

这里有一个用 Python 标准库中的 xml.etree.ElementTree 实现的解决方案。

这个方法的思路是根据 article_time 的文本值,把项目收集到 defaultdict(list) 中:

from collections import defaultdict
import xml.etree.ElementTree as ET

data = """<root>
    <article_date>09/09/2013
    <article_time>1
        <article_name>aaa1</article_name>
        <article_link>1aaaaaaa</article_link>
    </article_time>
    <article_time>0
        <article_name>aaa2</article_name>
        <article_link>2aaaaaaa</article_link>
    </article_time>
    <article_time>1
        <article_name>aaa3</article_name>
        <article_link>3aaaaaaa</article_link>
    </article_time>
    <article_time>0
        <article_name>aaa4</article_name>
        <article_link>4aaaaaaa</article_link>
    </article_time>
    <article_time>1
        <article_name>aaa5</article_name>
        <article_link>5aaaaaaa</article_link>
    </article_time>
    </article_date>
</root>
"""

tree = ET.fromstring(data)

root = ET.Element('root')
article_date = ET.SubElement(root, 'article_date')
article_date.text = tree.find('.//article_date').text

data = defaultdict(list)
for article_time in tree.findall('.//article_time'):
    text = article_time.text.strip()
    name = article_time.find('./article_name').text
    link = article_time.find('./article_link').text
    data[text].append((name, link))

for time_value, items in data.iteritems():
    article_time = ET.SubElement(article_date, 'article_time')
    article_name = ET.SubElement(article_time, 'article_name')
    article_link = ET.SubElement(article_time, 'article_name')

    article_time.text = time_value
    article_name.text = '+'.join(name for (name, _) in items)
    article_link.text = '+'.join(link for (_, link) in items)

print ET.tostring(root)

打印出来的结果(经过美化)是:

<root>
    <article_date>09/09/2013
        <article_time>1
            <article_name>aaa1+aaa3+aaa5</article_name>
            <article_name>1aaaaaaa+3aaaaaaa+5aaaaaaa</article_name>
        </article_time>
        <article_time>0
            <article_name>aaa2+aaa4</article_name>
            <article_name>2aaaaaaa+4aaaaaaa</article_name>
        </article_time>
    </article_date>
</root>

看吧,结果正是你想要的。

撰写回答