标准库 (6)
urllib
urllib 模块用于读取来自网上(服务器上)的数据,比如不少人用 Python 做爬虫程序,就可以使用这个模块。先看一个简单例子:
>>> import urllib
>>> itdiffer = urllib.urlopen("http://www.itdiffer.com")
这样就已经把我的网站[www.itdiffer.comhref="http://www.itdiffer.com)首页的内容拿过来了,得到了一个类似文件的对象。接下来的操作跟操作一个文件一样(如果忘记了文件怎么操作,可以参考:[《文件(1)](126.md"))
>>> print itdiffer.read()
<!DOCTYPE HTML>
<html>
<head>
<title>I am Qiwsir</title>
....//因为内容太多,下面就省略了
就这么简单,完成了对一个网页的抓取。当然,如果你真的要做爬虫程序,还不是仅仅如此。这里不介绍爬虫程序如何编写,仅说明 urllib 模块的常用属性和方法。
>>> dir(urllib)
['ContentTooShortError', 'FancyURLopener', 'MAXFTPCACHE', 'URLopener', '__all__', '__builtins__', '__doc__', '__file__', '__name__', '__package__', '__version__', 'asciire', 'ftperrors', 'havessl', 'hexdig', 'hextochr', 'hostprog', 'is_unicode', 'localhost', 'noheaders', 'nportprog', 'passwdprog', 'portprog', 'queryprog', 'safemap', 'safequoters', 'tagprog', 'thishost', 'typeprog', 'urlopener', 'userprog', 'valueprog', 'addbase', 'addclosehook', 'addinfo', 'addinfourl', 'always_safe', 'base64', 'basejoin', 'c', 'ftpcache', 'ftperrors', 'ftpwrapper', 'getproxies', 'getproxies_environment', 'i', 'localhost', 'noheaders', 'os', 'pathname2url', 'proxy_bypass', 'proxy_bypass_environment', 'quote', 'quote_plus', 're', 'reporthook', 'socket', 'splitattr', 'splithost', 'splitnport', 'splitpasswd', 'splitport', 'splitquery', 'splittag', 'splittype', 'splituser', 'splitvalue', 'ssl', 'string', 'sys', 'test1', 'thishost', 'time', 'toBytes', 'unquote', 'unquote_plus', 'unwrap', 'url2pathname', 'urlcleanup', 'urlencode', 'urlopen', 'urlretrieve']
选几个常用的介绍,其它的如果读者用到,可以通过查看文档了解。
urlopen()
urlopen() 主要用于打开 url 文件,然后就获得指定 url 的数据,接下来就如同在本地操作文件那样来操作。
Help on function urlopen in module urllib:
urlopen(url, data=None, proxies=None) Create a file-like object for the specified URL to read from.
得到的对象被叫做类文件。从名字中也可以理解后面的操作了。先对参数说明一下:
- url:远程数据的路径,常常是网址
- data:如果使用 post 方式,这里就是所提交的数据
- proxies:设置代理
关于参数的详细说明,还可以参考Python的官方文档,这里仅演示最常用的,如前面的例子那样。
当得到了类文件对象之后,就可以对它进行操作。变量 itdiffer 引用了得到的类文件对象,通过它查看:
>>> dir(itdiffer)
['__doc__', '__init__', '__iter__', '__module__', '__repr__', 'close', 'code', 'fileno', 'fp', 'getcode', 'geturl', 'headers', 'info', 'next', 'read', 'readline', 'readlines', 'url']
读者从这个结果中也可以看出,这个类文件对象也是可迭代的。常用的方法:
- read(),readline(),readlines(),fileno(),close():都与文件操作一样,这里不再赘述。可以参考前面有关文件章节
- info():返回头信息
- getcode():返回 http 状态码
- geturl():返回 url
简单举例:
>>> itdiffer.info()
<httplib.HTTPMessage instance at 0xb6eb3f6c>
>>> itdiffer.getcode()
200
>>> itdiffer.geturl()
'http://www.itdiffer.com'
更多情况下,已经建立了类文件对象,通过对文件操作方法,获得想要的数据。
对 url 编码、解码
url 对其中的字符有严格要求,不许可某些特殊字符,这就要对 url 进行编码和解码了。这个在进行 web 开发的时候特别要注意。urllib 模块提供这种功能。
- quote(string[, safe]):对字符串进行编码。参数 safe 指定了不需要编码的字符
- urllib.unquote(string) :对字符串进行解码
- quote_plus(string [ , safe ] ) :与 urllib.quote 类似,但这个方法用'+'来替换空格
' '
,而 quote 用'%20'来代替空格 - unquote_plus(string ) :对字符串进行解码;
- urllib.urlencode(query[, doseq]):将 dict 或者包含两个元素的元组列表转换成 url 参数。例如{'name': 'laoqi', 'age': 40}将被转换为"name=laoqi&age=40"
- pathname2url(path):将本地路径转换成 url 路径
- url2pathname(path):将 url 路径转换成本地路径
看例子就更明白了:
>>> du = "http://www.itdiffer.com/name=python book"
>>> urllib.quote(du)
'http%3A//www.itdiffer.com/name%3Dpython%20book'
>>> urllib.quote_plus(du)
'http%3A%2F%2Fwww.itdiffer.com%2Fname%3Dpython+book'
注意看空格的变化,一个被编码成 %20
,另外一个是 +
再看解码的,假如在 google 中搜索零基础 Python
,结果如下图:
我的教程可是在这次搜索中排列第一个哦。
这不是重点,重点是看 url,它就是用 +
替代空格了。
>>> dup = urllib.quote_plus(du)
>>> urllib.unquote_plus(dup)
'http://www.itdiffer.com/name=Python book'
从解码效果来看,比较完美地逆过程。
>>> urllib.urlencode({"name":"qiwsir","web":"itdiffer.com"})
'web=itdiffer.com&name=qiwsir'
这个在编程中,也会用到,特别是开发网站时候。
urlretrieve()
虽然 urlopen() 能够建立类文件对象,但是,那还不等于将远程文件保存在本地存储器中,urlretrieve() 就是满足这个需要的。先看实例:
>>> import urllib
>>> urllib.urlretrieve("http://www.itdiffer.com/images/me.jpg","me.jpg")
('me.jpg', <httplib.HTTPMessage instance at 0xb6ecb6cc>)
>>>
me.jpg 是一张存在于服务器上的图片,地址是:http://www.itdiffer.com/images/me.jpg,把它保存到本地存储器中,并且仍旧命名为 me.jpg。注意,如果只写这个名字,表示存在启动 Python 交互模式的那个目录中,否则,可以指定存储具体目录和文件名。
在urllib官方文档中有一大段相关说明,读者可以去认真阅读。这里仅简要介绍一下相关参数。
urllib.urlretrieve(url[, filename[, reporthook[, data]]])
- url:文件所在的网址
- filename:可选。将文件保存到本地的文件名,如果不指定,urllib 会生成一个临时文件来保存
- reporthook:可选。是回调函数,当链接服务器和相应数据传输完毕时触发本函数
- data:可选。如果用 post 方式所发出的数据
函数执行完毕,返回的结果是一个元组(filename, headers),filename 是保存到本地的文件名,headers 是服务器响应头信息。
#!usrbin/env Python
# coding=utf-8
import urllib
def go(a,b,c):
per = 100.0 a b c
if per > 100:
per = 100
print "%.2f%%" % per
url = "http:/youxi.66wz.com/uploads/1046/1321/11410192.90d133701b06f0cc2826c3e5ac34c620.jpg"
local = "homeqw/Pictures/g.jpg"
urllib.urlretrieve(url, local, go)
这段程序就是要下载指定的图片,并且保存为本地指定位置的文件,同时要显示下载的进度。上述文件保存之后,执行,显示如下效果:
$ Python 22501.py
0.00%
8.13%
16.26%
24.40%
32.53%
40.66%
48.79%
56.93%
65.06%
73.19%
81.32%
89.46%
97.59%
100.00%
到相应目录中查看,能看到与网上地址一样的文件。我这里就不对结果截图了,唯恐少部分读者鼻子流血。
urllib2
urllib2 是另外一个模块,它跟 urllib 有相似的地方——都是对 url 相关的操作,也有不同的地方。关于这方面,有一篇文章讲的不错:Python: difference between urllib and urllib2
我选取一段,供大家参考:
urllib2 can accept a Request object to set the headers for a URL request, urllib accepts only a URL. That means, you cannot masquerade your User Agent string etc.
urllib provides the urlencode method which is used for the generation of GET query strings, urllib2 doesn't have such a function. This is one of the reasons why urllib is often used along with urllib2.
所以,有时候两个要同时使用,urllib 模块和 urllib2 模块有的方法可以相互替代,有的不能。看下面的属性方法列表就知道了。
>>> dir(urllib2)
['AbstractBasicAuthHandler', 'AbstractDigestAuthHandler', 'AbstractHTTPHandler', 'BaseHandler', 'CacheFTPHandler', 'FTPHandler', 'FileHandler', 'HTTPBasicAuthHandler', 'HTTPCookieProcessor', 'HTTPDefaultErrorHandler', 'HTTPDigestAuthHandler', 'HTTPError', 'HTTPErrorProcessor', 'HTTPHandler', 'HTTPPasswordMgr', 'HTTPPasswordMgrWithDefaultRealm', 'HTTPRedirectHandler', 'HTTPSHandler', 'OpenerDirector', 'ProxyBasicAuthHandler', 'ProxyDigestAuthHandler', 'ProxyHandler', 'Request', 'StringIO', 'URLError', 'UnknownHandler', '__builtins__', '__doc__', '__file__', '__name__', '__package__', '__version__', 'cutport_re', 'opener', 'parse_proxy', 'safegethostbyname', 'addinfourl', 'base64', 'bisect', 'build_opener', 'ftpwrapper', 'getproxies', 'hashlib', 'httplib', 'install_opener', 'localhost', 'mimetools', 'os', 'parse_http_list', 'parse_keqv_list', 'posixpath', 'proxy_bypass', 'quote', 'random', 'randombytes', 're', 'request_host', 'socket', 'splitattr', 'splithost', 'splitpasswd', 'splitport', 'splittag', 'splittype', 'splituser', 'splitvalue', 'sys', 'time', 'toBytes', 'unquote', 'unwrap', 'url2pathname', 'urlopen', 'urlparse', 'warnings']
比较常用的比如 urlopen() 跟 urllib.open() 是完全类似的。
Request 类
正如前面区别 urllib 和 urllib2 所讲,利用 urllib2 模块可以建立一个 Request 对象。方法就是:
>>> req = urllib2.Request("http://www.itdiffer.com")
建立了 Request 对象之后,它的最直接应用就是可以作为 urlopen() 方法的参数
>>> response = urllib2.urlopen(req)
>>> page = response.read()
>>> print page
因为与前面的 urllib.open("http://www.itdiffer.com")
结果一样,就不浪费篇幅了。
但是,如果 Request 对象仅仅局限于此,似乎还没有什么太大的优势。因为刚才的访问仅仅是满足以 get 方式请求页面,并建立类文件对象。如果是通过 post 向某地址提交数据,也可以建立 Request 对象。
import urllib
import urllib2
url = 'http://www.itdiffer.com/register.py'
values = {'name' : 'qiwsir',
'location' : 'China',
'language' : 'Python' }
data = urllib.urlencode(values) # 编码
req = urllib2.Request(url, data) # 发送请求同时传 data 表单
response = urllib2.urlopen(req) #接受反馈的信息
the_page = response.read() #读取反馈的内容
注意,读者不能照抄上面的程序,然后运行代码。因为那个 url 中没有相应的接受客户端 post 上去的 data 的程序文件。上面的代码只是以一个例子来显示 Request 对象的另外一个用途,还有就是在这个例子中是以 post 方式提交数据。
在网站中,有的会通过 User-Agent 来判断访问者是浏览器还是别的程序,如果通过别的程序访问,它有可能拒绝。这时候,我们编写程序去访问,就要设置 headers 了。设置方法是:
user_agent = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)'
headers = { 'User-Agent' : user_agent }
然后重新建立 Request 对象:
req = urllib2.Request(url, data, headers)
再用 urlopen() 方法访问:
response = urllib2.urlopen(req)
除了上面演示之外,urllib2 模块的东西还很多,比如还可以:
- 设置 HTTP Proxy
- 设置 Timeout 值
- 自动 redirect
- 处理 cookie
等等。这些内容不再一一介绍,当需要用到的时候可以查看文档或者 google。
如果你认为有必要打赏我,请通过支付宝:qiwsir@126.com,不胜感激。
xml
xml 在软件领域用途非常广泛,有名人曰:
“当 XML(扩展标记语言)于 1998 年 2 月被引入软件工业界时,它给整个行业带来了一场风暴。有史以来第一次,这个世界拥有了一种用来结构化文档和数据的通用且适应性强的格式,它不仅仅可以用于 WEB,而且可以被用于任何地方。”
—-《Designing With Web Standards Second Edition》, Jeffrey Zeldman
对于 xml 如果要做一个定义式的说明,就不得不引用 w3school 里面简洁而明快的说明:
- XML 指可扩展标记语言(EXtensible Markup Language)
- XML 是一种标记语言,很类似 HTML
- XML 的设计宗旨是传输数据,而非显示数据
- XML 标签没有被预定义。您需要自行定义标签。
- XML 被设计为具有自我描述性。
- XML 是 W3C 的推荐标准
如果读者要详细了解和学习有关 xml,可以阅读w3school的教程
xml 的重要,关键在于它是用来传输数据,因为传输数据,特别是在 web 编程中,经常要用到的。有了这样一种东西,就让数据传输变得简单了。对于这么重要的,Python 当然有支持。
一般来讲,一个引人关注的东西,总会有很多人从不同侧面去关注。在编程语言中也是如此,所以,对 xml 这个明星式的东西,Python 提供了多种模块来处理。
- xml.dom.* 模块:Document Object Model。适合用于处理 DOM API。它能够将 xml 数据在内存中解析成一个树,然后通过对树的操作来操作 xml。但是,这种方式由于将 xml 数据映射到内存中的树,导致比较慢,且消耗更多内存。
- xml.sax.* 模块:simple API for XML。由于 SAX 以流式读取 xml 文件,从而速度较快,切少占用内存,但是操作上稍复杂,需要用户实现回调函数。
- xml.parser.expat:是一个直接的,低级一点的基于 C 的 expat 的语法分析器。 expat 接口基于事件反馈,有点像 SAX 但又不太像,因为它的接口并不是完全规范于 expat 库的。
- xml.etree.ElementTree (以下简称 ET):元素树。它提供了轻量级的 Python 式的 API,相对于 DOM,ET 快了很多 ,而且有很多令人愉悦的 API 可以使用;相对于 SAX,ET 也有 ET.iterparse 提供了 “在空中” 的处理方式,没有必要加载整个文档到内存,节省内存。ET 的性能的平均值和 SAX 差不多,但是 API 的效率更高一点而且使用起来很方便。
所以,我用 xml.etree.ElementTree
ElementTree 在标准库中有两种实现。一种是纯 Python 实现:xml.etree.ElementTree ,另外一种是速度快一点:xml.etree.cElementTree 。
如果读者使用的是 Python2.x,可以像这样引入模块:
try:
import xml.etree.cElementTree as ET
except ImportError:
import xml.etree.ElementTree as ET
如果是 Python3.3 以上,就没有这个必要了,只需要一句话 import xml.etree.ElementTree as ET
即可,然后由模块自动来寻找适合的方式。显然 Python3.x 相对 Python2.x 有了很大进步。但是,本教程碍于很多工程项目还没有升级换代,暂且忍受了。
遍历查询
先要搞一个 xml 文档。为了图省事,我就用 w3school 中的一个例子:
这是一个 xml 树,只不过是用图来表示的,还没有用 ET 解析呢。把这棵树写成 xml 文档格式:
<bookstore>
<book category="COOKING">
<title lang="en">Everyday Italian</title>
<author>Giada De Laurentiis</author>
<year>2005</year>
<price>30.00</price>
</book>
<book category="CHILDREN">
<title lang="en">Harry Potter</title>
<author>J K. Rowling</author>
<year>2005</year>
<price>29.99</price>
</book>
<book category="WEB">
<title lang="en">Learning XML</title>
<author>Erik T. Ray</author>
<year>2003</year>
<price>39.95</price>
</book>
</bookstore>
将 xml 保存为名为 22601.xml 的文件,然后对其进行如下操作:
>>> import xml.etree.cElementTree as ET
为了简化,我用这种方式引入,如果在编程实践中,推荐读者使用 try…except…方式。
>>> tree = ET.ElementTree(file="22601.xml")
>>> tree
<ElementTree object at 0xb724cc2c>
建立起 xml 解析树。然后可以通过根节点向下开始读取各个元素(element 对象)。
在上述 xml 文档中,根元素是
>>> root = tree.getroot() #获得根
>>> root.tag
'bookstore'
>>> root.attrib
{}
要想将根下面的元素都读出来,可以:
>>> for child in root:
... print child.tag, child.attrib
...
book {'category': 'COOKING'}
book {'category': 'CHILDREN'}
book {'category': 'WEB'}
也可以这样读取指定元素的信息:
>>> root[0].tag
'book'
>>> root[0].attrib
{'category': 'COOKING'}
>>> root[0].text #无内容
'\n '
再深点,就有感觉了:
>>> root[0][0].tag
'title'
>>> root[0][0].attrib
{'lang': 'en'}
>>> root[0][0].text
'Everyday Italian'
对于 ElementTree 对象,有一个 iter 方法可以对指定名称的子节点进行深度优先遍历。例如:
>>> for ele in tree.iter(tag="book"): #遍历名称为 book 的节点
... print ele.tag, ele.attrib
...
book {'category': 'COOKING'}
book {'category': 'CHILDREN'}
book {'category': 'WEB'}
>>> for ele in tree.iter(tag="title"): #遍历名称为 title 的节点
... print ele.tag, ele.attrib, ele.text
...
title {'lang': 'en'} Everyday Italian
title {'lang': 'en'} Harry Potter
title {'lang': 'en'} Learning XML
如果不指定元素名称,就是将所有的元素遍历一边。
>>> for ele in tree.iter():
... print ele.tag, ele.attrib
...
bookstore {}
book {'category': 'COOKING'}
title {'lang': 'en'}
author {}
year {}
price {}
book {'category': 'CHILDREN'}
title {'lang': 'en'}
author {}
year {}
price {}
book {'category': 'WEB'}
title {'lang': 'en'}
author {}
year {}
price {}
除了上面的方法,还可以通过路径,搜索到指定的元素,读取其内容。这就是 xpath。此处对 xpath 不详解,如果要了解可以到网上搜索有关信息。
>>> for ele in tree.iterfind("book/title"):
... print ele.text
...
Everyday Italian
Harry Potter
Learning XML
利用 findall() 方法,也可以是实现查找功能:
>>> for ele in tree.findall("book"):
... title = ele.find('title').text
... price = ele.find('price').text
... lang = ele.find('title').attrib
... print title, price, lang
...
Everyday Italian 30.00 {'lang': 'en'}
Harry Potter 29.99 {'lang': 'en'}
Learning XML 39.95 {'lang': 'en'}
编辑
除了读取有关数据之外,还能对 xml 进行编辑,即增删改查功能。还是以上面的 xml 文档为例:
>>> root[1].tag
'book'
>>> del root[1]
>>> for ele in root:
... print ele.tag
...
book
book
如此,成功删除了一个节点。原来有三个 book 节点,现在就还剩两个了。打开源文件再看看,是不是正好少了第二个节点呢?一定很让你失望,源文件居然没有变化。
的确如此,源文件没有变化,这就对了。因为至此的修改动作,还是停留在内存中,还没有将修改结果输出到文件。不要忘记,我们是在内存中建立的 ElementTree 对象。再这样做:
>>> import os
>>> outpath = os.getcwd()
>>> file = outpath + "/22601.xml"
把当前文件路径拼装好。然后:
>>> tree.write(file)
再看源文件,已经变成两个节点了。
除了删除,也能够修改:
>>> for price in root.iter("price"): #原来每本书的价格
... print price.text
...
30.00
39.95
>>> for price in root.iter("price"): #每本上涨 7 元,并且增加属性标记
... new_price = float(price.text) + 7
... price.text = str(new_price)
... price.set("updated","up")
...
>>> tree.write(file)
查看源文件:
<bookstore>
<book category="COOKING">
<title lang="en">Everyday Italian</title>
<author>Giada De Laurentiis</author>
<year>2005</year>
<price updated="up">37.0</price>
</book>
<book category="WEB">
<title lang="en">Learning XML</title>
<author>Erik T. Ray</author>
<year>2003</year>
<price updated="up">46.95</price>
</book>
</bookstore>
不仅价格修改了,而且在 price 标签里面增加了属性标记。干得不错。
上面用 del
来删除某个元素,其实,在编程中,这个用的不多,更喜欢用 remove() 方法。比如我要删除 price > 40
的书。可以这么做:
>>> for book in root.findall("book"):
... price = book.find("price").text
... if float(price) > 40.0:
... root.remove(book)
...
>>> tree.write(file)
于是就这样了:
<bookstore>
<book category="COOKING">
<title lang="en">Everyday Italian</title>
<author>Giada De Laurentiis</author>
<year>2005</year>
<price updated="up">37.0</price>
</book>
</bookstore>
接下来就要增加元素了。
>>> import xml.etree.cElementTree as ET
>>> tree = ET.ElementTree(file="22601.xml")
>>> root = tree.getroot()
>>> ET.SubElement(root, "book") #在 root 里面添加 book 节点
<Element 'book' at 0xb71c7578>
>>> for ele in root:
... print ele.tag
...
book
book
>>> b2 = root[1] #得到新增的 book 节点
>>> b2.text = "Python" #添加内容
>>> tree.write("22601.xml")
查看源文件:
<bookstore>
<book category="COOKING">
<title lang="en">Everyday Italian</title>
<author>Giada De Laurentiis</author>
<year>2005</year>
<price updated="up">37.0</price>
</book>
<book>python</book>
</bookstore>
常用属性和方法总结
ET 里面的属性和方法不少,这里列出常用的,供使用中备查。
Element 对象
常用属性:
- tag:string,元素数据种类
- text:string,元素的内容
- attrib:dictionary,元素的属性字典
- tail:string,元素的尾形
针对属性的操作
- clear():清空元素的后代、属性、text 和 tail 也设置为 None
- get(key, default=None):获取 key 对应的属性值,如该属性不存在则返回 default 值
- items():根据属性字典返回一个列表,列表元素为(key, value)
- keys():返回包含所有元素属性键的列表
- set(key, value):设置新的属性键与值
针对后代的操作
- append(subelement):添加直系子元素
- extend(subelements):增加一串元素对象作为子元素
- find(match):寻找第一个匹配子元素,匹配对象可以为 tag 或 path
- findall(match):寻找所有匹配子元素,匹配对象可以为 tag 或 path
- findtext(match):寻找第一个匹配子元素,返回其 text 值。匹配对象可以为 tag 或 path
- insert(index, element):在指定位置插入子元素
- iter(tag=None):生成遍历当前元素所有后代或者给定 tag 的后代的迭代器
- iterfind(match):根据 tag 或 path 查找所有的后代
- itertext():遍历所有后代并返回 text 值
- remove(subelement):删除子元素
ElementTree 对象
- find(match)
- findall(match)
- findtext(match, default=None)
- getroot():获取根节点.
- iter(tag=None)
- iterfind(match)
- parse(source, parser=None):装载 xml 对象,source 可以为文件名或文件类型对象.
- write(file, encoding="us-ascii", xml_declaration=None, default_namespace=None,method="xml")
一个实例
最后,提供一个参考,这是一篇来自网络的文章:Python xml 属性、节点、文本的增删改,本文的源码我也复制到下面,请读者参考:
实现思想:
使用 ElementTree,先将文件读入,解析成树,之后,根据路径,可以定位到树的每个节点,再对节点进行修改,最后直接将其输出.
#!usrbin/Python
# -*- coding=utf-8 -*-
# author : wklken@yeah.net
# date: 2012-05-25
# version: 0.1
from xml.etree.ElementTree import ElementTree,Element
def read_xml(in_path):
'''
读取并解析 xml 文件
in_path: xml 路径
return: ElementTree
'''
tree = ElementTree()
tree.parse(in_path)
return tree
def write_xml(tree, out_path):
'''
将 xml 文件写出
tree: xml 树
out_path: 写出路径
'''
tree.write(out_path, encoding="utf-8",xml_declaration=True)
def if_match(node, kv_map):
'''
判断某个节点是否包含所有传入参数属性
node: 节点
kv_map: 属性及属性值组成的 map
'''
for key in kv_map:
if node.get(key) != kv_map.get(key):
return False
return True
#---------------search -----
def find_nodes(tree, path):
'''
查找某个路径匹配的所有节点
tree: xml 树
path: 节点路径
'''
return tree.findall(path)
def get_node_by_keyvalue(nodelist, kv_map):
'''
根据属性及属性值定位符合的节点,返回节点
nodelist: 节点列表
kv_map: 匹配属性及属性值 map
'''
result_nodes = []
for node in nodelist:
if if_match(node, kv_map):
result_nodes.append(node)
return result_nodes
#---------------change -----
def change_node_properties(nodelist, kv_map, is_delete=False):
'''
修改/增加 删除 节点的属性及属性值
nodelist: 节点列表
kv_map:属性及属性值 map
'''
for node in nodelist:
for key in kv_map:
if is_delete:
if key in node.attrib:
del node.attrib[key]
else:
node.set(key, kv_map.get(key))
def change_node_text(nodelist, text, is_add=False, is_delete=False):
'''
改变增加/删除一个节点的文本
nodelist:节点列表
text : 更新后的文本
'''
for node in nodelist:
if is_add:
node.text += text
elif is_delete:
node.text = ""
else:
node.text = text
def create_node(tag, property_map, content):
'''
新造一个节点
tag:节点标签
property_map:属性及属性值 map
content: 节点闭合标签里的文本内容
return 新节点
'''
element = Element(tag, property_map)
element.text = content
return element
def add_child_node(nodelist, element):
'''
给一个节点添加子节点
nodelist: 节点列表
element: 子节点
'''
for node in nodelist:
node.append(element)
def del_node_by_tagkeyvalue(nodelist, tag, kv_map):
'''
同过属性及属性值定位一个节点,并删除之
nodelist: 父节点列表
tag:子节点标签
kv_map: 属性及属性值列表
'''
for parent_node in nodelist:
children = parent_node.getchildren()
for child in children:
if child.tag == tag and if_match(child, kv_map):
parent_node.remove(child)
if __name__ == "__main__":
#1. 读取 xml 文件
tree = read_xml("./test.xml")
#2. 属性修改
#A. 找到父节点
nodes = find_nodes(tree, "processers/processer")
#B. 通过属性准确定位子节点
result_nodes = get_node_by_keyvalue(nodes, {"name":"BProcesser"})
#C. 修改节点属性
change_node_properties(result_nodes, {"age": "1"})
#D. 删除节点属性
change_node_properties(result_nodes, {"value":""}, True)
#3. 节点修改
#A.新建节点
a = create_node("person", {"age":"15","money":"200000"}, "this is the firest content")
#B.插入到父节点之下
add_child_node(result_nodes, a)
#4. 删除节点
#定位父节点
del_parent_nodes = find_nodes(tree, "processers/services/service")
#准确定位子节点并删除之
target_del_node = del_node_by_tagkeyvalue(del_parent_nodes, "chain", {"sequency" : "chain1"})
#5. 修改节点文本
#定位节点
text_nodes = get_node_by_keyvalue(find_nodes(tree, "processers/services/service/chain"), {"sequency":"chain3"})
change_node_text(text_nodes, "new text")
#6. 输出到结果文件
write_xml(tree, "./out.xml")
操作对象(原始 xml 文件):
<?xml version="1.0" encoding="UTF-8"?>
<framework>
<processers>
<processer name="AProcesser" file="lib64/A.so"
path="tmp">
<processer>
<processer name="BProcesser" file="lib64/B.so" value="fordelete">
</processer>
<processer name="BProcesser" file="lib64/B.so2222222">
<services>
<service name="search" prefix="bin/search?"
output_formatter="OutPutFormatter:service_inc">
<chain sequency="chain1">
<chain sequency="chain2"><chain>
</service>
<service name="update" prefix="binupdate?">
<chain sequency="chain3" value="fordelete">
<service>
</services>
</processers>
</framework>
执行程序之后,得到的结果文件:
<?xml version='1.0' encoding='utf-8'?>
<framework>
<processers>
<processer file="lib64/A.so" name="AProcesser" path="tmp">
<processer>
<processer age="1" file="lib64/B.so" name="BProcesser">
<person age="15" money="200000">this is the firest content</person>
</processer>
<processer age="1" file="lib64/B.so2222222" name="BProcesser">
<person age="15" money="200000">this is the firest content</person>
</processer>
<services>
<service name="search" output_formatter="OutPutFormatter:service_inc"
prefix="binsearch?">
<chain sequency="chain2" >
<service>
<service name="update" prefix="binupdate?">
<chain sequency="chain3" value="fordelete">new text</chain>
</service>
</services>
</processers>
</framework>