如何解析嵌套的XML并提取属性+标签文本?

How to parse nested XML and extract attributes + tag text both?

提问人:x89 提问时间:1/23/2023 最后编辑:HedgeHogx89 更新时间:1/23/2023 访问量:408

问:

我的 XML 如下所示:

<?xml version="1.0" encoding="UTF-8" ?>
<main_heading timestamp="20220113">
<details>
    <offer id="11" new_id="12">
        <level>1&amp;1</level>
        <typ>Green</typ>
        <name>Alpha</name>
        <visits>
            <name>DONT INCLUDE</name>
        </visits>
    </offer>
    <offer id="12" new_id="31">
        <level>1&amp;1</level>
        <typ>Yellow</typ>
        <name>Beta</name>
        <visits>
            <name>DONT INCLUDE</name>
        </visits>
    </offer>
</details>
</main_heading>

我想将某些字段解析为数据帧。

预期输出

timestamp   id     new_id   level      name
20220113    11     12       1&amp;1    Alpha
20220113    12     31       1&amp;1    Beta

其中不包括嵌套在“visits”标记中的 NAME。我只想考虑外部的“名称”标签。

timestamp = soup.find('main_heading').get('timestamp')
df[timestamp'] = timestamp

这解决了一部分

其余的我可以这样做:

typ = []
for i in (soup.find_all('typ')):
    typ.append(i.text)

但我不想为每个新字段创建多个 for 循环

python-3.x beautifulsoup xml解析

评论

0赞 eike 1/23/2023
你到底期待什么?
0赞 x89 1/23/2023
预期输出在上面的 QS 中给出。数据帧。@eike
0赞 eike 1/23/2023
预期输出,是的,但不是算法的约束。你根本不想使用 for 循环吗?
0赞 x89 1/23/2023
我对建议持开放态度,但我希望如果可能的话,我不必为每个字段创建一个新的长循环(以防万一我有太多字段需要提取)@eike
0赞 eike 1/23/2023
如果您只对 的单个子字段感兴趣,那么是否可以接受一个遍历所有 s 的子字段?offeroffer

答:

3赞 HedgeHog 1/23/2023 #1

遍历选件并选择其上一个选件:main_heading

for e in soup.select('offer'):
    data.append({
        'timestamp': e.find_previous('main_heading').get('timestamp'),
        'id':e.get('id'),
        'id_old':e.get('old_id'),
        'level':e.level.text,
        'typ':e.typ.text,
        'name':e.select_one('name').text
    })

或者,作为仅排除某些元素并更通用的替代方法:

for e in soup.select('offer'):
    
    d = {
        'timestamp': e.find_previous('main_heading').get('timestamp'),
        'id':e.get('id'),
        'id_old':e.get('old_id'),
    }

    d.update({c.name:c.text for c in e.children if c.name is not None and 'visits' not in c.name})

    data.append(d)

from bs4 import BeautifulSoup
import pandas as pd

xml = '''<?xml version="1.0" encoding="UTF-8" ?>
<main_heading timestamp="20220113">
<details>
    <offer id="11" new_id="12">
        <level>1&amp;1</level>
        <typ>Green</typ>
        <name>Alpha</name>
        <visits>
            <name>DONT INCLUDE</name>
        </visits>
    </offer>
    <offer id="12" new_id="31">
        <level>1&amp;1</level>
        <typ>Yellow</typ>
        <name>Beta</name>
        <visits>
            <name>DONT INCLUDE</name>
        </visits>
    </offer>
</details>
</main_heading>
'''
soup = BeautifulSoup(xml,'xml')

data = []

for e in soup.select('offer'):
    data.append({
        'timestamp': e.find_previous('main_heading').get('timestamp'),
        'id':e.get('id'),
        'id_old':e.get('old_id'),
        'level':e.level.text,
        'typ':e.typ.text,
        'name':e.select_one('name').text
    })

pd.DataFrame(data)

输出

时间戳 编号 id_old 水平 典型值 名字
0 20220113 11 1&1 绿 阿尔法
1 20220113 12 1&1 黄色 试用版

评论

0赞 eike 1/23/2023
只是出于兴趣,在这种情况下,和 之间有区别吗?select("offer")find_all("offer")
1赞 HedgeHog 1/23/2023
不是在这种特定情况下,因为两者都使用元素名称,但通常使用 -> crummy.com/software/BeautifulSoup/bs4/doc/#css-selectorsselectcss selectors
0赞 HedgeHog 1/23/2023
@eike 此外,可以检查此问答:stackoverflow.com/questions/38028384/...
0赞 balderman 1/23/2023
@HedgeHog我认为这里已经足够好了。xml.etree.ElementTree
1赞 balderman 1/23/2023 #2

无需任何外部库。

核心 python 在这里就足够了。

import xml.etree.ElementTree as ET
import pandas as pd

xml = '''<?xml version="1.0" encoding="UTF-8" ?>
<main_heading timestamp="20220113">
<details>
    <offer id="11" new_id="12">
        <level>1&amp;1</level>
        <typ>Green</typ>
        <name>Alpha</name>
        <visits>
            <name>DONT INCLUDE</name>
        </visits>
    </offer>
    <offer id="12" new_id="31">
        <level>1&amp;1</level>
        <typ>Yellow</typ>
        <name>Beta</name>
        <visits>
            <name>DONT INCLUDE</name>
        </visits>
    </offer>
</details>
</main_heading>'''

data = []
root = ET.fromstring(xml)
timestamp = root.attrib.get('timestamp')
for offer in root.findall('.//offer'):
    temp = {'timestamp': timestamp}
    for attr in ['id', 'new_id']:
        temp[attr] = offer.attrib.get(attr)
    for ele in ['level', 'name']:
        temp[ele] = offer.find(ele).text
    data.append(temp)
df = pd.DataFrame(data)
print(df)

输出

  timestamp  id new_id level   name
0  20220113  11     12   1&1  Alpha
1  20220113  12     31   1&1   Beta

评论

0赞 HedgeHog 1/23/2023
合理的替代方案,如有必要,将牢记它以使其更简单。
1赞 Jack Fleeting 1/23/2023 #3

为了完整起见(和未来的访问者),这里还有另一个:由于我们正在处理 xml,并且最终输出是 DataFrame,因此最好(也是最简单)使用 pandas.read_xml

df = pd.read_xml(xml,xpath='//offer')
ts = pd.read_xml(xml,xpath="//main_heading")['timestamp'][0]
df.insert(0, 'timestamp', ts)
print(df.drop(['typ', 'visits'], axis=1))

这应该能给你带来预期的产出。

评论

0赞 HedgeHog 1/23/2023
就像一束可能性一样,今天又学到了一些东西——我发现直接使用和@jqurious一样好pandas
0赞 Jack Fleeting 1/23/2023
@HedgeHog - “可能性的花束” - 你绝对应该花一些时间在诗歌网站上:)。
0赞 HedgeHog 1/23/2023
难道我们所有人心中都不有一个小诗人吗?我们是源代码诗人,写着美丽的字母汤,寻找可爱的熊猫,想知道一束可能性。你是对的,从今天开始有更多的诗歌;)
2赞 jqurious 1/23/2023 #4

