XML 指可扩展标记语言(eXtensible Markup Language)。html
XML 被设计用来传输和存储数据。python
XML 是一套定义语义标记的规则,这些标记将文档分红许多部件并对这些部件加以标识。编程
它也是元标记语言,即定义了用于定义其余与特定领域有关的、语义的、结构化的标记语言的句法语言。api
常见的 XML 编程接口有 DOM 和 SAX,这两种接口处理 XML 文件的方式不一样,固然使用场合也不一样。函数
Python 有三种方法解析 XML,SAX,DOM,以及 ElementTree:工具
Python 标准库包含 SAX 解析器,SAX 用事件驱动模型,经过在解析XML的过程当中触发一个个的事件并调用用户定义的回调函数来处理XML文件。性能
将 XML 数据在内存中解析成一个树,经过对树的操做来操做XML。学习
ElementTree就像一个轻量级的DOM,具备方便友好的API。代码可用性好,速度快,消耗内存少。this
注:因DOM须要将XML数据映射到内存中的树,一是比较慢,二是比较耗内存,而SAX流式读取XML文件,比较快,占用内存少,但须要用户实现回调函数(handler)。推荐使用ET来处理XML,除非你有什么很是特别的须要。spa
ElementTree 生来就是为了处理 XML ,它在 Python 标准库中有两种实现。一种是纯 Python 实现例如 xml.etree.ElementTree
,另一种是速度快一点的 xml.etree.cElementTree
。你要记住: 尽可能使用 C 语言实现的那种,由于它速度更快,并且消耗的内存更少。若是你的电脑上没有 _elementtree
那么你须要这样作:
try: import xml.etree.cElementTree as ET except ImportError: import xml.etree.ElementTree as ET
这是一个让 Python 不一样的库使用相同 API 的一个比较经常使用的办法。仍是那句话,你的编译环境和别人的极可能不同,因此这样作能够防止一些莫名其妙的小问题。注意:从 Python 3.3 开始,你没有必要这么作了,由于 ElementTree
模块会自动寻找可用的 C 库来加快速度。因此只须要 import xml.etree.ElementTree
就能够了。可是在 3.3 正式推出以前,你最好仍是使用我上面提供的那段代码。
咱们来说点基础的。XML 是一种分级的数据形式,因此最天然的表示方法是将它表示为一棵树。ET 有两个对象来实现这个目的 - ElementTree
将整个 XML 解析为一棵树, Element
将单个结点解析为树。若是是整个文档级别的操做(好比说读,写,找到一些有趣的元素)一般用 ElementTree
。单个 XML 元素和它的子元素一般用 Element
。下面的例子能说明我刚才啰嗦的一大堆。
咱们用这个 XML 文件来作例子:
<?xml version="1.0"?> <doc> <branch name="testing" hash="1cdf045c"> text,source </branch> <branch name="release01" hash="f200013e"> <sub-branch name="subrelease01"> xml,sgml </sub-branch> </branch> <branch name="invalid"> </branch> </doc>
让咱们加载而且解析这个 XML :
>>> import xml.etree.cElementTree as ET >>> tree = ET.ElementTree(file='doc1.xml')
而后抓根结点元素:
>>> 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': 'testing'})
('branch', {'hash': 'f200013e', 'name': 'release01'})
('branch', {'name': 'invalid'})
咱们也能够进入一个指定的子结点:
>>> root[0].tag, root[0].text ('branch', '\n text,source\n ')
从上面的例子咱们能够垂手可得的看到,咱们能够用一个简单的递归获取 XML 中的任何元素。然而,由于这个操做比较广泛,ET 提供了一些有用的工具来简化操做.
Element 对象有一个 iter
方法能够对子结点进行深度优先遍历。(好像本身手敲哦一个dfs...) ElementTree
对象也有 iter
方法来提供便利。
>>> for elem in tree.iter(): ... print(elem.tag, elem.attrib) ... ('doc', {}) ('branch', {'hash': '1cdf045c', 'name': 'testing'}) ('branch', {'hash': 'f200013e', 'name': 'release01'}) ('sub-branch', {'name': 'subrelease01'}) ('branch', {'name': 'invalid'})
遍历全部的元素,而后检验有没有你想要的。ET 可让这个过程更便捷。 iter
方法接受一个标签名字,而后只遍历那些有指定标签的元素:
>>> for elem in tree.iter(tag='branch'): ... print elem.tag, elem.attrib ... branch {'hash': '1cdf045c', 'name': 'testing'} branch {'hash': 'f200013e', 'name': 'release01'} branch {'name': 'invalid'}
为了寻找咱们感兴趣的元素,一个更加有效的办法是使用 XPath 支持。 Element
有一些关于寻找的方法能够接受 XPath 做为参数。 find
返回第一个匹配的子元素, findall
以列表的形式返回全部匹配的子元素, iterfind
为全部匹配项提供迭代器。这些方法在 ElementTree
里面也有。
给出一个例子:
>>> for elem in tree.iterfind('branch/sub-branch'): ... print(elem.tag, elem.attrib) ... ('sub-branch', {'name': 'subrelease01'})
这个例子在 branch
下面找到全部标签为 sub-branch
的元素。而后给出如何找到全部的 branch
元素,用一个指定 name
的状态便可:
>>> for elem in tree.iterfind('branch[@name="release01"]'): ... print(elem.tag, elem.attrib) ... ('branch', {'hash': 'f200013e', 'name': 'release01'})
想要深刻学习 XPath 的话,请看 这里 。
ET 提供了创建 XML 文档和写入文件的便捷方式。 ElementTree
对象提供了 write
方法。
如今,这儿有两个经常使用的写 XML 文档的脚本。
修改文档可使用 Element
对象的方法:
>>> for subelem in root: ... print(subelem.tag, subelem.attrib) ... branch {'foo': 'bar', 'hash': '1cdf045c', 'name': 'testing'} branch {'hash': 'f200013e', 'name': 'release01'}
咱们在这里删除了根元素的第三个子结点,而后为第一个子结点增长新状态。而后这个树能够写回到文件中。
>>> import sys >>> tree.write(sys.stdout) # ET.dump can also serve this purpose, ET.dump(tree) <doc> <branch foo="bar" hash="1cdf045c" name="testing"> 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>
若是要修改好的保存到指定文件
tree.write(new_data.xml)
就像我在文章一开头提到的那样,XML 文档一般比较大,因此将它们所有读入内存的库可能会有点儿小问题。这也是为何我建议使用 SAX API 来替代 DOM 。
咱们刚讲过如何使用 ET 来将 XML 读入内存而且处理。但它就不会碰到和 DOM 同样的内存问题么?固然会。这也是为何这个包提供一个特殊的工具,用来处理大型文档,而且解决了内存问题,这个工具叫 iterparse
。
我给你们演示一个 iterparse
如何使用的例子。我用 自动生成 拿到了一个 XML 文档来进行说明。这只是开头的一小部分:
<?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> [...]
我已经用注释标出了我要处理的元素,咱们用一个简单的脚原本计数有多少 location
元素而且文本内容为“Zimbabwe”。这是用 ET.parse
的一个标准的写法:
tree = ET.parse(sys.argv[2]) count = 0 for elem in tree.iter(tag='location'): if elem.text == 'Zimbabwe': count += 1 print count
全部 XML 树中的元素都会被检验。当处理一个大约 100MB 的 XML 文件时,占用的内存大约是 560MB ,耗时 2.9 秒。
注意:咱们并不须要在内存中加载整颗树。它检测咱们须要的带特定值的 location
元素。其余元素被丢弃。这是 iterparse
的来源:
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() # discard the element print(count)
这个循环遍历 iterparse
事件,检测“闭合的”(end)事件而且寻找 location
标签和指定的值。在这里 elem.clear()
是关键 - iterparse
仍然创建一棵树,只不过不须要所有加载进内存,这样作能够有效的利用内存空间。
处理一样的文件,这个脚本占用内存只须要仅仅的 7MB ,耗时 2.5 秒。速度的提高归功于生成树的时候只遍历一次。相比较来讲, parse
方法首先创建了整个树,而后再次遍从来寻找咱们须要的元素(因此慢了一点)。
在 Python 众多处理 XML 的模块中, ElementTree
真是屌爆了。它将轻量,符合 Python 哲学的 API ,出色的性能完美的结合在了一块儿。因此说若是要处理 XML ,果断地使用它吧!
参考资料:
1. 用 ElementTree 在 Python 中解析 XML