当前位置: 首页 > 科技观察

深入解读Python解析XML的几种方式

时间:2023-03-19 00:25:46 科技观察

在XML解析方面,Python实现了自己“开箱即用”(自带电池)的原则。在内置的标准库中,Python提供了大量可用于处理XML语言的包和工具。数量之多,连Python编程的新手都无从选择。本文将深入解读几种使用Python语言解析XML文件的方法,并以笔者推荐的ElementTree模块为例,演示具体的使用方法和场景。本文使用的Python版本为2.7。什么是XML?XML是ExtensibleMarkupLanguage的缩写,标记是其中的关键部分。您可以创建内容,然后使用限定标签对其进行标记,使每个单词、短语或块成为可识别、可分类的信息片段。标记语言从早期的私人公司和政府规定的形式演变为标准通用标记语言(SGML)、超文本标记语言(HTML),最后是XML。XML具有以下特点。XML旨在传输数据,而不是显示数据。XML标记不是预定义的。您需要自己定义标签。XML被设计为自描述的。XML是W3C推荐标准。目前,XML在Web中的作用不亚于HTML,它一直是Web的基石。XML无处不在。XML是各种应用程序之间最常用的数据传输工具,在信息存储和描述领域也越来越流行。因此,学习如何解析XML文件对于Web开发来说是非常重要的。有哪些可以解析XML的Python包?在Python的标准库中,有6个包可以用来处理XML。xml.domxml.dom实现了W3C制定的DOMAPI。如果您习惯于使用DOMAPI或有人要求您这样做,请使用此包。不过需要注意的是,在这个包中,还提供了几个不同的模块,它们的表现各不相同。DOM解析器必须在开始任何处理之前将基于XML文件生成的树状数据放入内存中,因此DOM解析器的内存使用量完全取决于输入数据的大小。xml.dom.minidomxml.dom.minidom是DOMAPI的简化实现,比完整版的DOM简单很多,包也小很多。不熟悉DOM的人应该考虑使用xml.etree.ElementTree模块。根据lxml作者的评价,这个模块使用起来不方便,效率不高,容易出问题。xml.dom.pulldom与其他模块不同。xml.dom.pulldom模块提供了一个“拉解析器”。它背后的基本概念是指从XML流中提取事件,然后对其进行处理。虽然它使用了与SAX相同的事件驱动处理模型,但不同的是,在使用拉式解析器时,用户需要显式地从XML流中拉取事件,并对这些事件进行遍历和处理,直到处理完成或发生错误.拉式解析(pullparsing)是最近的XML处理趋势。以前流行的XML解析框架如SAX和DOM是基于推送的,这意味着解析工作的控制权在解析器手中。xml.saxxml.sax模块实现SAXAPI。该模块牺牲了速度和内存使用的便利性。SAX是SimpleAPIforXML的缩写,不是W3C提出的官方标准。它是事件驱动的,不需要一次性读取整个文档,读取文档的过程也就是SAX的解析过程。所谓事件驱动是指基于回调机制的程序运行方式。xml.parser.expatxml.parser.expat为用C编写的expat解析器提供了一个直接的、低级的API接口。expat接口类似于SAX,也是基于事件回调机制,但这个接口不是标准化的,只是适用于外籍人士图书馆。expat是一个面向流的解析器。您注册解析器回调(或处理程序)函数,然后开始搜索其文档。当解析器在指定位置识别文件时,它会调用该部分的适当处理程序(如果您已经注册了一个)。该文件被提供给解析器,在那里它被分解成片段并被加载到内存中。所以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的API并不是Python独有的,Python也有借用其他语言或直接从其他语言引入的。例如expat是用C语言开发的用于解析XML文档的开发库。虽然SAX最初是由DavidMegginson使用java语言开发的,但DOM可以以独立于平台和语言的方式访问和修改文档的内容和结构,并且可以应用于任何编程语言。下面我们以ElementTree模块为例,介绍如何在Python中解析lxml。使用ElementTree解析XMLPython标准库,提供了两种ET的实现。一种是xml.etree.ElementTree的纯Python实现,另一种是xml.etree.cElementTree的更快的C语言实现。请记住始终使用C语言实现,因为它速度更快,内存消耗也更少。如果你使用的Python版本没有cElementTree需要的加速模块,可以这样导入模块:常用的导入方法。当然,很有可能直接导入第一个模块就没有问题。请注意,从Python3.3开始,上述导入方法不是必需的,因为ElemenTree模块会自动优先使用C加速器,如果没有C实现,它将使用Python实现。所以使用Python3.3+的朋友只需要importxml.etree.ElementTree即可。将XML文档解析为树让我们从基础开始。XML是一种结构化、层次化的数据格式,最适合XML的数据结构是树。ET提供了两个对象:ElementTree将整个XML文档转换成一棵树,Element表示树上的单个节点。整个XML文档的交互(读取、写入和查找所需元素)通常在ElementTree级别执行。对于单个XML元素及其子元素,它在元素级别执行。下面我们结合实例介绍主要的使用方法。我们使用以下XML文档作为演示数据:text,sourcexml,sgml接下来,我们加载这个文档,并解析:>>>importxml.etree.ElementTreeasET>>>tree=ET.ElementTree(file='doc1.xml')然后,我们得到根元素(rootelement):>>>tree.getroot()如前所述,根元素(root)是一个Element对象。让我们看看根元素有哪些属性:>>>root=tree.getroot()>>>root.tag,root.attrib('doc',{})是的,根元素没有属性。与其他Element对象一样,根元素也有一个接口来遍历它的直接子元素:>>>forchild_of_rootinroot:...printchild_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','\ntext,source\n')找到需要的元素从上面的例子中,很明显我们可以使用一个简单的递归方法(对于每个An元素,递归访问其所有子元素)获取树中的所有元素。然而,由于这是一项很常见的工作,ET提供了一些简单的方法来完成它。Element对象有一个iter方法,可以对一个元素对象下的所有子元素进行深度优先遍历(DFS)。ElementTree对象也有这个方法。这是在XML文档中查找所有元素的最简单方法:>>>forelemtree.iter():...printelem.tag,elem.attrib...doc{}branch{'hash':'1cdf045c','name':'codingpy.com'}branch{'hash':'f200013e','name':'release01'}sub-branch{'name':'subrelease01'}branch{'name':'invalid'}这里基本上,我们可以任意遍历树——遍历所有元素并找出我们感兴趣的属性。但是ET可以使这项工作变得更容易和更快。iter方法可以接受一个标签名,然后遍历所有带有提供标签的元素:>>>forelemtree.iter(tag='branch'):...printelem.tag,elem.attrib...branch{'hash':'1cdf045c','name':'codingpy.com'}branch{'hash':'f200013e','name':'release01'}branch{'name':'invalid'}支持XPath查找元素使用XPath查找感兴趣的元素,比较方便。Element对象中有一些查找方法可以接受XPath路径作为参数。find方法会返回第一个匹配的子元素,findall会以列表的形式返回所有匹配的子元素,iterfind会返回所有匹配元素的迭代器(iterator)。ElementTree对象也有这些方法,因此它的搜索从根节点开始。下面是使用XPath查找元素的示例:>>>forelemintree.iterfind('branch/sub-branch'):...printelem.tag,elem.attrib...sub-branch{'name':'subrelease01'}上面的代码返回分支元素下标记为子分支的所有元素。接下来,找到所有具有名称属性的分支元素:>>>forelemintree.iterfind('branch[@name="release01"]'):...printelem.tag,elem.attrib...branch{'hash':'f200013e','name':'release01'}构建XML文档使用ET可以轻松完成XML文档的构建,并写入并保存为文件。ElementTree对象的write方法可以实现这个需求。一般来说,主要有两种使用场景。您要么读取XML文档,进行更改,然后将更改写入文档,要么从头开始创建新的XML文档。可以通过调整元素对象来修改文档。请看下面的例子:>>>root=tree.getroot()>>>delroot[2]>>>root[0].set('foo','bar')>>>forsubeleminroot:...printsubelem.tag,subelem.attrib...branch{'foo':'bar','hash':'1cdf045c','name':'codingpy.com'}branch{'hash':'f200013e','name':'release01'}在上面的代码中,我们删除了根元素的第三个子元素,并为第一个子元素添加了一个新属性。这棵树可以写回文件。最终的XML文档应如下所示:>>>importsys>>>tree.write(sys.stdout)text,sourcexml,sgml请注意元素的属性该文件的顺序与原始文件不同。这是因为ET以字典的形式存储属性,而字典是一种无序的数据结构。当然,XML也不关心属性的顺序。从头开始构建完整的文档也很容易。ET模块提供了SubElement工厂函数,使得创建元素的过程非常简单:>>>a=ET.Element('elem')>>>c=ET.SubElement(a,'child1')>>>c.text="sometext">>>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)sometext使用iterparse解析XML流XML文档通常比较大。如果直接把文档读入内存,解析的时候就会出问题。这也是为什么不推荐DOM,而推荐SAXAPI的原因之一。正如我们上面提到的,ET可以将XML文档加载为内存中的树,然后对其进行处理。但是在解析大文件的时候,这应该也会有和DOM一样的内存消耗问题吧?是的,确实存在这个问题。为了解决这个问题,ET提供了一个类似SAX的专用工具——iterparse,可以顺序解析XML。接下来笔者将向大家展示iterparse的使用方法,并与标准的树解析方式进行对比。我们使用一个自动生成的XML文档,这里是文档的开头:UnitedStates1duteousnineeighteenCreditcard[...]让我们数一数看看如何文档中出现了许多文本值为Zimbabwe的位置元素。下面是使用ET.parse的标准方法:tree=ET.parse(sys.argv[2])count=0forelemintree.iter(tag='location'):ifelem.text=='Zimbabwe':count+=1printcount以上代码会将所有元素加载到内存中,并一一解析。在解析一个大约100MB的XML文档时,运行上述脚本的Python进程的峰值内存使用量约为560MB,总运行时间为2.9秒。请注意,我们实际上并不需要将整棵树加载到内存中。只要将文本检测为相应的位置元素。可以丢弃所有其他数据。这时候我们可以使用iterparse方法:count=0forevent,eleminET.iterparse(sys.argv[2]):ifevent=='end':ifelem.tag=='location'andelem.text=='Zimbabwe':count+=1elem.clear()#丢弃元素printcount上面的for循环会遍历iterparse事件,先检查事件是否结束,然后判断元素的tag是否为location,其text值是否满足目标值。此外,调用elem.clear()很关键:因为iterparse仍然会生成一棵树,只是顺序地。丢弃不需要的元素,相当于丢弃整棵树,释放系统分配的内存。使用上述脚本解析同一文件时,内存使用峰值仅为7MB,运行时间为2.5秒。速度提高的原因是我们只在构建树时遍历一次树。parse的标准使用方式是先完成整棵树的构建,然后再遍历寻找需要的元素。iterparse的性能与SAX相当,但它的API更有用:iterparse将顺序构建树;使用SAX时,您必须自己完成树构造。