详细解读Python中解析XML数据的方法


Posted in Python onOctober 15, 2015

Python可以使用 xml.etree.ElementTree 模块从简单的XML文档中提取数据。 为了演示,假设你想解析Planet Python上的RSS源。下面是相应的代码:

from urllib.request import urlopen
from xml.etree.ElementTree import parse

# Download the RSS feed and parse it
u = urlopen('http://planet.python.org/rss20.xml')
doc = parse(u)

# Extract and output tags of interest
for item in doc.iterfind('channel/item'):
  title = item.findtext('title')
  date = item.findtext('pubDate')
  link = item.findtext('link')

  print(title)
  print(date)
  print(link)
  print()

运行上面的代码,输出结果类似这样:

Steve Holden: Python for Data Analysis
Mon, 19 Nov 2012 02:13:51 +0000
http://holdenweb.blogspot.com/2012/11/python-for-data-analysis.html

Vasudev Ram: The Python Data model (for v2 and v3)
Sun, 18 Nov 2012 22:06:47 +0000
http://jugad2.blogspot.com/2012/11/the-python-data-model.html

Python Diary: Been playing around with Object Databases
Sun, 18 Nov 2012 20:40:29 +0000
http://www.pythondiary.com/blog/Nov.18,2012/been-...-object-databases.html

Vasudev Ram: Wakari, Scientific Python in the cloud
Sun, 18 Nov 2012 20:19:41 +0000
http://jugad2.blogspot.com/2012/11/wakari-scientific-python-in-cloud.html

Jesse Jiryu Davis: Toro: synchronization primitives for Tornado coroutines
Sun, 18 Nov 2012 20:17:49 +0000
http://feedproxy.google.com/~r/EmptysquarePython/~3/_DOZT2Kd0hQ/

很显然,如果你想做进一步的处理,你需要替换 print() 语句来完成其他有趣的事。

在很多应用程序中处理XML编码格式的数据是很常见的。 不仅因为XML在Internet上面已经被广泛应用于数据交换, 同时它也是一种存储应用程序数据的常用格式(比如字处理,音乐库等)。 接下来的讨论会先假定读者已经对XML基础比较熟悉了。

在很多情况下,当使用XML来仅仅存储数据的时候,对应的文档结构非常紧凑并且直观。 例如,上面例子中的RSS订阅源类似于下面的格式:

<?xml version="1.0"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Planet Python</title>
    <link>http://planet.python.org/</link>
    <language>en</language>
    <description>Planet Python - http://planet.python.org/</description>
    <item>
      <title>Steve Holden: Python for Data Analysis</title>
      <guid>http://holdenweb.blogspot.com/...-data-analysis.html</guid>
      <link>http://holdenweb.blogspot.com/...-data-analysis.html</link>
      <description>...</description>
      <pubDate>Mon, 19 Nov 2012 02:13:51 +0000</pubDate>
    </item>
    <item>
      <title>Vasudev Ram: The Python Data model (for v2 and v3)</title>
      <guid>http://jugad2.blogspot.com/...-data-model.html</guid>
      <link>http://jugad2.blogspot.com/...-data-model.html</link>
      <description>...</description>
      <pubDate>Sun, 18 Nov 2012 22:06:47 +0000</pubDate>
    </item>
    <item>
      <title>Python Diary: Been playing around with Object Databases</title>
      <guid>http://www.pythondiary.com/...-object-databases.html</guid>
      <link>http://www.pythondiary.com/...-object-databases.html</link>
      <description>...</description>
      <pubDate>Sun, 18 Nov 2012 20:40:29 +0000</pubDate>
    </item>
    ...
  </channel>
</rss>

xml.etree.ElementTree.parse() 函数解析整个XML文档并将其转换成一个文档对象。 然后,你就能使用 find() 、iterfind() 和 findtext() 等方法来搜索特定的XML元素了。 这些函数的参数就是某个指定的标签名,例如 channel/item 或 title 。

每次指定某个标签时,你需要遍历整个文档结构。每次搜索操作会从一个起始元素开始进行。 同样,每次操作所指定的标签名也是起始元素的相对路径。 例如,执行 doc.iterfind('channel/item') 来搜索所有在 channel 元素下面的 item 元素。 doc 代表文档的最顶层(也就是第一级的 rss 元素)。 然后接下来的调用 item.findtext() 会从已找到的 item 元素位置开始搜索。

ElementTree 模块中的每个元素有一些重要的属性和方法,在解析的时候非常有用。 tag 属性包含了标签的名字,text 属性包含了内部的文本,而 get() 方法能获取属性值。例如:

>>> doc
<xml.etree.ElementTree.ElementTree object at 0x101339510>
>>> e = doc.find('channel/title')
>>> e
<Element 'title' at 0x10135b310>
>>> e.tag
'title'
>>> e.text
'Planet Python'
>>> e.get('some_attribute')
>>>

有一点要强调的是 xml.etree.ElementTree 并不是XML解析的唯一方法。 对于更高级的应用程序,你需要考虑使用 lxml 。 它使用了和ElementTree同样的编程接口,因此上面的例子同样也适用于lxml。 你只需要将刚开始的import语句换成 from lxml.etree import parse 就行了。 lxml 完全遵循XML标准,并且速度也非常快,同时还支持验证,XSLT,和XPath等特性。

增量式解析大型XML文件
任何时候只要你遇到增量式的数据处理时,第一时间就应该想到迭代器和生成器。 下面是一个很简单的函数,只使用很少的内存就能增量式的处理一个大型XML文件:

from xml.etree.ElementTree import iterparse

def parse_and_remove(filename, path):
  path_parts = path.split('/')
  doc = iterparse(filename, ('start', 'end'))
  # Skip the root element
  next(doc)

  tag_stack = []
  elem_stack = []
  for event, elem in doc:
    if event == 'start':
      tag_stack.append(elem.tag)
      elem_stack.append(elem)
    elif event == 'end':
      if tag_stack == path_parts:
        yield elem
        elem_stack[-2].remove(elem)
      try:
        tag_stack.pop()
        elem_stack.pop()
      except IndexError:
        pass

为了测试这个函数,你需要先有一个大型的XML文件。 通常你可以在政府网站或公共数据网站上找到这样的文件。 例如,你可以下载XML格式的芝加哥城市道路坑洼数据库。 在写这本书的时候,下载文件已经包含超过100,000行数据,编码格式类似于下面这样:

假设你想写一个脚本来按照坑洼报告数量排列邮编号码。你可以像这样做:

from xml.etree.ElementTree import parse
from collections import Counter

potholes_by_zip = Counter()

doc = parse('potholes.xml')
for pothole in doc.iterfind('row/row'):
  potholes_by_zip[pothole.findtext('zip')] += 1
for zipcode, num in potholes_by_zip.most_common():
  print(zipcode, num)

这个脚本唯一的问题是它会先将整个XML文件加载到内存中然后解析。 在我的机器上,为了运行这个程序需要用到450MB左右的内存空间。 如果使用如下代码,程序只需要修改一点点:

from collections import Counter

potholes_by_zip = Counter()

data = parse_and_remove('potholes.xml', 'row/row')
for pothole in data:
  potholes_by_zip[pothole.findtext('zip')] += 1
for zipcode, num in potholes_by_zip.most_common():
  print(zipcode, num)

结果是:这个版本的代码运行时只需要7MB的内存?大大节约了内存资源。

讨论
这里技术会依赖 ElementTree 模块中的两个核心功能。 第一,iterparse() 方法允许对XML文档进行增量操作。 使用时,你需要提供文件名和一个包含下面一种或多种类型的事件列表: start , end, start-ns 和 end-ns 。 由 iterparse() 创建的迭代器会产生形如 (event, elem) 的元组, 其中 event 是上述事件列表中的某一个,而 elem 是相应的XML元素。例如:

>>> data = iterparse('potholes.xml',('start','end'))
>>> next(data)
('start', <Element 'response' at 0x100771d60>)
>>> next(data)
('start', <Element 'row' at 0x100771e68>)
>>> next(data)
('start', <Element 'row' at 0x100771fc8>)
>>> next(data)
('start', <Element 'creation_date' at 0x100771f18>)
>>> next(data)
('end', <Element 'creation_date' at 0x100771f18>)
>>> next(data)
('start', <Element 'status' at 0x1006a7f18>)
>>> next(data)
('end', <Element 'status' at 0x1006a7f18>)
>>>

start 事件在某个元素第一次被创建并且还没有被插入其他数据(如子元素)时被创建。 而 end 事件在某个元素已经完成时被创建。 尽管没有在例子中演示, start-ns 和 end-ns 事件被用来处理XML文档命名空间的声明。

这本节例子中, start 和 end 事件被用来管理元素和标签栈。 栈代表了文档被解析时的层次结构, 还被用来判断某个元素是否匹配传给函数 parse_and_remove() 的路径。 如果匹配,就利用 yield 语句向调用者返回这个元素。

在 yield 之后的下面这个语句才是使得程序占用极少内存的ElementTree的核心特性:

elem_stack[-2].remove(elem)