熊猫有.read_xml()

您可以使用传递自定义 XPath 表达式来指定要提取的内容。xpath=

例如,和标签:<offer><main_heading>

>>> pd.read_xml("main.xml", xpath="""//*[name() = "offer" or name() = "main_heading"]""")
    timestamp  details    id  new_id level     typ   name  visits
0  20220113.0      NaN   NaN     NaN  None    None   None     NaN
1         NaN      NaN  11.0    12.0   1&1   Green  Alpha     NaN
2         NaN      NaN  12.0    31.0   1&1  Yellow   Beta     NaN

从那里您可以添加时间戳并删除详细信息/访问列:.ffill()

>>> (pd.read_xml("main.xml", xpath="""//*[name() = "offer" or name() = "main_heading"]""")
...    .ffill()
...    .drop(columns=["details", "visits"])
...    .dropna()
... )
    timestamp    id  new_id level     typ   name
1  20220113.0  11.0    12.0   1&1   Green  Alpha
2  20220113.0  12.0    31.0   1&1  Yellow   Beta

评论

0赞 HedgeHog 1/23/2023
从未想过使用 ,最好的替代方法可以直接使用xpathpandas
0赞 Jack Fleeting 1/23/2023
看起来我们相隔几秒钟发布 - 使用同样的想法!伟大的思想是一样的(如果我自己这么说的话)!