如何在Python中解析XML?

How do I parse XML in Python?

我在一个包含XML的数据库中有许多行,我正在尝试编写一个python脚本,该脚本将遍历这些行,并计算特定节点属性的实例数。例如,我的树看起来像:

1
2
3
4
5
6
<foo>
   <bar>
      <type foobar="1"/>
      <type foobar="2"/>
   </bar>
</foo>

如何使用python访问XML中的属性1和2?


我建议用ElementTree。同一个API还有其他兼容的实现,比如在python标准库本身中的lxmlcElementTree;但是在这种情况下,它们主要添加的是更快的速度——编程的容易程度取决于ElementTree定义的API。

首先从XML构建元素实例root,例如,使用XML函数,或者通过以下方式解析文件:

1
2
import xml.etree.ElementTree as ET
root = ET.parse('thefile.xml').getroot()

或者在ElementTree中显示的许多其他方式中的任何一种。然后做如下的事情:

1
2
3
for type_tag in root.findall('bar/type'):
    value = type_tag.get('foobar')
    print(value)

以及类似的,通常非常简单的代码模式。


minidom是最快和最直接的:

XML:

1
2
3
4
5
6
7
8
<data>
    <items>
        <item name="item1"></item>
        <item name="item2"></item>
        <item name="item3"></item>
        <item name="item4"></item>
    </items>
</data>

Python:

1
2
3
4
5
6
7
from xml.dom import minidom
xmldoc = minidom.parse('items.xml')
itemlist = xmldoc.getElementsByTagName('item')
print(len(itemlist))
print(itemlist[0].attributes['name'].value)
for s in itemlist:
    print(s.attributes['name'].value)

产量

1
2
3
4
5
6
4
item1
item1
item2
item3
item4


你可以用漂亮的汤

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from bs4 import BeautifulSoup

x="""<foo>
   <bar>
      <type foobar="1"/>
      <type foobar="2"/>
   </bar>
</foo>"""


y=BeautifulSoup(x)
>>> y.foo.bar.type["foobar"]
u'1'

>>> y.foo.bar.findAll("type")
[<type foobar="1"></type>, <type foobar="2"></type>]

>>> y.foo.bar.findAll("type")[0]["foobar"]
u'1'
>>> y.foo.bar.findAll("type")[1]["foobar"]
u'2'


有很多选择。如果速度和内存使用是一个问题,CelementTree看起来很好。与使用readlines读取文件相比,它的开销非常小。

相关指标见下表,从CelementTree网站复制:

1
2
3
4
5
6
7
8
9
10
11
12
library                         time    space
xml.dom.minidom (Python 2.1)    6.3 s   80000K
gnosis.objectify                2.0 s   22000k
xml.dom.minidom (Python 2.4)    1.4 s   53000k
ElementTree 1.2                 1.6 s   14500k  
ElementTree 1.2.4/1.3           1.1 s   14500k  
cDomlette (C extension)         0.540 s 20500k
PyRXPU (C extension)            0.175 s 10850k
libxml2 (C extension)           0.098 s 16000k
readlines (read as utf-8)       0.093 s 8850k
cElementTree (C extension)  --> 0.047 s 4900K <--
readlines (read as ascii)       0.032 s 5050k

正如@jfs所指出的,cElementTree与python捆绑在一起:

  • python 2:from xml.etree import cElementTree as ElementTree
  • python 3:from xml.etree import ElementTree(自动使用加速的C版本)。


为了简单起见,我建议使用xmltodict。

它将XML解析为ordereddict;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
>>> e = '<foo>
             <bar>
                 <type foobar="1"/>
                 <type foobar="2"/>
             </bar>
        </foo> '


>>> import xmltodict
>>> result = xmltodict.parse(e)
>>> result

OrderedDict([(u'foo', OrderedDict([(u'bar', OrderedDict([(u'type', [OrderedDict([(u'@foobar', u'1')]), OrderedDict([(u'@foobar', u'2')])])]))]))])

>>> result['foo']

OrderedDict([(u'bar', OrderedDict([(u'type', [OrderedDict([(u'@foobar', u'1')]), OrderedDict([(u'@foobar', u'2')])])]))])

>>> result['foo']['bar']

OrderedDict([(u'type', [OrderedDict([(u'@foobar', u'1')]), OrderedDict([(u'@foobar', u'2')])])])


lxml.objectify非常简单。

提取示例文本:

1
2
3
4
5
6
7
8
9
10
11
from lxml import objectify
from collections import defaultdict

count = defaultdict(int)

root = objectify.fromstring(text)

for item in root.bar.type:
    count[item.attrib.get("foobar")] += 1

print dict(count)

输出:

1
{'1': 1, '2': 1}


python有一个到expat XML解析器的接口。

1
xml.parsers.expat

它是一个未验证的解析器,因此不会捕获坏的XML。但是如果你知道你的文件是正确的,那么这是非常好的,你可能会得到你想要的确切信息,你可以在飞行中丢弃其余的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
stringofxml ="""<foo>
    <bar>
        <type arg="value" />
        <type arg="value" />
        <type arg="value" />
    </bar>
    <bar>
        <type arg="value" />
    </bar>
</foo>"""

count = 0
def start(name, attr):
    global count
    if name == 'type':
        count += 1

