《Python在Unix和Linux系统管理中的应用》第3章文本,本章讲述了在Python中操作文本的一些基本技术。然后对其中的一些技术进行联合,应用到两个解析Apache日志文件的示例中。最后介绍了ElementTree库的一些经典用法,并通过两个示例演示了实际应用效果。本节为大家介绍ElementTree。
ElementTree(1)
如果需要解析的文本是XML,那么你需要一种不同于处理面向行的日志文件的处理方式。你可能不想一行接一行地读入文件、查找模式,也不想太多地依赖正则表达式。XML使用一种树结构,因此你并不希望对其按行读取。而使用正则表达式来建立一个树状数据结构对于处理任何略微大一点的文件来说,都是一件十分令人头痛的事。
这样的话,还能使用什么呢?有两种典型方法可以处理XML。一种是“simple API forXML,”或者称为 SAX。Python标准库中包括了SAX解析器。SAX是一种典型的极为快速的工具,在解析XML时,不会自动占用大量内存。但是这是基于回调机制的,因此在某些数据中,当它命中XML文档的某些部分时(例如开始和结束标签),它会调用某些方法并进行传递。这意味着必须为数据指定句柄,以维持自己的状态,而这是非常困难的。这两件事情使得“simple”在“simpleAPI for XML”中看起来有些牵强。另一个操作XML的方法是使用Document Object Model, 或者称为DOM。Python标准库也包括一个DOM XML库。与SAX相比,DOM是典型的比较慢,消耗更多内存的方法,因为DOM会将整个XML树读入到内存中,并为树中的第一个节点建立一个对象。使用DOM的好处是你不需要对状态进行追踪,因为每一个节点都知道谁是它的父节点,谁是它的子节点。但是DOM API使用起来多少有些麻烦。
第三种选择是使用ElementTree(元素树)。ElementTree是XML解析库,已经在Python2.5之后被包括在标准库中。ElementTree感觉就像一个轻量级的DOM,具有方便使用、十分友好的API。除了代码可复用之外,它运行速度快,消耗内存较少。这里我们重点推荐使用ElementTree。如果需要使用XML解析器,不妨先试一试ElementTree。
使用ElementTree开始解析XML文件,只须简单地加载库和使用parse()对文件进行处理:
- In [1]: from xml.etree import ElementTree as ET
- In [2]: tcusers = ET.parse('/etc/tomcat5.5/tomcat-users.xml')
- In [3]: tcusers
- Out[3]: <xml.etree.ElementTree.ElementTree instance at 0xabb4d0></xml>
为了可以在使用库时省去不必要的键盘输入,我们使用名字ET来加载ElementTree模块,这样在使用该库时就可以使用ET,而不用输入全称。接下来,我们告诉ElementTree解析用户的XML文件,该文件来自一个已成功安装的Tomcat servlet引擎。我们称ElementTree对象为tcusers。tcusers的类型是xml.etree.Elementtree.ElementTree。
在删除了授权许可和用法提示之后,可以看到解析后的Tomcat用户文件内容如下:
- <?xml version="1.0" encoding="UTF-8"?>
- <tomcat-users>
- <user name="tomcat" password="tomcat" roles="tomcat" />
- <user name="role1" password="tomcat" roles="role1" />
- <user name="both" password="tomcat" roles="tomcat,role1" />
- </tomcat-users>
ElementTree在解析Tomcat XML文件时创建了一个树对象,我们使用tcusers表示,这样就可以获得XML文件中的任何节点。在树对象中,两个最有意义的方法是f i n d()和findall()。find()根据传递给它的查询内容查找匹配查询的第一个节点,并返回一个基于该节点的e l e m e n t对象。f i n d a l l()会对匹配查询的所有节点进行查找,并返回一个element对象列表,该列表由找到的所有匹配的节点组成。
find()和findall()寻找的模式的类型都是XPath表达式的有限子集。ElementTree的有效搜索格式包括标签名*(匹配所有子元素)、.(匹配当前元素)和//(匹配搜索起启点的所有子孙节点)。斜杠(/)可以用来分隔匹配格式。利用Tomcat 用户文件,我们可以使用find()提取第一个用户节点的标签名:
- In [4]: first_user = tcusers.find('/user')
- In [5]: first_user
- Out[5]: <Element user at abdd88>
给find()指定搜索格式为“/user”。最前面的斜杠定义了绝对路径,即从根节点开始搜索。文本“user”定义了要查找的标签。因此,find()返回标签是“user”的第一个节点。可以看到,引用的对象first_user是element类型。
一些更有趣的e l e m e n t方法和属性包括A t t r i b、f i n d()、f i n d a l l()、g e t()、t a g和text。attrib是一个元素属性字典。find()和findall()与ElementTree对象采用相同的处理方式。g e t()是一个字典方法,可以获取指定的属性,如果没有定义属性,返回为空。attrib和get()都可以为当前的XML标签使用相同的属性字典。Tag是包含当前元素标签名的属性。text是当前元素作为文本节点时所具有的文本的属性。
以下是一个XML元素ElementTree,是为fist_user元素对象创建的:
- <user name="tomcat" password="tomcat" roles="tomcat" />
现在调用方法并且引用tcusers对象的属性:
- In [6]: first_user.attrib
- Out[6]: {'name': 'tomcat', 'password': 'tomcat', 'roles': 'tomcat'}
- In [7]: first_user.get('name')
- Out[7]: 'tomcat'
- In [8]: first_user.get('foo')
- In [9]: first_user.tag
- Out[9]: 'user'
- In [10]: first_user.text
至此,你已经看到了一些ElementTree如何使用的基本示例。接下来看一个稍微深入且更通用的示例。我们解析Tomcat用户文件并且搜索name属性匹配我们指定内容(在本例中为'tomcat')的用户节点(参见例3-27)。
例3-27:ElementTree解析Tomcat用户文件
- #!/usr/bin/env python
- from xml.etree import ElementTree as ET
- if __name__ == '__main__':
- infile = '/etc/tomcat5.5/tomcat-users.xml'
- tomcat_users = ET.parse(infile)
- for user in [e for e in tomcat_users.findall('/user') if
- e.get('name') == 'tomcat']:
- print user.attrib
ElementTree(2)
示例中,我们只是使用了一个复合列表来匹配n a m e属性。运行这个示例将返回下面的结果:
- jmjones@dinkgutsy:code$ python elementtree_tomcat_users.py
- {'password': 'tomcat', 'name': 'tomcat', 'roles': 'tomcat'}
最后是一个ElementTree示例,该示例用来从一个写得不是很好的XML中提取信息。Mac OS X有一个称为s y s t e m_p r o f i l e r的工具,可以显示系统的大量信息。XML是system_profiler支持的两种输出格式之一,但是对XML的支持似乎来得晚一些。希望被提取出来的信息内容是操作系统的版本号,包括在类似下面这样的XML文件中:
- <dict>
- <key>_dataType</key>
- <string>SPSoftwareDataType</string>
- <key>_detailLevel</key>
- <integer>-2</integer>
- <key>_items</key>
- <array>
- <dict>
- <key>_name</key>
- <string>os_overview</string>
- <key>kernel_version</key>
- <string>Darwin 8.11.1</string>
- <key>os_version</key>
- <string>Mac OS X 10.4.11 (8S2167)</string>
- </dict>
- </array>
为什么我们认为这个XML格式写得不规范呢?因为在任何一个XML标签中都没有属性。标签类型是主要的数据类型。一些元素,如可替换的k e y、s t r i n g标签,位于相同的父节点下(参见例3-28)。
例3-28:Mac OS X system_profiler输出解析
- #!/usr/bin/env python
- import sys
- from xml.etree import ElementTree as ET
- e = ET.parse('system_profiler.xml')
- if __name__ == '__main__':
- for d in e.findall('/array/dict'):
- if d.find('string').text == 'SPSoftwareDataType':
- sp_data = d.find('array').find('dict')
- break
- else:
- print "SPSoftwareDataType NOT FOUND"
- sys.exit(1)
- record = []
- for child in sp_data.getchildren():
- record.append(child.text)
- if child.tag == 'string':
- print "%-15s -> %s" % tuple(record)
- record = []
基本上, 脚本搜索d i c t 标签, 该标签有一个字符串子元素, 它的文本值为“SPSoftwareDataType”。脚本搜索的信息在该节点下。在这个示例中,我们之前没有介绍过的唯一内容是g e t c h i l d r e n()方法。该方法可以简单地返回一个指定元素的子节点列表。另外,该示例条理非常清晰,XML也写得更好一些。下面是该脚本运行在MacOS X Tiger笔记本上产生的输出结果:
- dink:~/code jmjones$ python elementtree_system_profile.py
- _name -> os_overview
- kernel_version -> Darwin 8.11.1
- os_version -> Mac OS X 10.4.11 (8S2167)
ElementTree是Python标准库的有力补充。到目前为止,我们已经多次使用,并且对使用它所带来的好处非常满意。可以尝试一下Python标准库中的SAX和DOM库,但是我们认为在你尝试之后仍会回来选择使用ElementTree。
本章小结
本章讲述了在Python中操作文本的一些基本技术。我们使用了来自标准库的内建string类型、正则表达式、标准输入输出、StringIO以及urllib模块。然后对其中的一些技术进行联合,应用到两个解析Apache日志文件的示例中。最后介绍了ElementTree库的一些经典用法,并通过两个示例演示了实际应用效果。
一些UNIX使用者认为当复杂的文本处理已经超出了使用grep或awk所能应对的程度时,这种情况下,他们只考虑选用Perl作为更高级的替代工具。Perl是一个功能极为强大的语言,特别是在文本处理方面,然而我们认为Python具有与Perl一样优秀的性能。事实上,如果看到Python简单的语法,以及可以轻松实现由面向过程的编码方式到面向对象的编码方式的转变,你会认为Python比Perl更为优秀,甚至在文本处理方面同样如此。因此,我们希望你下次从事文本处理工作时,首先选择Python。