在XML解析方面,它实现了自己的“开箱即用”(out-of-the-box)原则。 内置的标准库提供了大量可用于处理XML语言的包和工具。 数量之大,连新手程序员都没有选择。
本文将深入讲解使用语言解析XML文件的几种方式,并以作者推荐的模块为例演示具体的使用方法和场景。 本文使用的版本是2.7。
什么是XML?
XML是可扩展标记语言( )的缩写,其中标记()是其关键部分。 您可以创建内容,然后用限定标签对其进行标记,使每个单词、短语或信息块成为可识别、可分类的信息。
标记语言从早期私人公司和政府开发的形式演变为标准通用标记语言(SGML)、超文本标记语言(HTML),并最终发展为 XML。 XML 具有以下特点。
目前,XML在Web中所扮演的角色并不亚于HTML,而HTML一直是Web的基石。 XML 无处不在。 XML是各种应用程序之间最常用的数据传输工具,在信息存储和描述领域变得越来越流行。 因此,学习如何解析XML文件对于Web开发来说非常重要。
有哪些包可以解析 XML?
标准库提供了6个可用于处理XML的包。
xml.dom
xml.dom 实现了 W3C 开发的 DOM API。 如果您习惯使用 DOM API 或者有人要求您这样做,请使用此包。 但是,请注意,该软件包还提供了几个不同的模块,每个模块都有不同的性能。
在任何处理开始之前,DOM 解析器必须将基于 XML 文件生成的树数据放入内存中,因此 DOM 解析器的内存使用量完全基于输入数据的大小。
xml.dom。
xml.dom。 是 DOM API 的极简实现,比完整版 DOM 简单很多,包也小很多。 那些不熟悉 DOM 的人应该考虑使用 xml.etree。 模块。 根据lxml作者的评价,这个模块使用起来不方便,效率不高,而且容易出问题。
xml.dom。
与其他模块不同,xml.dom。 模块提供了一个“拉式解析器”。 其背后的基本概念是指从 XML 流中提取事件然后对其进行处理。 虽然它使用与SAX相同的事件驱动模型,但不同的是,使用拉解析器时,用户需要显式地从XML流中拉取事件并遍历和处理这些事件,直到处理完成或发生错误。 。
拉式解析(pull)是最近的 XML 处理趋势。 以前流行的SAX、DOM等XML解析框架都是基于推送的,这意味着解析工作的控制权掌握在解析器手中。
xml.sax
xml.sax 模块实现 SAX API。 该模块牺牲了速度和内存使用的便利性。 SAX 是 API for XML 的缩写。 它不是 W3C 提出的官方标准。 它是事件驱动的,不需要一次性读取整个文档,并且文档读取过程也就是SAX解析过程。 所谓事件驱动是指基于()机制的程序运行方式。
xml..expat
xml..expat 为用 C 编写的 expat 解析器提供了直接的、低级的 API 接口。expat 接口与 SAX 类似,也是基于事件回调机制。 但该接口并未标准化,仅适用于 expat 库。
expat 是一个面向流的解析器。 您注册一个解析器回调(或)函数,然后开始搜索其文档。 当解析器识别出文件的指定位置时,它会调用该部分的相应处理程序(如果您已注册)。 文件被送入解析器,在解析器中它被分割成多个片段并加载到内存中。 所以外籍人士可以解析那些巨大的文件。
xml.etree。 (以下简称ET)
xml.etree。 模块提供了一个轻量级、干净的API,以及一个高效的C语言实现,xml.etree。 与 DOM 相比,ET 速度更快,API 更直接、使用方便。 与SAX相比,ET. 函数还提供按需解析,不会一次性将整个文档读入内存。 ET的性能与SAX模块大致相似,但其API级别更高,更方便用户使用。
作者建议在使用XML解析时,ET模块是首选,除非你有其他特殊需求,可能需要额外的模块来满足。
这些解析XML的API并不是原创的,它们也是从其他语言借用的或者直接从其他语言引入的。 例如expat是一个用C语言开发的用于解析XML文档的开发库。 虽然 SAX 最初是使用 Java 语言开发的,但 DOM 可以以独立于平台和语言的方式访问和修改文档的内容和结构,并且可以应用于任何编程语言。
下面,我们以模块为例,介绍如何解析其中的lxml。
利用解析 XML
标准库提供了 ET 的两种实现。 一种是xml.etree的纯实现,另一种是xml.etree的更快的C语言实现。 请记住始终使用 C 语言实现,因为它速度更快并且消耗的内存更少。 如果您使用的版本中没有所需的加速模块,您可以像这样导入该模块:
try:
import xml.etree.cElementTree as ET
except ImportError:
import xml.etree.ElementTree as ET
如果某个API有不同的实现,以上是常见的导入方式。 当然,也有可能如果直接导入第一个模块,就不会有问题。 请注意,从3.3开始,不需要使用上面的导入方法,因为模块会自动首先使用C加速器,如果不存在C实现,则会使用该实现。 因此,使用3.3+的朋友只需要.etree即可。
将 XML 文档解析为树
让我们从基础开始。 XML是一种结构化、层次化的数据格式,最适合体现XML的数据结构是树。 ET提供了两个对象:将整个XML文档转换成一棵树,它代表树上的单个节点。 与整个 XML 文档的交互(读取、写入、搜索所需元素)通常是在该级别执行的。 对于单个 XML 元素及其子元素,它是在级别上执行的。 下面我们举例介绍主要的使用方法。
我们使用以下 XML 文档作为演示数据:
<?xml version="1.0"?>
.com" hash="1cdf045c">
text,source
xml,sgml
接下来,我们加载该文档并解析它:
>>> import xml.etree.ElementTree as ET
>>> tree = ET.ElementTree(file='doc1.xml')
然后,我们得到根元素(root):
>>> tree.getroot
如前所述,根元素(root)是一个对象。 我们来看看根元素有哪些属性:
>>> root = tree.getroot
>>> root.tag, root.attrib
('doc', {})
没错,根元素没有属性。 与其他对象一样,根元素也有一个用于遍历其直接子元素的接口:
>>> 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'}
我们还可以通过索引值访问特定的子元素:
>>> root[0].tag, root[0].text
('branch', '\n text,source\n ')
找到需要的元素
从上面的例子不难看出,我们可以通过简单的递归方法获取树中的所有元素(对于每个元素,递归访问其所有子元素)。 然而,由于这是一个非常常见的任务,ET 提供了一些简单的方法来实现它。
该对象有一个iter方法,可以对一个元素对象下的所有子元素进行深度优先遍历(DFS)。 对象也有这个方法。 以下是查找 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 {'name': 'invalid'}
在此基础上,我们可以任意遍历这棵树——遍历所有元素,找到我们感兴趣的属性。但是ET可以让这个工作变得更简单、更快。 iter 方法接受标签名称并迭代具有所提供标签的所有元素:
>>> 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'}
支持通过 XPath 查找元素
使用 XPath 来查找感兴趣的元素更加方便。 对象中有一些 find 方法可以接受 Xpath 路径作为参数。 find方法会返回第一个匹配的子元素,以列表的形式返回所有匹配的子元素,并返回所有匹配元素的()。 对象也有这些方法,因此它们的搜索从根节点开始。
下面是使用 XPath 查找元素的示例:
>>> for elem in tree.iterfind('branch/sub-branch'):
... print elem.tag, elem.attrib
...
sub-branch {'name': 'subrelease01'}
上面的代码返回元素下方带有 sub- 标签的所有元素。 接下来查找具有特定名称属性的所有元素:
>>> for elem in tree.iterfind('branch[@name="release01"]'):
... print elem.tag, elem.attrib
...
branch {'hash': 'f200013e', 'name': 'release01'}
构建 XML 文档
使用ET,可以轻松完成XML文档的构建并写入并保存为文件。 对象的write方法可以实现这个需求。
一般来说,主要有两种使用场景。 一种是首先读取 XML 文档,进行修改,然后将修改写入该文档。 另一种是从头开始创建一个新的 XML 文档。
如果修改文档,可以通过调整对象来完成。 请看下面的例子:
>>> 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'}
在上面的代码中,我们删除了根元素的第三个子元素,并为第一个子元素添加了一个新属性。 该树可以重写为文件。 最终的 XML 文档应如下所示:
>>> import sys
>>> tree.write(sys.stdout)
.com">
text,source
xml,sgml
请注意,文档中元素属性的顺序与原始文档不同。 这是因为ET以字典的形式保存属性,而字典是一种无序的数据结构。 当然,XML 也不关心属性的顺序。
从头开始构建完整的文档也很容易。 ET模块提供了一个工厂函数,使创建元素的过程变得非常简单:
>>> 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)
some text
利用解析 XML 流
XML 文档通常比较大。 如果直接将文档读入内存,解析时就会出现问题。 这也是不推荐使用DOM而是使用SAX API的原因之一。
正如我们上面提到的,ET 可以将 XML 文档作为存储在内存中的树(in-tree)加载,然后对其进行处理。 但是在解析大文件时,这应该也会造成和DOM一样的内存消耗问题吧? 是的,这个问题确实存在。 为了解决这个问题,ET提供了一个类似SAX的特殊工具,可以顺序解析XML。
接下来,作者向您展示如何使用它,并与标准的树解析方法进行比较。 我们使用自动生成的 XML 文档。 这是文档的开头:
<?xml version="1.0" standalone="yes"?>
United States
1
duteous nine eighteen
Creditcard
[...]
让我们计算一下文档中出现了多少个具有文本值的元素。 这是使用 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
上面的代码会将所有元素加载到内存中并一一解析。 在解析约100MB的XML文档时,运行上述脚本的进程的内存使用峰值约为560MB,总运行时间为2.9秒。
请注意,我们实际上不需要将整个树加载到内存中。 只需将文本检测为相应的值元素即可。 所有其他数据都可以被丢弃。 这时候我们可以使用方法:
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循环会遍历事件,首先检查事件是否结束,然后判断元素的标签是否结束以及其文本值是否满足目标值。 另外,调用elem.clear非常关键:因为仍然会生成一棵树,只是按顺序生成。 丢弃不必要的元素相当于丢弃整棵树,释放系统分配的内存。
使用上述脚本解析同一个文件时,内存使用峰值仅为7MB,运行时间为2.5秒。 速度提高的原因是我们在构建树时只遍历了一次树。 使用parse的标准方法是完成整个树的构建,然后再再次遍历以找到所需的元素。
性能与SAX相当,但它的API更有用:它按顺序构建树; 使用SAX,你必须自己完成树的构建。