详细解读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 getopt模块处理命令行选项实例
May 13 Python
python代码制作configure文件示例
Jul 28 Python
跟老齐学Python之玩转字符串(1)
Sep 14 Python
Python+Socket实现基于TCP协议的客户与服务端中文自动回复聊天功能示例
Aug 31 Python
Flask框架信号用法实例分析
Jul 24 Python
python实现彩票系统
Jun 28 Python
numpy 对矩阵中Nan的处理:采用平均值的方法
Oct 30 Python
python实现电子书翻页小程序
Jul 23 Python
wxpython实现按钮切换界面的方法
Nov 19 Python
ansible动态Inventory主机清单配置遇到的坑
Jan 19 Python
Python 实现网课实时监控自动签到、打卡功能
Mar 12 Python
ASP.NET Core中的配置详解
Feb 05 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 生成签名及验证签名详解
2016/10/26 PHP
ubutu 16.04环境下,PHP与mysql数据库,网页登录验证实例讲解
2017/07/20 PHP
php 替换文章中的图片路径,下载图片到本地服务器的方法
2018/02/06 PHP
Yii框架视图、视图布局、视图数据块操作示例
2019/10/14 PHP
tp5.1 实现setInc字段自动加1
2019/10/18 PHP
JavaScript 常用函数
2009/12/30 Javascript
超简单的jquery的AJAX用法
2010/05/10 Javascript
jQuery操作Select选择的Text和Value(获取/设置/添加/删除)
2013/03/06 Javascript
以JSON形式将JS中Array对象数组传至后台的方法
2014/01/06 Javascript
js加载读取内容及显示与隐藏div示例
2014/02/13 Javascript
jQuery 1.9.1源码分析系列(十五)动画处理之缓动动画核心Tween
2015/12/03 Javascript
理解JavaScript事件对象
2016/01/25 Javascript
基于Bootstrap的网页设计实例
2017/03/01 Javascript
jQuery实现select下拉框获取当前选中文本、值、索引
2017/05/08 jQuery
vue-cli单页应用改成多页应用配置详解
2017/07/14 Javascript
vue中tab选项卡的实现思路
2018/11/25 Javascript
JS实现的类似微信聊天效果示例
2019/01/29 Javascript
vue2.0 实现富文本编辑器功能
2019/05/26 Javascript
vue 框架下自定义滚动条(easyscroll)实现方法
2019/08/29 Javascript
element ui分页多选,翻页记忆的实例
2019/09/03 Javascript
JS原型和原型链原理与用法实例详解
2020/02/05 Javascript
[01:25]DOTA2超级联赛专访iG 将调整状态找回自己
2013/06/05 DOTA
Python实现的概率分布运算操作示例
2017/08/14 Python
itchat接口使用示例
2017/10/23 Python
对pandas数据判断是否为NaN值的方法详解
2018/11/06 Python
Python3爬虫学习之应对网站反爬虫机制的方法分析
2018/12/12 Python
利用Python脚本实现自动刷网课
2020/02/03 Python
浅谈Keras的Sequential与PyTorch的Sequential的区别
2020/06/17 Python
python中如何打包用户自定义模块
2020/09/23 Python
Html5原生拖拽相关事件简介以及基础实现
2020/11/19 HTML / CSS
Laura Mercier官网:彩妆大师罗拉玛斯亚的化妆品牌
2018/01/04 全球购物
财务会计专业应届毕业生求职信
2013/10/18 职场文书
计算机专业大学生的自我评价
2013/11/14 职场文书
2014婚礼司仪主持词
2014/03/14 职场文书
python基础学习之生成器与文件系统知识总结
2021/05/25 Python
Spring mvc是如何实现与数据库的前后端的连接操作的?
2021/06/30 Java/Android