python xml.etree.ElementTree 处理xml 文件 变量 流 xml概念
python xml.etree.ElementTree 处理xml 文件 变量 流 xml概念
2018/11/27 Chenxin
XML概念
XML 被设计用来传输和存储数据.HTML 被设计用来显示数据.
标记语言从早期的私有公司和政府制定形式逐渐演变成标准通用标记语言(Standard Generalized Markup Language,SGML)、超文本标记语言(Hypertext Markup Language,HTML),并且最终演变成 XML。XML有以下几个特点。
- XML的设计宗旨是传输数据,而非显示数据。
- XML标签没有被预定义。您需要自行定义标签。
- XML被设计为具有自我描述性。
- XML是W3C的推荐标准。
XML是各种应用程序之间进行数据传输的最常用的工具,并且在信息存储和描述领域变得越来越流行。因此,学会如何解析XML文件,对于Web开发来说是十分重要的。
基本名词
声明
父(元素),子(元素),同胞
根
属性
文本内容
开始标签,结束标签
空格
注释
实体引用
命名空间
默认命名空间
结构
XML 声明.声明不属于XML本身的组成部分.它不是 XML 元素,不需要关闭标签.
Don't forget the meeting!
从本例可以设想,该 XML 文档包含了 John 给 George 的一张便签.XML 具有出色的自我描述性.
打开标签和关闭标签通常被称为开始标签和结束标签.
XML 表示的书:
实体引用
如:
在 XML 中,有 5 个预定义的实体引用:
< < 小于 (注意后面那个分号)
> > 大于
& & 和号
' ' 单引号
" " 引号
说明:在XML中,只有字符 "<" 和 "&" 是非法的.大于号其实是合法的,但仍推荐用实体引用来代替它,便于规范化.
注释
在 XML 中编写注释的语法与 HTML 的语法很相似:
XML中,多个空格仍会被保留成多个
HTML 会把多个连续的空格字符裁减(合并)为一个:HTML: Hello my name is David.输出: Hello my name is David.
但在 XML 中,文档中的空格不会被删节.
XML属性
XML 元素可以在开始标签中包含属性,类似 HTML.
属性 (Attribute) 提供关于元素的额外(附加)信息.
属性通常提供不属于数据组成部分的信息.在下面的例子中,文件类型与数据无关,但是对需要处理这个元素的软件来说却很重要:
如果属性值本身包含双引号,那么有必要使用单引号包围它,就像:
或者可以使用实体引用:
在 HTML 中,属性用起来很便利,但是在 XML 中,您应该尽量避免使用属性.如果信息感觉起来很像数据,那么请使用子元素吧.
避免使用 XML 属性的原因,因使用属性而引起的一些问题:
- 属性无法包含多重的值(元素可以)
- 属性无法描述树结构(元素可以)
- 属性不易扩展(为未来的变化)
- 属性难以阅读和维护
请尽量使用元素来描述数据.而仅仅使用属性来提供与数据无关的信息.
针对元数据的 XML 属性
有时候会向元素分配 ID 引用.这些 ID 索引可用于标识 XML 元素,它起作用的方式与 HTML 中 ID 属性是一样的.这个例子向我们演示了这种情况:
George
John
Reminder Don't forget the meeting!John George Re: Reminder I will not
XML文档的语法说明
形式良好(Well Formed)XML 语法规则:
- XML 文档必须有根元素
- XML 文档必须有关闭标签
- XML 标签对大小写敏感
- XML 元素必须被正确的嵌套
- XML 属性必须加引号
命名空间(Namespaces)
与仅仅使用前缀不同,我们为某个标签添加了一个 xmlns 属性,这样就为前缀赋予了一个与某个命名空间相关联的限定名称.
XML Namespace (xmlns) 属性
XML 命名空间属性被放置于元素的开始标签之中,并使用以下语法:xmlns:namespace-prefix="namespaceURI" 如
<f:table xmlns:f="http://www.w3school.com.cn/furniture">
<f:name>Coffee Table</f:name>
<f:width>80</f:width>
<f:length>120</f:length>
</f:table>
当命名空间被定义在元素的开始标签中时,所有带有相同前缀的子元素都会与同一个命名空间相关联.
用于标示命名空间的地址不会被解析器用于查找信息.其惟一的作用是赋予命名空间一个惟一的名称.不过,很多公司常常会作为指针来使用命名空间指向实际存在的网页,这个网页包含关于命名空间的信息.
统一资源标识符(Uniform Resource Identifier (URI))
URI是一串可以标识因特网资源的字符.最常用的 URI 是用来标示因特网域名地址的统一资源定位器(URL).
另一个不那么常用的 URI 是统一资源命名(URN).
默认的命名空间(Default Namespaces)
为元素定义默认的命名空间可以让我们省去在所有的子元素中使用前缀的工作.
请使用下面的语法:xmlns="namespaceURI"
访问有命名空间的元素的时候,需要跟上命名空间才行,如
for result in root.findall('.//{http://monitoring.amazonaws.com/doc/2010-08-01/}StateReason'):
print(result, result.text)
python解析XML的6种方式
Python的标准库中,提供了6种可以用于处理XML的包。
xml.dom
xml.dom实现的是W3C制定的DOM API。
xml.dom.minidom
xml.dom.minidom是DOM API的极简化实现,比完整版的DOM要简单的多,而且这个包也小的多。
xml.dom.pulldom
与其他模块不同,xml.dom.pulldom模块提供的是一个“pull解析器”,其背后的基本概念指的是从XML流中pull事件,然后进行处理。
xml.sax
xml.sax模块实现的是SAX API,这个模块牺牲了便捷性来换取速度和内存占用。SAX是Simple API for XML的缩写,它并不是由W3C官方所提出的标准。
xml.parser.expat
xml.parser.expat提供了对C语言编写的expat解析器的一个直接的、底层API接口。expat接口与SAX类似,也是基于事件回调机制,但是这个接口并不是标准化的,只适用于expat库。
xml.etree.ElementTree(以下简称ET)
xml.etree.ElementTree模块提供了一个轻量级、Pythonic的API,同时还有一个高效的C语言实现,即xml.etree.cElementTree。与DOM相比,ET的速度更快,API使用更直接、方便。与SAX相比,ET.iterparse函数同样提供了按需解析的功能,不会一次性在内存中读入整个文档。ET的性能与SAX模块大致相仿,但是它的API更加高层次,用户使用起来更加便捷。
笔者建议,在使用Python进行XML解析时,首选使用ET模块,除非你有其他特别的需求,可能需要另外的模块来满足。
xml.etree.ElementTree
参考
https://cloud.tencent.com/developer/section/1372094 官方文档的一个翻译版本
https://docs.python.org/3.6/library/xml.etree.elementtree.html 官方文档
将XML文档解析为树(tree)
我们先从基础讲起。XML是一种结构化、层级化的数据格式,最适合体现XML的数据结构就是树。ET提供了两个对象:ElementTree将整个XML文档转化为树,Element则代表着树上的单个节点。对整个XML文档的交互(读取,写入,查找需要的元素),一般是在ElementTree层面进行的。对单个XML元素及其子元素,则是在Element层面进行的。
导入数据
import xml.etree.ElementTree as ET
1从磁盘读取文件:
tree = ET.parse('country_data.xml')
root = tree.getroot()
2从字符串读取数据:
root = ET.fromstring(country_data_as_string)
fromstring()将XML从字符串中直接解析为一个Element,这是解析树的根元素.
另,其他解析函数可能会创建一个ElementTree.
演示数据
<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>
加载解析
接下来,我们加载这个文档,并进行解析:
import xml.etree.ElementTree as ET
tree = ET.ElementTree(file='doc1.xml')
然后,我们获取根元素(root element):
tree.getroot()
<Element 'doc' at 0x11eb780>
正如之前所讲的,根元素(root)是一个Element对象。我们看看根元素都有哪些属性:
root = tree.getroot()
root.tag, root.attrib
('doc', {})
没错,根元素并没有属性。与其他Element对象一样,根元素也具备遍历其直接子元素的接口:
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
通过索引值访问元素(或子元素)
我们还可以通过索引值来访问特定的子元素:
root[0].tag, root[0].text
('branch', '\n text,source\n ')
子节点是嵌套的,我们可以通过索引访问特定的子节点:
root[0][1].text
'2008'
查找需要的元素
避免使用上面的循环递归方式访问(使用ET更便捷).
Element对象有一个iter方法,可以对某个元素对象之下所有的子元素进行深度优先遍历(DFS)。ElementTree对象同样也有这个方法。
下面是查找XML文档中所有元素的最简单方法:
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
在此基础上,我们可以对树进行任意遍历——遍历所有元素,查找出自己感兴趣的属性。但是ET可以让这个工作更加简便、快捷。iter方法可以接受tag名称,然后遍历所有具备所提供tag的元素:
for elem in tree.iter(tag='branch'):
... print elem.tag, elem.attrib
...
branch {'hash': '1cdf045c', 'name': 'codingpy.com'}
branch {'hash': 'f200013e', 'name': 'release01'}
branch
支持通过XPath查找元素
使用XPath查找感兴趣的元素,更加方便。Element对象中有一些find方法可以接受Xpath路径作为参数,
find(match)方法通过tag名字或xpath在第一层子元素中查找第一个匹配的元素,返回匹配的元素或None.-->返回元素
findall(match)方法通过tag名字或xpath匹配第一层子元素,按照子元素顺序以列表形式返回所有匹配的元素. -->返回list
get(key, default=None)返回元素名字为key的属性值,如果没有找到,返回None或设置的默认值.-->返回属性值.Element.get()访问元素的属性.
iter('元素标签') ,对于这个方法,无论子元素有多少层,都可以递归得到元素.返回值是个迭代器(iterator).
iterfind则返回一个所有匹配元素的迭代器(iterator).
Element.text访问元素的文本内容.
Element.attrib元素的属性
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
遍历多层嵌套的元素
(注意这里必须带默认的命名空间,因为原xml带了:
print('+++root.iter+++')
for neighbor in root.iter('{http://xxx/doc/}StateReason'):
print(neighbor.text)
print('-----findall.//-----')
print(root.tag)
for result in root.findall('.//{http://xxx/doc/}StateReason'): # 这里的 "//" 是XPATH方式
print(result, result.text)
上面的for也可以写成(每个路径都带namespace):
for result in root.findall('.//{http://xxx/doc/}DescribeAlarmsResult/{http://xxx/doc/}MetricAlarms/{http://xxx/doc/}member/{http:/xxx/doc/}StateReason'):
XML参考文件:
<country name="Liechtenstein">
<rank>1</rank>
<year>2008</year>
<gdppc>141100</gdppc>
<neighbor name="Austria" direction="E"/>
<neighbor name="Switzerland" direction="W"/>
</country>
<country name="Singapore">
<rank>4</rank>
<year>2011</year>
<gdppc>59900</gdppc>
<neighbor name="Malaysia" direction="N"/>
</country>
<country name="Panama">
<rank>68</rank>
<year>2011</year>
<gdppc>13600</gdppc>
<neighbor name="Costa Rica" direction="W"/>
<neighbor name="Colombia" direction="E"/>
</country></data>
递归遍历
递归地遍历它下面的所有子树(子元素,子元素等等).
例如,Element.iter():
for neighbor in root.iter('neighbor'): # root.iter('元素标签') ,对于这个方法,无论子元素有多少层,都可以递归得到
... print neighbor.attrib
...
{'name': 'Austria', 'direction': 'E'}
{'name': 'Switzerland', 'direction': 'W'}
{'name': 'Malaysia', 'direction': 'N'}
该模块对XPath的部分支持
示例
import xml.etree.ElementTree as ET
root = ET.fromstring(countrydata)
根节点元素
root.findall(".")
All 'neighbor' grand-children of 'country' children of the top-level elements
root.findall("./country/neighbor")
Nodes with name='Singapore' that have a 'year' child
root.findall(".//year/..[@name='Singapore']")
'year' nodes that are children of nodes with name='Singapore'
root.findall(".//*[@name='Singapore']/year")
All 'neighbor' nodes that are the second child of their parent
root.findall(".//neighbor[2]")
支持的XPath语法
句法 含义
tag 选择具有给定标签的所有子元素.
-
选择所有子元素.例如,* /鸡蛋选择所有名为鸡蛋的孙子.
. 选择当前节点.这在路径开始时最有用,表明它是相对路径.
// 选择当前元素下所有级别的所有子元素.例如,// egg选择整棵树中的所有蛋元素.
.. 选择父元素.
@attrib 选择具有给定属性的所有元素.
@attrib='value' 选择给定属性具有给定值的所有元素.该值不能包含引号.
tag 选择具有子名称标签的所有元素.只支持直接的孩子.
tag='text' 选择具有子名称标签的所有元素,其完整文本内容(包括后代)等于给定文本.
position 选择位于给定位置的所有元素.该位置可以是整数(1是第一个位置),表达式last()(用于最后一个位置)或相对于最后位置(例如last()-1)的位置.
谓词(方括号内的表达式)必须以标记名称,星号或其他谓词开头.position谓词必须以标签名称开头.