p = expat.ParserCreate()
p.StartElementHandler = start
p.Parse(stringofxml)

print count # prints 4


我建议使用declxml。

完全公开:我编写这个库是因为我在寻找一种在XML和Python数据结构之间转换的方法,而不需要用elementtree编写几十行必需的解析/序列化代码。

使用declXML,可以使用处理器声明性地定义XML文档的结构以及如何在XML和Python数据结构之间进行映射。处理器用于序列化和解析以及基本的验证级别。

解析为python数据结构非常简单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import declxml as xml

xml_string ="""
<foo>
   <bar>
      <type foobar="1"/>
      <type foobar="2"/>
   </bar>
</foo>
"""


processor = xml.dictionary('foo', [
    xml.dictionary('bar', [
        xml.array(xml.integer('type', attribute='foobar'))
    ])
])

xml.parse_from_string(processor, xml_string)

产生输出:

1
{'bar': {'foobar': [1, 2]}}

也可以使用同一个处理器将数据序列化为XML

1
2
3
4
5
data = {'bar': {
    'foobar': [7, 3, 21, 16, 11]
}}

xml.serialize_to_string(processor, data, indent='    ')

产生以下输出

1
2
3
4
5
6
7
8
9
10
<?xml version="1.0" ?>
<foo>
    <bar>
        <type foobar="7"/>
        <type foobar="3"/>
        <type foobar="21"/>
        <type foobar="16"/>
        <type foobar="11"/>
    </bar>
</foo>

如果您想使用对象而不是字典,那么您也可以定义处理器来在对象之间转换数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import declxml as xml

class Bar:

    def __init__(self):
        self.foobars = []

    def __repr__(self):
        return 'Bar(foobars={})'.format(self.foobars)


xml_string ="""
<foo>
   <bar>
      <type foobar="1"/>
      <type foobar="2"/>
   </bar>
</foo>
"""


processor = xml.dictionary('foo', [
    xml.user_object('bar', Bar, [
        xml.array(xml.integer('type', attribute='foobar'), alias='foobars')
    ])
])

xml.parse_from_string(processor, xml_string)

产生以下输出

1
{'bar': Bar(foobars=[1, 2])}

为了增加另一种可能性,您可以使用untangle,因为它是一个简单的XML到Python对象库。这里有一个例子:

安装

1
pip install untangle

用法

您的XML文件(有点更改):

1
2
3
4
5
<foo>
   <bar name="bar_name">
      <type foobar="1"/>
   </bar>
</foo>

使用untangle访问属性:

1
2
3
4
5
6
import untangle

obj = untangle.parse('/path_to_xml_file/file.xml')

print obj.foo.bar['name']
print obj.foo.bar.type['foobar']

输出将是:

1
2
bar_name
1

有关untangle的更多信息可以在这里找到。另外(如果您好奇的话),您可以在这里找到一个用于XML和Python的工具列表(您还会看到前面的答案提到了最常见的工具)。


这里有一个使用cElementTree的非常简单但有效的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
try:
    import cElementTree as ET
except ImportError:
  try:
    # Python 2.5 need to import a different module
    import xml.etree.cElementTree as ET
  except ImportError:
    exit_err("Failed to import cElementTree from any known place")      

def find_in_tree(tree, node):
    found = tree.find(node)
    if found == None:
        print"No %s in file" % node
        found = []
    return found  

# Parse a xml file (specify the path)
def_file ="xml_file_name.xml"
try:
    dom = ET.parse(open(def_file,"r"))
    root = dom.getroot()
except:
    exit_err("Unable to open and parse input definition file:" + def_file)

# Parse to find the child nodes list of node 'myNode'
fwdefs = find_in_tree(root,"myNode")

来源:

http://www.snip2code.com/snippet/991/python-xml-parse?来自1页


我发现python xml.dom和xml.dom.minidom非常简单。请记住,DOM不适合于大量的XML,但是如果您的输入相当小,那么这将很好地工作。


1
2
3
4
5
6
7
8
9
10
11
import xml.etree.ElementTree as ET
data = '''<foo>
           <bar>
               <type foobar="1"/>
               <type foobar="2"/>
          </bar>
       </foo>'''

tree = ET.fromstring(data)
lst = tree.findall('bar/type')
for item in lst:
    print item.get('foobar')

这将打印foobar属性的值。


XML

1
2
3
4
5
6
<foo>
   <bar>
      <type foobar="1"/>
      <type foobar="2"/>
   </bar>
</foo>

Python码

1
2
3
4
5
6
7
8
9
10
11
12
import xml.etree.cElementTree as ET

tree = ET.parse("foo.xml")
root = tree.getroot()
root_tag = root.tag
print(root_tag)

for form in root.findall("./bar/type"):
    x=(form.attrib)
    z=list(x)
    for i in z:
        print(x[i])

输出:

1
2
3
foo
1
2

xml.etree.elementtree与lxml

这是两个最常用的库中的一些优点,在选择它们之前,我会先了解它们。

xml.etree.element树:

  • 从标准库:不需要安装任何模块
  • LXML

  • 轻松编写XML声明:是否需要添加,例如standalone="no"?
  • 漂亮的打印:您可以有一个漂亮的缩进XML,而不需要额外的代码。
  • 对象化功能:它允许您像处理普通的Python对象层次结构一样使用XML。