python常用内置模块之xml模块

XML 指可扩展标记语言(eXtensible Markup Language),XML 被设计用来传输和存储数据。

XML是实现不同语言或程序之间进行数据交换的协议,跟json差不多,但json使用起来更简单,不过,古时候,在json还没诞生的黑暗年代,大家只能选择用xml呀,至今很多传统公司如金融行业的很多系统的接口还主要是xml。xml的格式如下,就是通过<>节点来区别数据结构的:

 

 1 <?xml version="1.0"?>
 2 
 3 <data>
 4 
 5     <country name="Liechtenstein">
 6 
 7         <rank updated="yes">2</rank>
 8 
 9         <year>2008</year>
10 
11         <gdppc>141100</gdppc>
12 
13         <neighbor name="Austria" direction="E"/>
14 
15         <neighbor name="Switzerland" direction="W"/>
16 
17     </country>
18 
19     <country name="Singapore">
20 
21         <rank updated="yes">5</rank>
22 
23         <year>2011</year>
24 
25         <gdppc>59900</gdppc>
26 
27         <neighbor name="Malaysia" direction="N"/>
28 
29     </country>
30 
31     <country name="Panama">
32 
33         <rank updated="yes">69</rank>
34 
35         <year>2011</year>
36 
37         <gdppc>13600</gdppc>
38 
39         <neighbor name="Costa Rica" direction="W"/>
40 
41         <neighbor name="Colombia" direction="E"/>
42 
43     </country>
44 
45 </data>


2. 如何用Python来处理XML文件?????

python对XML的解析:ElementTree(元素树)

 

ElementTree是python的XML处理模块,它提供了一个轻量级的对象模型。在使用ElementTree模块时,需要import xml.etree.ElementTree的操作。

ElementTree表示整个XML节点树,而Element表示节点数中的一个单独的节点。

Element是个容器对象,有些属性:1) 一个tag;  2) 一些attrib,或者为空 {};  3) 一个text;  4) 一些子element

 

1、将XML文档解析为树(tree)

我们先从基础讲起。XML是一种结构化、层级化的数据格式,最适合体现XML的数据结构就是树。ET提供了两个对象:ElementTree将整个XML文档转化为树,Element则代表着树上的单个节点。对整个XML文档的交互(读取,写入,查找需要的元素),一般是在ElementTree层面进行的。对单个XML元素及其子元素,则是在Element层面进行的。下面我们举例介绍主要使用方法。

我们使用下面的XML文档,作为演示数据:

 
1
2
3
4
5
6
7
8
9
10
11
12
13
<?xml version="1.0"?>
<doc>
  <branch name="codingpy.com" hash="1cdf045c">
    text,source
  </branch>
  <branch name="release01" hash="f200013e">
    <sub-branch name="subrelease01">
      xml,sgml
    </sub-branch>
  </branch>
  <branch name="invalid">
  </branch>
</doc>

接下来,我们加载这个文档,并进行解析:

 
1
2
>>> import xml.etree.ElementTree as ET
>>> tree = ET.ElementTree(file='doc1.xml')

然后,我们获取根元素(root element):

 
1
2
>>> tree.getroot()
<Element 'doc' at 0x11eb780>
 

正如之前所讲的,根元素(root)是一个Element对象。我们看看根元素都有哪些属性:

1
2
3
>>> root = tree.getroot()
>>> root.tag, root.attrib
('doc', {})

没错,根元素并没有属性。与其他Element对象一样,根元素也具备遍历其直接子元素的接口:

1
2
3
4
5
6
>>> for child_of_root in root:
...  print child_of_root.tag, child_of_root.attrib
...
branch {'hash': '1cdf045c', 'name': 'codingpy.com'}
branch {'hash': 'f200013e', 'name': 'release01'}
branch {'name': 'invalid'}

我们还可以通过索引值来访问特定的子元素:

1
2
>>> root[0].tag, root[0].text
('branch', '\n    text,source\n  ')

2、查找需要的元素

从上面的示例中,可以明显发现我们能够通过简单的递归方法(对每一个元素,递归式访问其所有子元素)获取树中的所有元素。但是,由于这是十分常见的工作,ET提供了一些简便的实现方法。

Element对象有一个iter方法,可以对某个元素对象之下所有的子元素进行深度优先遍历(DFS)。ElementTree对象同样也有这个方法。下面是查找XML文档中所有元素的最简单方法:

1
2
3
4
5
6
7
8
>>> for elem in tree.iter():
...  print elem.tag, elem.attrib
...
doc {}
branch {'hash': '1cdf045c', 'name': 'codingpy.com'}
branch {'hash': 'f200013e', 'name': 'release01'}
sub-branch {'name': 'subrelease01'}
branch {'name': 'invalid'}

在此基础上,我们可以对树进行任意遍历——遍历所有元素,查找出自己感兴趣的属性。但是ET可以让这个工作更加简便、快捷。iter方法可以接受tag名称,然后遍历所有具备所提供tag的元素:

1
2
3
4
5
6
>>> for elem in tree.iter(tag='branch'):
...  print elem.tag, elem.attrib
...
branch {'hash': '1cdf045c', 'name': 'codingpy.com'}
branch {'hash': 'f200013e', 'name': 'release01'}
branch {'name': 'invalid'}

3、支持通过XPath查找元素

使用XPath查找感兴趣的元素,更加方便。Element对象中有一些find方法可以接受Xpath路径作为参数,find方法会返回第一个匹配的子元素,findall以列表的形式返回所有匹配的子元素, iterfind则返回一个所有匹配元素的迭代器(iterator)。ElementTree对象也具备这些方法,相应地它的查找是从根节点开始的。