这个语句使得之前由 yield 产生的元素从它的父节点中删除掉。 假设已经没有其它的地方引用这个元素了,那么这个元素就被销毁并回收内存。

对节点的迭代式解析和删除的最终效果就是一个在文档上高效的增量式清扫过程。 文档树结构从始自终没被完整的创建过。尽管如此,还是能通过上述简单的方式来处理这个XML数据。

这种方案的主要缺陷就是它的运行性能了。 我自己测试的结果是,读取整个文档到内存中的版本的运行速度差不多是增量式处理版本的两倍快。 但是它却使用了超过后者60倍的内存。 因此,如果你更关心内存使用量的话,那么增量式的版本完胜。

Python 相关文章推荐
python分割和拼接字符串
Nov 01 Python
python实现监控windows服务并自动启动服务示例
Apr 17 Python
详解Python当中的字符串和编码
Apr 25 Python
Python之re操作方法(详解)
Jun 14 Python
用python写个自动SSH登录远程服务器的小工具(实例)
Jun 17 Python
Python使用ConfigParser模块操作配置文件的方法
Jun 29 Python
Python中注释(多行注释和单行注释)的用法实例
Aug 28 Python
python线程信号量semaphore使用解析
Nov 30 Python
如何写python的配置文件
Jun 07 Python
使用Python中tkinter库简单gui界面制作及打包成exe的操作方法(二)
Oct 12 Python
Python调用飞书发送消息的示例
Nov 10 Python
总结Pyinstaller打包的高级用法
Jun 28 Python
深入解析Python编程中JSON模块的使用
Oct 15 #Python
使用Python解析JSON数据的基本方法
Oct 15 #Python
深入讲解Python编程中的字符串
Oct 14 #Python
Python编程中字符串和列表的基本知识讲解
Oct 14 #Python
Python循环语句之break与continue的用法
Oct 14 #Python
Python编程中的for循环语句学习教程
Oct 14 #Python
在Python的while循环中使用else以及循环嵌套的用法
Oct 14 #Python
You might like
PHP 进程锁定问题分析研究
2009/11/24 PHP
PHP学习之字符串比较和查找
2011/04/17 PHP
php使用ICQ网关发送手机短信
2013/10/30 PHP
PHP PDOStatement::fetchColumn讲解
2019/01/31 PHP
Laravel Eloquent分表方法并使用模型关联的实现
2019/11/25 PHP
PHP实现本地图片转base64格式并上传
2020/05/29 PHP
jQuery 点击图片跳转上一张或下一张功能的实现代码
2010/03/12 Javascript
javascript时间函数基础介绍
2013/03/28 Javascript
js 固定悬浮效果实现思路代码
2013/08/02 Javascript
bootstrapfileinput实现文件自动上传
2016/11/08 Javascript
html判断当前页面是否在iframe中的实例
2016/11/30 Javascript
JavaScript中的toString()和toLocaleString()方法的区别
2017/02/15 Javascript
AngularJS 限定$scope的范围实例详解
2017/06/23 Javascript
vue监听input标签的value值方法
2018/08/27 Javascript
vue-router传递参数的几种方式实例详解
2018/11/13 Javascript
JavaScript面向对象中接口实现方法详解
2019/07/24 Javascript
教你搭建按需加载的Vue组件库(小结)
2019/07/29 Javascript
[51:44]2018DOTA2亚洲邀请赛 4.3 突围赛 Optic vs iG 第二场
2018/04/04 DOTA
Python判断字符串与大小写转换
2015/06/08 Python
python 队列详解及实例代码
2016/10/18 Python
基于Python函数的作用域规则和闭包(详解)
2017/11/29 Python
python中利用zfill方法自动给数字前面补0
2018/04/10 Python
django js实现部分页面刷新的示例代码
2018/05/28 Python
Python绘制频率分布直方图的示例
2019/07/08 Python
python实现通过队列完成进程间的多任务功能示例
2019/10/28 Python
Python pandas库中的isnull()详解
2019/12/26 Python
Django 项目布局方法(值得推荐)
2020/03/22 Python
基于TensorFlow的CNN实现Mnist手写数字识别
2020/06/17 Python
css3圆角边框和边框阴影示例
2014/05/05 HTML / CSS
HTML5实现QQ聊天气泡效果
2017/06/26 HTML / CSS
工程项目经理岗位职责
2013/12/15 职场文书
跑操口号
2014/06/12 职场文书
教师工作失职检讨书
2014/09/18 职场文书
上市公司财务总监岗位职责
2015/04/03 职场文书
go语言基础 seek光标位置os包的使用
2021/05/09 Golang
浅析Python OpenCV三种滤镜效果
2022/04/11 Python