文章目录

  • 一、xml文件结构
  • 二、基础解析
  • 1.加载文档
  • 2.获取根元素
  • 3.根元素的属性
  • 4.遍历其直接子元素
  • 5.通过索引值来访问特定的子元素
  • 查找需要的元素
  • 三、支持通过XPath查找元素
  • 四、构建XML文档
  • 利用iterparse解析XML流


一、xml文件结构

将XML文档解析为树(tree)

XML是一种结构化、层级化的数据格式,最适合体现XML的数据结构就是树。

ET提供了两个对象:ElementTree将整个XML文档转化为树,Element则代表着树上的单个节点。

  • 对整个XML文档的交互(读取,写入,查找需要的元素),一般是在ElementTree层面进行的。
  • 对单个XML元素及其子元素,则是在Element层面进行的。下面我们举例介绍主要使用方法。

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

<?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>

可以看到都是对应的

python elementtree格式化输出 python elementtree生成xml_加载

二、基础解析

1.加载文档

import xml.etree.ElementTree as ET
tree = ET.ElementTree(file=r'E:\Sheeps-PascalVOC-export\123\test.xml')
tree

<xml.etree.ElementTree.ElementTree at 0x23842ef5b08>

2.获取根元素

tree.getroot()

<Element 'doc' at 0x00000238425F80E8>

python elementtree格式化输出 python elementtree生成xml_python_02


可以看到根元素(root)是一个Element对象

3.根元素的属性

root = tree.getroot()
root.tag,root.attrib	#toot 的标签toot的属性

('doc', {})

嗯!!!根元素并没有属性。

4.遍历其直接子元素

与其他Element对象一样,根元素也具备遍历其直接子元素的接口

for child_of_root in root:
    print(child_of_root.tag,child_of_root.attrib)

branch {'name': 'codingpy.com', 'hash': '1cdf045c'}
branch {'name': 'release01', 'hash': 'f200013e'}
branch {'name': 'invalid'}

这个是直接遍历root这个跟元素的子元素,可以看到sub-branch这个元素是root子元素的子元素,所以不能遍历。

python elementtree格式化输出 python elementtree生成xml_python_03

5.通过索引值来访问特定的子元素

可以把0改成其他的自己试试

root[0].tag, root[0].text

('branch', '\n  text,source\n ')

python elementtree格式化输出 python elementtree生成xml_python_04

查找需要的元素

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

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

这里是直接遍历的tree的所有元素

for elem in tree.iter():
     print(elem.tag, elem.attrib)

doc {}
branch {'name': 'codingpy.com', 'hash': '1cdf045c'}
branch {'name': 'release01', 'hash': 'f200013e'}
sub-branch {'name': 'subrelease01'}
branch {'name': 'invalid'}

python elementtree格式化输出 python elementtree生成xml_XML_05


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

for elem in tree.iter(tag='branch'):
    print(elem.tag, elem.attrib)

branch {'name': 'codingpy.com', 'hash': '1cdf045c'}
branch {'name': 'release01', 'hash': 'f200013e'}
branch {'name': 'invalid'}

三、支持通过XPath查找元素

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

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

for elem in tree.iterfind('branch/sub-branch'):
    print (elem.tag, elem.attrib)

sub-branch {'name': 'subrelease01'}

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

for elem in tree.iterfind('branch[@name="release01"]'):
     print(elem.tag, elem.attrib)

branch {'name': 'release01', 'hash': 'f200013e'}

四、构建XML文档

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

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

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

root = tree.getroot()
del root[2]
root[0].set('foo', 'bar')
for subelem in root:
    print(subelem.tag, subelem.attrib)

branch {'name': 'codingpy.com', 'hash': '1cdf045c', 'foo': 'bar'}
branch {'name': 'release01', 'hash': 'f200013e'}

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

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工厂函数,让创建元素的过程变得很简单:

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>

利用iterparse解析XML流

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

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

接下来,笔者为大家展示如何使用iterparse。

count = 0
for elem in tree.iter():
    print(elem)
    if elem.tag=='branch':
            count += 1
print (count)

<Element 'doc' at 0x0000021E6773CDB8>
<Element 'branch' at 0x0000021E6768D228>
<Element 'branch' at 0x0000021E6768D868>
<Element 'sub-branch' at 0x0000021E6768D9A8>
<Element 'branch' at 0x0000021E6768DF98>
3

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

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