下面是一个使用XPath查找元素的示例:

1
2
3
4
>>> for elem in tree.iterfind('branch/sub-branch'):
...  print elem.tag, elem.attrib
...
sub-branch {'name': 'subrelease01'}

上面的代码返回了branch元素之下所有tag为sub-branch的元素。接下来查找所有具备某个name属性的branch元素:

1
2
3
4
>>> for elem in tree.iterfind('branch[@name="release01"]'):
...  print elem.tag, elem.attrib
...
branch {'hash': 'f200013e', 'name': 'release01'}

4、构建XML文档

利用ET,很容易就可以完成XML文档构建,并写入保存为文件。ElementTree对象的write方法就可以实现这个需求。

一般来说,有两种主要使用场景。一是你先读取一个XML文档,进行修改,然后再将修改写入文档,二是从头创建一个新XML文档。

修改文档的话,可以通过调整Element对象来实现。请看下面的例子:

1
2
3
4
5
6
7
8
>>> root = tree.getroot()
>>> del root[2]
>>> root[0].set('foo', 'bar')
>>> for subelem in root:
...  print subelem.tag, subelem.attrib
...
branch {'foo': 'bar', 'hash': '1cdf045c', 'name': 'codingpy.com'}
branch {'hash': 'f200013e', 'name': 'release01'}

在上面的代码中,我们删除了root元素的第三个子元素,为第一个子元素增加了新属性。这个树可以重新写入至文件中。最终的XML文档应该是下面这样的:

1
2
3
4
5
6
7
8
9
10
11
12
>>> import sys
>>> tree.write(sys.stdout)
<doc>
  <branch foo="bar" hash="1cdf045c" name="codingpy.com">
    text,source
  </branch>
  <branch hash="f200013e" name="release01">
    <sub-branch name="subrelease01">
      xml,sgml
    </sub-branch>
  </branch>
  </doc>

请注意,文档中元素的属性顺序与原文档不同。这是因为ET是以字典的形式保存属性的,而字典是一个无序的数据结构。当然,XML也不关注属性的顺序。

从头构建一个完整的文档也很容易。ET模块提供了一个SubElement工厂函数,让创建元素的过程变得很简单:

1
2
3
4
5
6
7
8
9
10
>>> a = ET.Element('elem')
>>> c = ET.SubElement(a, 'child1')
>>> c.text = "some text"
>>> d = ET.SubElement(a, 'child2')
>>> b = ET.Element('elem_b')
>>> root = ET.Element('root')
>>> root.extend((a, b))
>>> tree = ET.ElementTree(root)
>>> tree.write(sys.stdout)
<root><elem><child1>some text</child1><child2 /></elem><elem_b /></root>

5、利用iterparse解析XML流

XML文档通常都会比较大,如何直接将文档读入内存的话,那么进行解析时就会出现问题。这也就是为什么不建议使用DOM,而是SAX API的理由之一。

我们上面谈到,ET可以将XML文档加载为保存在内存里的树(in-memory tree),然后再进行处理。但是在解析大文件时,这应该也会出现和DOM一样的内存消耗大的问题吧?没错,的确有这个问题。为了解决这个问题,ET提供了一个类似SAX的特殊工具——iterparse,可以循序地解析XML。

接下来,笔者为大家展示如何使用iterparse,并与标准的树解析方式进行对比。我们使用一个自动生成的XML文档,下面是该文档的开头部分:

1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0" standalone="yes"?>
<site>
 <regions>
  <africa>
   <item id="item0">
    <location>United States</location<!-- Counting locations -->
    <quantity>1</quantity>
    <name>duteous nine eighteen </name>
    <payment>Creditcard</payment>
    <description>
     <parlist>
[...]

我们来统计一下文档中出现了多少个文本值为Zimbabwe的location元素。下面是使用ET.parse的标准方法:

1
2
3
4
5
6
7
8
tree = ET.parse(sys.argv[2])
 
count = 0
for elem in tree.iter(tag='location'):
  if elem.text == 'Zimbabwe':
    count += 1
 
print count

上面的代码会将全部元素载入内存,逐一解析。当解析一个约100MB的XML文档时,运行上面脚本的Python进程的内存使用峰值为约560MB,总运行时间问2.9秒。

请注意,我们其实不需要讲整个树加载到内存里。只要检测出文本为相应值得location元素即可。其他数据都可以废弃。这时,我们就可以用上iterparse方法了:

1
2
3
4
5
6
7
8
count = 0
for event, elem in ET.iterparse(sys.argv[2]):
  if event == 'end':
    if elem.tag == 'location' and elem.text == 'Zimbabwe':
      count += 1
  elem.clear() # 将元素废弃
 
print count

上面的for循环会遍历iterparse事件,首先检查事件是否为end,然后判断元素的tag是否为location,以及其文本值是否符合目标值。另外,调用elem.clear()非常关键:因为iterparse仍然会生成一个树,只是循序生成的而已。废弃掉不需要的元素,就相当于废弃了整个树,释放出系统分配的内存。

当利用上面这个脚本解析同一个文件时,内存使用峰值只有7MB,运行时间为2.5秒。速度提升的原因,是我们这里只在树被构建时,遍历一次。而使用parse的标准方法是先完成整个树的构建后,才再次遍历查找所需要的元素。

iterparse的性能与SAX相当,但是其API却更加有用:iterparse会循序地构建树;而利用SAX时,你还得自己完成树的构建工作。

以上就是为大家分享的Python解析XML的几种方式,希望对大家的学习有所帮助。

 

 

 

posted on 2017-07-25 14:40  momo8238  阅读(189)  评论(0)    收藏  举报