将XML文件解析为Python对象

23 投票
5 回答
54992 浏览
提问于 2025-04-16 15:00

我有一个看起来像这样的XML文件:

<encspot>
  <file>
   <Name>some filename.mp3</Name>
   <Encoder>Gogo (after 3.0)</Encoder>
   <Bitrate>131</Bitrate>
   <Mode>joint stereo</Mode>
   <Length>00:02:43</Length>
   <Size>5,236,644</Size>
   <Frame>no</Frame>
   <Quality>good</Quality>
   <Freq.>44100</Freq.>
   <Frames>6255</Frames>
   ..... and so forth ......
  </file>
  <file>....</file>
</encspot>

我想把它读入一个Python对象,类似于字典的列表。因为这个文件的格式是固定的,我有点想用正则表达式(我对这个还挺在行的)。不过,我想先看看有没有人知道怎么在这里轻松避免使用正则表达式。我对SAX或其他解析方法没有太多经验,但我愿意学习。

我期待有人能教我怎么在Python中快速做到这一点,而不需要用正则表达式。谢谢大家的帮助!

5 个回答

6

我也在寻找一种简单的方法,可以在XML文档和Python数据结构之间转换数据,类似于Golang的XML库,它允许你以声明的方式指定如何将数据结构映射到XML。

我没有找到这样的Python库,所以我自己写了一个,叫做declxml,用于声明式的XML处理。

使用declxml,你可以创建处理器,这些处理器可以声明性地定义你的XML文档的结构。处理器不仅用于解析和序列化数据,还可以进行基本的验证。

用declxml将XML数据解析成字典列表非常简单

import declxml as xml

xml_string = """
<encspot>
  <file>
   <Name>some filename.mp3</Name>
   <Encoder>Gogo (after 3.0)</Encoder>
   <Bitrate>131</Bitrate>
  </file>
  <file>
   <Name>another filename.mp3</Name>
   <Encoder>iTunes</Encoder>
   <Bitrate>128</Bitrate>  
  </file>
</encspot>
"""

processor = xml.dictionary('encspot', [
    xml.array(xml.dictionary('file', [
        xml.string('Name'),
        xml.string('Encoder'),
        xml.integer('Bitrate')
    ]), alias='files')
])

xml.parse_from_string(processor, xml_string)

这将产生以下结果

{'files': [
  {'Bitrate': 131, 'Encoder': 'Gogo (after 3.0)', 'Name': 'some filename.mp3'},
  {'Bitrate': 128, 'Encoder': 'iTunes', 'Name': 'another filename.mp3'}
]}

想把数据解析成对象而不是字典?这也可以做到

import declxml as xml

class AudioFile:

    def __init__(self):
        self.name = None
        self.encoder = None
        self.bit_rate = None

    def __repr__(self):
        return 'AudioFile(name={}, encoder={}, bit_rate={})'.format(
            self.name, self.encoder, self.bit_rate)


processor = xml.array(xml.user_object('file', AudioFile, [
    xml.string('Name', alias='name'),
    xml.string('Encoder', alias='encoder'),
    xml.integer('Bitrate', alias='bit_rate')
]), nested='encspot')

xml.parse_from_string(processor, xml_string)

这将产生以下输出

[AudioFile(name=some filename.mp3, encoder=Gogo (after 3.0), bit_rate=131),
 AudioFile(name=another filename.mp3, encoder=iTunes, bit_rate=128)]
8

可以使用ElementTree这个库。你不需要去搞一些只会解析的工具,比如pyexpat,因为那样你只会部分且糟糕地重新发明一个ElementTree。

还有一个选择是lxml,这是一个第三方的包,它实现了ElementTree的接口,并且功能更多。

更新 有人开始玩代码高尔夫;这是我的一个代码示例,实际上创建了你所需要的数据结构:

# xs = """<encspot> etc etc </encspot"""
>>> import xml.etree.cElementTree as et
>>> from pprint import pprint as pp
>>> pp([dict((attr.tag, attr.text) for attr in el) for el in et.fromstring(xs)])
[{'Bitrate': '131',
  'Encoder': 'Gogo (after 3.0)',
  'Frame': 'no',
  'Frames': '6255',
  'Freq.': '44100',
  'Length': '00:02:43',
  'Mode': 'joint stereo',
  'Name': 'some filename.mp3',
  'Quality': 'good',
  'Size': '5,236,644'},
 {'Bitrate': '0', 'Name': 'foo.mp3'}]
>>>

你可能想要一个字典,把“属性”名称映射到转换函数:

converters = {
    'Frames': int,
    'Size': lambda x: int(x.replace(',', '')),
    # etc
    }
32

如果你觉得正则表达式比这个简单,那我真是佩服你,我的SD Chargers帽子都要脱下来了:

#!/usr/bin/env python
import xml.etree.cElementTree as et

sxml="""
<encspot>
  <file>
   <Name>some filename.mp3</Name>
   <Encoder>Gogo (after 3.0)</Encoder>
   <Bitrate>131</Bitrate>
  </file>
  <file>
   <Name>another filename.mp3</Name>
   <Encoder>iTunes</Encoder>
   <Bitrate>128</Bitrate>  
  </file>
</encspot>
"""
tree=et.fromstring(sxml)

for el in tree.findall('file'):
    print '-------------------'
    for ch in el.getchildren():
        print '{:>15}: {:<30}'.format(ch.tag, ch.text) 

print "\nan alternate way:"  
el=tree.find('file[2]/Name')  # xpath
print '{:>15}: {:<30}'.format(el.tag, el.text)  

输出结果:

-------------------
           Name: some filename.mp3             
        Encoder: Gogo (after 3.0)              
        Bitrate: 131                           
-------------------
           Name: another filename.mp3          
        Encoder: iTunes                        
        Bitrate: 128                           

an alternate way:
           Name: another filename.mp3  

如果你喜欢正则表达式的简洁,这里有一段同样让人看不懂的列表推导式,用来创建一个数据结构:

[(ch.tag,ch.text) for e in tree.findall('file') for ch in e.getchildren()]

这段代码会生成一个包含XML中<file>标签下子元素的元组列表,顺序和文档中的顺序一致:

[('Name', 'some filename.mp3'), 
 ('Encoder', 'Gogo (after 3.0)'), 
 ('Bitrate', '131'), 
 ('Name', 'another filename.mp3'), 
 ('Encoder', 'iTunes'), 
 ('Bitrate', '128')]

当然,如果你多写几行代码,想得再仔细一点,你就可以用ElementTree从XML中创建任何你想要的数据结构。这个工具是Python自带的。

编辑

代码比赛开始了!

[{item.tag: item.text for item in ch} for ch in tree.findall('file')] 
[ {'Bitrate': '131', 
   'Name': 'some filename.mp3', 
   'Encoder': 'Gogo (after 3.0)'}, 
  {'Bitrate': '128', 
   'Name': 'another filename.mp3', 
   'Encoder': 'iTunes'}]

如果你的XML文件只有file这一部分,你可以随意发挥。如果你的XML还有其他标签和部分,你就需要考虑子元素所在的部分,并且需要使用findall

关于ElementTree的教程可以在Effbot.org找到。

撰写回答