-
- 1. 对Python标准库的一个大致认识
- 2. Python urllib模块与urlopen()函数解析
- 3. urllib urlopen()对象方法/代理的补充说明
- 4. Python urllib的urlretrieve()函数解析
- 5. Python urllib模块的URL编码解码功能
- 6. Python库里urllib和urllib2的区别
- 7. urllib2的一些简单介绍
- 8. urllib2与urllib一些常用方法的比较
- 9. urllib2的一些常用方法介绍
- 10. Python urllib2 的更多使用细节
- 11. coiokielib模块与urllib2的配合使用
- 12. 创建Opener对象以实现Cookie与其它HTTP功能
- 13. Python HTTP客户端如何实现自定义Cookie
- 14. 用Python模拟登录网站
正如那句 Python 社区中很有名的话所说的:“battery included”,Python 的一大好处在于它有一套很有用的标准库(standard library)。标准库是随着 Python 一起安装在你的电脑中的,是 Python 的一部分 (当然也有特殊情况。有些场合会因为系统安全性的要求,不使用全部的标准库,比如说Google App Engine)。
利用已有的类(class)和函数(function)进行开发,可以省去你从头写所有程序的苦恼。这些标准库就是盖房子已经烧好的砖,要比你自己去烧砖来得便捷得多。
我将根据我个人的使用经验中,先挑选出标准库下面三个方面的包(package)介绍,以说明标准库的强大功能:
- Python增强
- 系统互动
- 网络
第一类:Python增强
Python 自身的已有的一些功能可以随着标准库的使用而得到增强。
1. 文字处理
Python的string类提供了对字符串进行处理的方法。但Python并不止步于此。通过标准库中的re包,Python实现了对正则表达式(regular expression)的支持。Python的正则表达式可以和Perl以及Linux bash的正则表达相媲美。正则表达式通过自定义的模板在文本中搜索或替换符合该模板的字符串。比如你可以搜索一个文本中所有的数字。正则表达式的关键在于根据自己的需要构成模板。
此外,Python 标准库还为字符串的输出提供更加丰富的格式, 比如: string包,textwrap包。
2. 数据对象
我们之前的快速教程介绍了表(list), 字典(dictionary)等数据对象。它们各自有不同的特征,适用于不同场合的对数据的组织和管理。Python的标准库定义了更多的数据对象,比如说数组(array),队列(Queue)。这些数据对象也分别有各自的特点和功能。一个熟悉数据结构(data structure)的Python用户可以在这些包中找到自己需要的数据结构。
此外,我们也会经常使用copy包,以复制对象。
3. 日期和时间
日期和时间的管理并不复杂,但容易犯错。Python的标准库中对日期和时间的管理颇为完善(利用time包管理时间,利用datetime包管理日期和时间),你不仅可以进行日期时间的查询和变换(比如:2012年7月18日对应的是星期几),还可以对日期时间进行运算(比如2000.1.1 13:00的378小时之后是什么日期,什么时间)。通过这些标准库,还可以根据需要控制日期时间输出的文本格式(比如:输出’2012-7-18‘还是'18 Jul 2012')
4. 数学运算
标准库中,Python定义了一些新的数字类型(decimal包, fractions包), 以弥补之前的数字类型(integer, float)可能的不足。标准库还包含了random包,用于处理随机数相关的功能(产生随机数,随机取样等)。math包补充了一些重要的数学常数和数学函数,比如pi,三角函数等等。
尽管numpy并不是标准库中的包,但它的数组运算的良好支持,让它在基于Python的科研和计算方面得到相当广泛的应用,可以适当关注。
5. 存储
之前我们的快速教程中,只提及了文本的输入和输出。实际上,Python可以输入或输出任意的对象。这些对象可以通过标准库中的pickle包转换成为二进制格式(binary),然后存储于文件之中,也可以反向从二进制文件中读取对象。
此外,标准库中还支持基本的数据库功能(sqlite3包)。XML和csv格式的文件也有相应的处理包。
第二类:系统互动
系统互动,主要指Python和操作系统(operate system)、文件系统(file system)的互动。Python可以实现一个操作系统的许多功能。它能够像bash脚本那样管理操作系统,这也是Python有时被成为脚本语言的原因。
1. Python运行控制
sys包被用于管理Python自身的运行环境。Python是一个解释器(interpreter), 也是一个运行在操作系统上的程序。我们可以用sys包来控制这一程序运行的许多参数,比如说Python运行所能占据的内存和CPU, Python所要扫描的路径等。另一个重要功能是和Python自己的命令行互动,从命令行读取命令和参数。
2. 操作系统
如果说Python构成了一个小的世界,那么操作系统就是包围这个小世界的大世界。Python与操作系统的互动可以让Python在自己的小世界里管理整个大世界。
os包是Python与操作系统的接口。我们可以用os包来实现操作系统的许多功能,比如管理系统进程,改变当前路径(相当于’cd‘),改变文件权限等,建立。但要注意,os包是建立在操作系统的平台上的,许多功能在Windows系统上是无法实现的。另外,在使用os包中,要注意其中的有些功能已经被其他的包取代。
我们通过文件系统来管理磁盘上储存的文件。查找、删除,复制文件,以及列出文件列表等都是常见的文件操作。这些功能经常可以在操作系统中看到(比如ls, mv, cp等Linux命令),但现在可以通过Python标准库中的glob包、shutil包、os.path包、以及os包的一些函数等,在Python内部实现。
subprocess包被用于执行外部命令,其功能相当于我们在操作系统的命令行中输入命令以执行,比如常见的系统命令'ls'或者'cd',还可以是任意可以在命令行中执行的程序。
4. 线程与进程
Python支持多线程(threading包)运行和多进程(multiprocessing包)运行。通过多线程和多进程,可以提高系统资源的利用率,提高计算机的处理速度。Python在这些包中,附带有相关的通信和内存管理工具。此外,Python还支持类似于UNIX的signal系统,以实现进程之间的粗糙的信号通信。
第三类:网络
现在,网络功能的强弱很大程度上决定了一个语言的成功与否。从Ruby, JavaScript, php身上都可以感受到这一点。Python的标准库对互联网开发的支持并不充分,这也是Django等基于Python的项目的出发点: 增强Python在网络方面的应用功能。这些项目取得了很大的成功,也是许多人愿意来学习Python的一大原因。但应注意到,这些基于Python的项目也是建立在Python标准库的基础上的。
1. 基于socket层的网络应用
socket是网络可编程部分的底层。通过socket包,我们可以直接管理socket,比如说将socket赋予给某个端口(port),连接远程端口,以及通过连接传输数据。我们也可以利用SocketServer包更方便地建立服务器。
通过与多线程和多进程配合,建立多线程或者多进程的服务器,可以有效提高服务器的工作能力。此外,通过asyncore包实现异步处理,也是改善服务器性能的一个方案。
2. 互联网应用
在实际应用中,网络的很多底层细节(比如socket)都是被高层的协议隐藏起来的。建立在socket之上的http协议实际上更容易也更经常被使用。http通过request/responce的模式建立连接并进行通信,其信息内容也更容易理解。Python标准库中有http的服务器端和客户端的应用支持(BaseHTTPServer包; urllib包, urllib2包), 并且可以通过urlparse包对URL(URL实际上说明了网络资源所在的位置)进行理解和操作。
这些内容可以说非常粗糙,只希望能为大家提供一个了解标准库的入口。欢迎大家一起分享标准库的使用经验。
Python urllib 库提供了一个从指定的 URL 地址获取网页数据,然后对其进行分析处理,获取想要的数据。
下面是在 Python Shell 里的 urllib 的使用情况:
Python 2.7.5 (default, May 15 2013, 22:44:16) [MSC v.1500 64 bit (AMD64)] on win32 Type "copyright", "credits" or "license()" for more information. >>> import urllib >>> google = urllib.urlopen('http://www.google.com') >>> print 'http header:\n',google.info() http header: Date: Wed, 30 Oct 2013 03:11:44 GMT Expires: -1 Cache-Control: private, max-age=0 Content-Type: text/html; charset=Big5 Set-Cookie: PREF=ID=7ee0cbd58be6fb74:FF=0:NW=1:TM=1383102704:LM=1383102704:S=w6DoLuUBc7KUOE69; expires=Fri, 30-Oct-2015 03:11:44 GMT; path=/; domain=.google.com.hk Set-Cookie: NID=67=cNyh4vZeoDJFnSe12viwoMNh47Hjq98F72I6TTNZGBuJx78aRgQbAA-RtGNFFpARCaN3zJ6OYIpJASB3Q7cmfyRguFh6epcBOSL930KEfIxUa-85e946hE97WfP0lgk7; expires=Thu, 01-May-2014 03:11:44 GMT; path=/; domain=.google.com.hk; HttpOnly P3P: CP="This is not a P3P policy! See http://www.google.com/support/accounts/bin/answer.py?hl=en&answer=151657 for more info." Server: gws X-XSS-Protection: 1; mode=block X-Frame-Options: SAMEORIGIN Alternate-Protocol: 80:quic >>> print 'http status:',google.getcode() http status: 200 >>> print 'url:',google.geturl() url: http://www.google.com.hk/ >>>
上面主要用到了 urllib 库里的 urlopen() 函数。我们可以了解一下这个函数。
继续使用 Python Shell:
>>> help(urllib.urlopen) 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 来读取。
详细点就是,创建一个表示远程url的类文件对象,然后像本地文件一样操作这个类文件对象来获取远程数据。参数url表示远程数据的路径,一般是网址;参数data表示以post方式提交到url的数据(玩过web的人应该知道提交数据的两种方式:post与get。如果你不清楚,也不必太在意,一般情况下很少用到这个参数);参数proxies用于设置代理(这里不详细讲怎么使用代理,感兴趣的看客可以去翻阅Python手册urllib模块)。urlopen返回 一个类文件对象,他提供了如下方法:
- 参数 url 表示远程数据的路径,一般是 http 或者 ftp 路径。
- 参数 data 表示以 get 或者 post 方式提交到 url 的数据。
- 参数 proxies 表示用于代理的设置。
urlopen 返回一个类文件对象,它提供了如下方法:
- read() , readline() , readlines(),fileno()和close(): 这些方法的使用与文件对象完全一样。
- info():返回一个httplib.HTTPMessage 对象,表示远程服务器返回的头信息。
- getcode():返回Http状态码,如果是http请求,200表示请求成功完成;404表示网址未找到。
- geturl():返回请求的url地址。
再看一个例子,这个例子把Google首页的html抓取下来并显示在控制台上:
# 别惊讶,整个程序确实只用了两行代码 import urllib print urllib.urlopen('http://www.google.com').read()
再运行一下这个例子,以加深对urllib的印象:
google = urllib.urlopen('http://www.google.com') print 'http header:/n', google.info() print 'http status:', google.getcode() print 'url:', google.geturl() for line in google: # 就像在操作本地文件 print line, google.close()
urllib 是 python 自带的一个抓取网页信息一个接口,他最主要的方法是urlopen(),是基于 python 的 open() 方法的。下面是主要说明:
urllib.urlopen('网址')
- 这里传入urlopen()的参数有特别说要求,要遵循一些网络协议,比如http,ftp,也就是说,在网址的开头必须要有http://这样的说明,如:urllib.urlopen('http://www.baidu.com')。
- 要么就是本地文件,本地文件需要使用file关键字,比如 urllib.urlopen('file:nowamagic.py'),注意,这里的hello.py是指的是当前的classpath所指定的内容,如果对hello.py这里有什么疑问那一定是python寻找classpath的顺序不是很清楚了,当然也可以直接写全部路径,urllib.urlopen('file:F:\pythontest\nowamagic.py')。
- 打开 ftp 文件也是可以的,写法 urllib.urlopen(url='ftp://用户名:密码@ftp地址/') 等。
示例程序:
import urllib f = urllib.urlopen('file:F:\pythontest\nowamagic.py') a = f.read() print a
如果传入的参数正确,比如该网站可以访问,没有特殊情况(比如需要代理,被墙等),那么将返回一个类似于文件对象的对象。即上面代码中的f,f对象有的方法一些操作方法,使用dir(f):
['__doc__', '__init__', '__iter__', '__module__', '__repr__', 'close', 'fileno', 'fp', 'geturl', 'headers', 'info', 'next', 'read', 'readline', 'readlines', 'url']
使用read()方法会将所有内容读取出来,并且同时f对象类似于先入先出的数据,在使用f.read()将得不到任何数据,也就是说,得到的数据在这个时候如果想在后面进行任何处理操作的话,需要另外定义一个对象来进行存储。如上例中的a。而info(),geturl()方法,也是基于f这个文档对象的,所以,使用
>>>f.geturl() 'F://pythontest//nowamagic.py'
接下来是urllib的代理设置:
import urllib proxies = {'http':'http://***.***.***.***:1984'} filehandle = urllib.urlopen('http://www.需要代理才能访问的网站.com/',proxies = proxies) a = filehandle.read() print a
以上是最基本代理,即代理访问到该网站,并且能够获得该网站的内容。但是如果遇到需要登录,或者需要cookie等的网站呢?
查看urllib的源码:
def urlopen(url, data=None, proxies=None): """urlopen(url [, data]) -> open file-like object""" global _urlopener if proxies is not None: opener = FancyURLopener(proxies=proxies) elif not _urlopener: opener = FancyURLopener() _urlopener = opener else: opener = _urlopener if data is None: return opener.open(url) else: return opener.open(url, data)
由上面urllib的urlopen的源码,可以看出,还可以传入一个data参数,data参数也应该是一个字典,因为在使用浏览器向服务器发送数据的时候,我们发送的就是字典类型的数据。
还有一点,就是代理支持是 python 2.3 以后加入的。
下面我们再来看看 urllib 模块提供的 urlretrieve() 函数。urlretrieve() 方法直接将远程数据下载到本地。
>>> help(urllib.urlretrieve) Help on function urlretrieve in module urllib: urlretrieve(url, filename=None, reporthook=None, data=None)
- 参数 finename 指定了保存本地路径(如果参数未指定,urllib会生成一个临时文件保存数据。)
- 参数 reporthook 是一个回调函数,当连接上服务器、以及相应的数据块传输完毕时会触发该回调,我们可以利用这个回调函数来显示当前的下载进度。
- 参数 data 指 post 到服务器的数据,该方法返回一个包含两个元素的(filename, headers)元组,filename 表示保存到本地的路径,header 表示服务器的响应头。
下面通过例子来演示一下这个方法的使用,这个例子将 google 的 html 抓取到本地,保存在 D:/google.html 文件中,同时显示下载的进度。
import urllib def cbk(a, b, c): '''回调函数 @a: 已经下载的数据块 @b: 数据块的大小 @c: 远程文件的大小 ''' per = 100.0 * a * b / c if per > 100: per = 100 print '%.2f%%' % per url = 'http://www.google.com' local = 'd://google.html' urllib.urlretrieve(url, local, cbk)
在 Python Shell 里执行如下:
Python 2.7.5 (default, May 15 2013, 22:44:16) [MSC v.1500 64 bit (AMD64)] on win32 Type "copyright", "credits" or "license()" for more information. >>> import urllib >>> def cbk(a, b, c): '''回调函数 @a: 已经下载的数据块 @b: 数据块的大小 @c: 远程文件的大小 ''' per = 100.0 * a * b / c if per > 100: per = 100 print '%.2f%%' % per >>> url = 'http://www.google.com' >>> local = 'd://google.html' >>> urllib.urlretrieve(url, local, cbk) -0.00% -819200.00% -1638400.00% -2457600.00% ('d://google.html', <httplib.HTTPMessage instance at 0x0000000003450608>) >>>
下面是 urlretrieve() 下载文件实例,可以显示下载进度。
#!/usr/bin/python #encoding:utf-8 import urllib import os def Schedule(a,b,c): ''''' a:已经下载的数据块 b:数据块的大小 c:远程文件的大小 ''' per = 100.0 * a * b / c if per > 100 : per = 100 print '%.2f%%' % per url = 'http://www.python.org/ftp/python/2.7.5/Python-2.7.5.tar.bz2' #local = url.split('/')[-1] local = os.path.join('/data/software','Python-2.7.5.tar.bz2') urllib.urlretrieve(url,local,Schedule) ######output###### #0.00% #0.07% #0.13% #0.20% #.... #99.94% #100.00%
通过上面的练习可以知道,urlopen() 可以轻松获取远端 html 页面信息,然后通过 python 正则对所需要的数据进行分析,匹配出想要用的数据,在利用urlretrieve() 将数据下载到本地。对于访问受限或者对连接数有限制的远程 url 地址可以采用 proxies(代理的方式)连接,如果远程数据量过大,单线程下载太慢的话可以采用多线程下载,这个就是传说中的爬虫
前面介绍了 urllib 模块,以及它常用的 urlopen() 和 urlretrieve()函数的使用介绍。当然 urllib 还有一些其它很有用的辅助方法,比如对 url 进行编码、解码等等。接下来我们再大概介绍一下。
我们知道,url 中是不能出现一些特殊的符号的,有些符号有特殊的用途。比如以 get 方式提交数据的时候,会在 url 中添加 key=value 这样的字符串,所以在 value 中是不允许有 '=',因此要对其进行编码;与此同时服务器接收到这些参数的时候,要进行解码,还原成原始的数据。这个时候,这些辅助方法会很有用:
- urllib.quote(string[, safe]):对字符串进行编码。参数 safe 指定了不需要编码的字符;
- urllib.unquote(string) :对字符串进行解码;
- urllib.quote_plus(string [ , safe ] ) :与 urllib.quote 类似,但这个方法用'+'来替换' ',而 quote 用'%20'来代替' '
- urllib.unquote_plus(string ) :对字符串进行解码;
- urllib.urlencode(query[, doseq]):将dict或者包含两个元素的元组列表转换成url参数。例如 字典{'name': 'dark-bull', 'age': 200}将被转换为"name=dark-bull&age=200"
- urllib.pathname2url(path):将本地路径转换成 url 路径;
- urllib.url2pathname(path):将url路径转换成本地路径;
我们接下来运行一下下面的脚本来加深理解。
import urllib data = 'name = ~nowamagic+5' data1 = urllib.quote(data) print data1 # result: name%20%3D%20%7Enowamagic%2B5 print urllib.unquote(data1) # name = ~nowamagic+5 data2 = urllib.quote_plus(data) print data2 # result: name+%3D+%7Enowamagic%2B5 print urllib.unquote_plus(data2) # name = ~nowamagic+5 data3 = urllib.urlencode({ 'name': 'nowamagic-gonn', 'age': 200 }) print data3 # result: age=200&name=nowamagic-gonn data4 = urllib.pathname2url(r'd:/a/b/c/23.php') print data4 # result: ///D://a/b/c/23.php print urllib.url2pathname(data4) # result: D:/a/b/c/23.php
在 Python Shell 里执行的具体情况为:
Python 2.7.5 (default, May 15 2013, 22:44:16) [MSC v.1500 64 bit (AMD64)] on win32 Type "copyright", "credits" or "license()" for more information. >>> import urllib >>> data = 'name = ~nowamagic+5' >>> data1 = urllib.quote(data) >>> print data1 name%20%3D%20%7Enowamagic%2B5 >>> print urllib.unquote(data1) name = ~nowamagic+5 >>> data2 = urllib.quote_plus(data) >>> print data2 name+%3D+%7Enowamagic%2B5 >>> print urllib.unquote_plus(data2) name = ~nowamagic+5 >>> data3 = urllib.urlencode({ 'name': 'nowamagic-gonn', 'age': 200 }) >>> print data3 age=200&name=nowamagic-gonn >>> data4 = urllib.pathname2url(r'd:/a/b/c/23.php') >>> print data4 ///D://a/b/c/23.php >>> print urllib.url2pathname(data4) D:\a\b\c\23.php
urllib 模块的基本使用也比较简单,后面根据使用情况会继续跟进了解。
如果你用过 Python 写一些网络应用,应该对urllib和urllib2比较熟悉。那么urllib和urllib2之间有什么区别呢?我们应该用urllib还是urllib2呢?
看到老外写的一篇《Python: difference between urllib and urllib2》,这里简单翻译一下。
You might be intrigued by the existence of two separate URL modules in Python -urllib and urllib2. Even more intriguing: they are not alternatives for each other. So what is the difference between urllib and urllib2, and do we need them both?
你可能对于Python中两个独立存在的 urllib 和 urllib2 感到好奇。更有趣的是:它们并不是可以相互代替的。那么这两个模块间的区别是什么,并且这两个我们都需要吗?
urllib and urllib2are both Python modules that do URL request related stuff but offer different functionalities. Their two most significant differences are listed below:
urllib 和 urllib2 都是接受URL请求的相关模块,但是提供了不同的功能。两个最显著的不同如下:
- 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. urllib2可以接受一个Request类的实例来设置URL请求的headers,urllib仅可以接受URL。这意味着,你不可以伪装你的User Agent字符串等。
- 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提供urlencode方法用来GET查询字符串的产生,而urllib2没有。这是为何urllib常和urllib2一起使用的原因。
For other differences between urllib and urllib2 refer to their documentations, the links are given in the References section.
Tip: if you are planning to do HTTP stuff only, check out httplib2, it is much better than httplib or urllib or urllib2.
提示:如果你仅做HTTP相关的,看一下httplib2,比其他几个模块好用
先来一点题外话。看看下面一段代码:
main() { printf(&unix["\021%six\012\0"],(unix)["have"]+"fun"-0x60);}
这一行代码是1987年由贝尔实验室的 David Korn 提交的获奖作品,为什么我想起这茬儿呢?还不是因为urllib和urllib2,“大师把代码写成上面那样可以获奖,你要把代码写成那样,就是垃圾”,这不是我的话,不过是有他的意思的。
我看到了 urllib 和 urllib2 在设计和代码构造上很多不同,想到,或者是猜测 python 发展过程中,guido 越来越看不惯 urllib 的混乱结构了,但是很多人已经习惯 import urllib 了,并且用的还可以,所以 urllib 不管代码里多么混乱,但他能运行。很好,于是 guido 只能在urllib 外在开发了 urllib2,来满足一个有“洁癖”的程序员的心理需求。所以,当看到 urllib2 的代码结构的时候,明显比 urllib 清晰了很多,明确了很多,心情好多了。介绍的时候我会对比 urllib2 怎么做的,而 urllib 又是怎么做的。
大概了解
urlib2 是使用各种协议完成打开 url 的一个扩展包。最简单的使用方式是调用urlopen() 方法,比如:
import urllib2 content_stream = urllib2.urlopen('http://www.google.com/') content = content_stream.read() print content
即可以接受一个字符串型的 url 地址或者一个 Request 对象。将打开这个 url 并返回结果为一个像文件对象一样的对象。
接下来是 OpenerDirector 操作类。这是一个管理很多处理类(Handler)的类。而所有这些 Handler 类都对应处理相应的协议,或者特殊功能。分别有下面的处理类:
- BaseHandler
- HTTPErrorProcessor
- HTTPDefaultErrorHandler
- HTTPRedirectHandler
- ProxyHandler
- AbstractBasicAuthHandler
- HTTPBasicAuthHandler
- ProxyBasicAuthHandler
- AbstractDigestAuthHandler
- ProxyDigestAuthHandler
- AbstractHTTPHandler
- HTTPHandler
- HTTPCookieProcessor
- UnknownHandler
- FileHandler
- FTPHandler
- CacheFTPHandler
是不是很多?是不是和我说的结构简单不一致?不是的,他们都是遵循相应的规则,需求创建的类,甚至是相似的。你何必一口想吃成个胖子,全部都要会,先只管最最基本的需求吧,http 协议的处理类。
刚才我们说的最简单的 urlib2 的使用,也就是源码中给出的使用方式:
#file: urllib2.py _opener = None def urlopen(url, data=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT): global _opener if _opener is None: _opener = build_opener() return _opener.open(url, data, timeout)
对比给出 urllib 的最基本的使用方式:
_urlopener = None def urlopen(url, data=None, proxies=None): """Create a file-like object for the specified URL to read from.""" from warnings import warnpy3k warnpy3k("urllib.urlopen() has been removed in Python 3.0 in " "favor of urllib2.urlopen()", stacklevel=2) global _urlopener if proxies is not None: opener = FancyURLopener(proxies=proxies) elif not _urlopener: opener = FancyURLopener() _urlopener = opener else: opener = _urlopener if data is None: return opener.open(url) else: return opener.open(url, data)
很明显,urlib2 里 urlopen() 的实现方式更加优雅。
有个小警告: warnpy3k("urllib.urlopen() has been removed in Python 3.0 in " "favor of urllib2.urlopen()", stacklevel=2),说在 py3k 中已经移除了 urllib.urlopen,更加偏爱 urllib2.urlopen(),我想或多或少证明了我前面的一点猜测,guido 看不惯 urllib 里的一些代码,在 python2.6 里先给你些小提示。
在下一篇会深入探讨下两者 urlopen() 的实现方式的细节比较。
现在分析 urllib2 里的urlopen, 一般我们直接调用urlopen()就是表示去调用build_opener()方法,然后用build_opener()方法返回的类对象去调用该对象的open方法,下面给出部分build_opener()的代码:
#build_opener() def build_opener(*handlers): opener = OpenerDirector() default_classes = [ProxyHandler, UnknownHandler, HTTPHandler, HTTPDefaultErrorHandler, HTTPRedirectHandler, FTPHandler, FileHandler, HTTPErrorProcessor] skip = set() for klass in default_classes: for check in handlers: if isclass(check): if issubclass(check, klass): skip.add(klass) elif isinstance(check, klass): skip.add(klass) for klass in skip: default_classes.remove(klass) for klass in default_classes: opener.add_handler(klass()) for h in handlers: if isclass(h): h = h() opener.add_handler(h) return opener
这里我们可以看到,使用 urllib2.urlopen() 生成的是我们前面提到的管理很多处理器类的 OpenerDirector 操作类,然后给他加入很多的处理器,作为其一个属性,然后调用该处理器操作类对象的 open 方法就可以获取页面了。 流程就是:
- 生成一个处理器 opener = OpenerDirector() 对象
- 给这个处理器对象加入处理器 opener.add_handler(h)
- 使用打开方法,可能是具体 handler 类的打开方法。获取至本地的“文件流”对象,使用“文件流”。read() 获取内容,写入文件。
就是说,只要你是使用urllib2,不管你是要用代理,还是要用ftp,http,都逃不过这套过程。
下面再分析 urllib 里的 urlopen(),使用 urllib.urlopen() 方法会生成一个 FancyURLopener 类的对象,而 FancyURLopener 类是URLopener 类的子类,那么这个类对象直接调用 URLopener 类的 open(url) 方法就行了,对于使用者来说,urllib.urlopen() 的使用方式更易于使用,但这只是初步使用上简单,在深度使用后,urllib 就没有 urllib2 结构清晰了。
下面给出2种方法的不同实现:
1. HTTPHandler方式
#! -*- encoding:utf-8 -*- import urllib2 opener = urllib2.OpenerDirector() handler = urllib2.HTTPHandler() opener.add_handler(handler) content_stream = opener.open('http://www.baidu.com/') print content_stream.read()
2. ProxyHandler方式
#! -*- encoding:utf-8 -*- import urllib2 handler = urllib2.ProxyHandler(proxies = {'http' : 'http://217.66.205.76:8080/'}) opener = urllib2.build_opener(handler) f = opener.open('http://www.baidu.com/') print f.read()
这里可以看到做的操作有先实例化一个处理类,然后调用 build_opener 类产生我们的管理器对象,调用管理器的 open 法,就能获取网页内容了。细心的读者对这段示例程序不知道会不会有一个疑问,确定这里是以代理去打开的 baidu 首页吗?而不是这里的代理根本没起作用,实际仍然是以本地IP打开的?那么就需要测试了,只需要做一个简单的操作,将代理地址改一个不能使用的地址,比如:
#! -*- encoding:utf-8 -*- import urllib2 handler = urllib2.ProxyHandler(proxies = {'http' : 'http://216.664.205.76:8080/'}) opener = urllib2.build_opener(handler) f = opener.open('http://www.baidu.com/') print f.read()
运行这段代码就会得到错误的信息的。因为这代理地址根本就是我胡编乱造的。
3. FileHandler
#! -*- encoding:utf-8 -*- import urllib2 handler = urllib2.FileHandler() request = urllib2.Request(url='file:/D:\myapplesapple_id.txt') opener = urllib2.build_opener(handler) f = opener.open(request) print f.read()
注意这里一定要指定是文件类型,即url一定要有file:/而不能单单只写url='D:\myapplesapple_id.txt'
4. FTPHandler
先一步步的来:
#! -*- encoding:utf-8 -*- import urllib2 handler = urllib2.FTPHandler() request = urllib2.Request(url='ftp://www.×××××.com/') opener = urllib2.build_opener(handler) f = opener.open(request) print f.read()
执行结果显示:
if resp[0] == '3': resp = self.sendcmd('PASS ' + passwd) File "C:\Python26\lib\ftplib.py", line 243, in sendcmd return self.getresp() File "C:\Python26\lib\ftplib.py", line 218, in getresp raise error_perm, resp urllib2.URLError: <urlopen error ftp error: 530 Login or password incorrect!>
提示需要登录用户名和密码:
#! -*- encoding:utf-8 -*- import urllib2 handler = urllib2.FTPHandler() request = urllib2.Request(url='ftp://用户名:密码@ftp地址/') opener = urllib2.build_opener(handler) f = opener.open(request) print f.read()
执行以下,获得结果:
drwxr-xr-x 1 ftp ftp 0 Jan 08 2011 ***** drwxr-xr-x 1 ftp ftp 0 Jan 11 2011 ***** drwxr-xr-x 1 ftp ftp 0 Oct 28 2010 *******
OK了,带*的该ftp服务器下跟目录下的文件名。
以上是一些 urllib 2 基本使用方法,但大家也可以看出就那么一套模式。下面是 urllib 的在写这些程序的做法。
还是先给urllib的简单介绍吧。urllib 可以打开任意的一个 url 地址,遵循了一些标准,比如:
- RFC1808:相对路径处理方法
- RFC1738:标准url地址
- RFC1630:url细则
通过使用 URLopener().open(file) 将返回一个使用了不同协议操作的对象。接下来这个对象就可以调用像read(),readline(),readlines(),fileno(), close()和info()方法,大家可以看出很多都是类似于文件对象的方法。info方法返回一个mimetools.Message对象,能用于这个对象的各种信息状态的显示。如果使用info方法,将相应的调用getheader方法.
urllib中主题就2个类,一个URLopener类,一个FancyURLopener类,FancyURLopener是URLopener类的子类,也就是对URLopener类的扩展。而其他绝大部分的类都是围绕或者基于这2个类进行处理,一个urllib模块只要通了URLopener其他甚至都可以自己扩展了。
大多数情况下我们都是使用urllib.urlopen()。
刚才已经对比过这2个方法了,可以看到urllib.urlopen()可以直接使用代理,假如我们真的使用不那么高级点,这个还是不错的,而urllib2 的 urlopen 却不是能直接支持代理的。所以这对于很多同学认定 urllib 比 urllib2 好,不明白为什么会有 urllib2 这个玩意的一个原因吧。
直接使用urlopen:
#! -*- encoding:utf-8 -*- import urllib f = urllib.urlopen('http://www.baidu.com/') print f.read()
加入代理:
#! -*- encoding:utf-8 -*- import urllib f = urllib.urlopen(url='http://www.baidu.com/', proxies={'has_key' : 'http://216.66.205.76:8080/'}) print f.read()
这里对比一下urllib2的代理的写法:
- urllib2: proxies = {'http' : 'http://217.66.205.76:8080/'}
- urllib : proxies={'has_key' : 'http://216.66.205.76:8080/'}
这里没有谁要谁坏的一说,只是告诉大家加以区别而已。
3. 打开本地文件
#! -*- encoding:utf-8 -*- import urllib f = urllib.urlopen(url='file:/D:\\myapplesapple_id.txt') print f.read()
4. ftp
#! -*- encoding:utf-8 -*- import urllib f = urllib.urlopen(url='ftp://python:read@www.*****.com/') print f.read()
URLopener结构:
class URLopener: __tempfiles = None version = "Python-urllib/%s" % __version__ def __init__(self, proxies=None, **x509): def __del__(self): def close(self): def cleanup(self): def addheader(self, *args): def open(self, fullurl, data=None): def open_unknown(self, fullurl, data=None): def open_unknown_proxy(self, proxy, fullurl, data=None): def retrieve(self, url, filename=None, reporthook=None, data=None): def open_http(self, url, data=None): def http_error(self, url, fp, errcode, errmsg, headers, data=None): def http_error_default(self, url, fp, errcode, errmsg, headers): def open_https(self, url, data=None): def open_file(self, url): def open_local_file(self, url): def open_ftp(self, url): def open_data(self, url, data=None):
该类的设计逻辑是不管37是否等于21,先实例化该类对象出来,如果有代理,在实例化的时候将代理制定给这个实例化对象的一个属性,然后直接调用这个类的open方法,open方法里面有很多处理逻辑,比如,通过你给定url来判断要使用什么协议来对这个url进行处理,调用本类中的那个方法,设计中的一个经典核心代码:
urltype, url = splittype(fullurl) #解析url,分析出该url的特点,比如file,ftp,http,等 if not urltype: urltype = 'file' if urltype in self.proxies: proxy = self.proxies[urltype] urltype, proxyhost = splittype(proxy) host, selector = splithost(proxyhost) url = (host, fullurl) # Signal special case to open_*() else: proxy = None name = 'open_' + urltype self.type = urltype name = name.replace('-', '_') if not hasattr(self, name): if proxy: return self.open_unknown_proxy(proxy, fullurl, data) else: return self.open_unknown(fullurl, data) try: if data is None: return getattr(self, name)(url) #getattr()方法,如果name(形如'open_http','open_ftp','open_local_file')为真,返回这个属性, #即调用了这个方法,并且url是他传入的一个参数!从该类中其他方法名可以看出作者就是这个意思。 else: return getattr(self, name)(url, data) except socket.error, msg: raise IOError, ('socket error', msg), sys.exc_info()[2]
调用到符合 url 协议的方法了,然后就知道识别找方法,多么有意思的 python 代码,但是 guido 不满意,我不能妄自揣夺各种原因,一点我都不敢说,因为我在用他设计的语言,并且用的还很高兴。
urllib2模块和urllib模块类似,用来打开URL并从中获取数据。与urllib模块不同的是,urllib2模块不仅可以使用urlopen()函数还可以自定义opener来访问网页。但同时要注意:urlretrieve()函数是urllib模块中的,urllib2模块中不存在该函数。但是使用urllib2模块时一般都离不开urllib模块,因为post的数据需要使用urllib.urlencode()函数来编码。
下面介绍一下urllib2常用的方法。
urlopen(url, [,data, [timeout]])
urlopen()是最简单的请求方式,它打开url并返回类文件对象,并且使用该对象可以读取返回的内容。参数url可以是包含url的字符串,也可以是urllib2.request类的实例。data是经过编码的post数据(一般使用urllib.urlencode()来编码)。timeout是可选的超时期(以秒为单位),供所有阻塞操作内部使用。
假设urlopen()返回的文件对象u,它支持下面的这些常用的方法:
- u.read([nbytes]) 以字节字符串形式读取nbytes个数据
- u.readline() 以字节字符串形式读取单行文本
- u.readlines() 读取所有输入行然后返回一个列表
- u.close() 关闭链接
- u.getcode() 返回整数形式的HTTP响应代码,比如成功返回200,未找到文件时返回404
- u.geturl() 返回所返回的数据的实际url,但是会考虑发生的重定向问题
- u.info() 返回映射对象,该对象带有与url关联的信息,对HTTP来说,返回的服务器响应包含HTTP包头。对于FTP来说,返回的报头包含'content-length'。对于本地文件,返回的报头包含‘content-length’和'content-type'字段。
要注意的是,类文件对象u以二进制模式操作。如果需要以文本形式处理响应数据,则需要使用codecs模块或类似方式解码数据。
Request (url [data,headers [,origin_req_host ,[unverifiable]]]])
对于比较简单的请求,urlopen()的参数url就是一个代表url的但如果需要执行更复杂的操作,如修改HTTP报头,可以创建Request实例并将其作为url参数。
新建Request实例。url为url字符串,data是伴随url提交的数据(比如要post的数据)。不过要注意,提供data参数时,它会将HTTP请求从'GET'改为‘POST’。headers是一个字典,包含了可表示HTTP报头的键值映射(即要提交的header中包含的内容)。originreqhost通常是发出请求的主机的名称,如果请求的是无法验证的url(通常是指不是用户直接输入的url,比如加载图像的页面中镶入的url),则后一个参数unverifiable设为TRUE。
假设Request实例r,其比较重要的方法有下面几个:
- r.add_data(data) 向请求添加数据。如果请求是HTTP请求,则方法改为‘POST’。data是向指定url提交的数据,要注意该方法不会将data追教导之前已经设置的任何数据上,而是使用现在的data替换之前的。
- r.add_header(key, val) 向请求添加header信息,key是报头名,val是报头值,两个参数都是字符串。
- r.addunredirectedheader(key, val) 作用基本同上,但不会添加到重定向请求中。
- r.set_proxy(host, type) 准备请求到服务器。使用host替换原来的主机,使用type替换原来的请求类型。
自定义Opener
基本的urlopen()函数不支持验证、cookie或其他的HTTP高级功能。要支持这些功能,必须使用build_opener()函数来创建自己的自定义Opener对象。
install_opener(opener) 安装opener作为urlopen()使用的全局URL opener,即意味着以后调用urlopen()时都会使用安装的opener对象。opener通常是build_opener()创建的opener对象。
Python 标准库中有很多实用的工具类,但是在具体使用时,标准库文档上对使用细节描述的并不清楚,比如 urllib2 这个 HTTP 客户端库。这里总结了一些 urllib2 的使用细节。
- Proxy 的设置
- Timeout 设置
- 在 HTTP Request 中加入特定的 Header
- Redirect
- Cookie
- 使用 HTTP 的 PUT 和 DELETE 方法
- 得到 HTTP 的返回码
- Debug Log
Proxy 的设置
urllib2 默认会使用环境变量 http_proxy 来设置 HTTP Proxy。如果想在程序中明确控制 Proxy 而不受环境变量的影响,可以使用下面的方式:
import urllib2 enable_proxy = True proxy_handler = urllib2.ProxyHandler({"http" : 'http://some-proxy.com:8080'}) null_proxy_handler = urllib2.ProxyHandler({}) if enable_proxy: opener = urllib2.build_opener(proxy_handler) else: opener = urllib2.build_opener(null_proxy_handler) urllib2.install_opener(opener)
这里要注意的一个细节,使用 urllib2.install_opener() 会设置 urllib2 的全局 opener 。这样后面的使用会很方便,但不能做更细粒度的控制,比如想在程序中使用两个不同的 Proxy 设置等。比较好的做法是不使用 install_opener 去更改全局的设置,而只是直接调用 opener 的 open 方法代替全局的 urlopen 方法。
Timeout 设置
在老版 Python 中,urllib2 的 API 并没有暴露 Timeout 的设置,要设置 Timeout 值,只能更改 Socket 的全局 Timeout 值。
import urllib2 import socket socket.setdefaulttimeout(10) # 10 秒钟后超时 urllib2.socket.setdefaulttimeout(10) # 另一种方式
在 Python 2.6 以后,超时可以通过 urllib2.urlopen() 的 timeout 参数直接设置。
import urllib2 response = urllib2.urlopen('http://www.google.com', timeout=10)
在 HTTP Request 中加入特定的 Header
要加入 header,需要使用 Request 对象:
import urllib2 request = urllib2.Request(uri) request.add_header('User-Agent', 'fake-client') response = urllib2.urlopen(request)
对有些 header 要特别留意,服务器会针对这些 header 做检查:
- User-Agent : 有些服务器或 Proxy 会通过该值来判断是否是浏览器发出的请求。
- Content-Type : 在使用 REST 接口时,服务器会检查该值,用来确定 HTTP Body 中的内容该怎样解析。常见的取值有:
- application/xml : 在 XML RPC,如 RESTful/SOAP 调用时使用
- application/json : 在 JSON RPC 调用时使用
- application/x-www-form-urlencoded : 浏览器提交 Web 表单时使用
在使用服务器提供的 RESTful 或 SOAP 服务时, Content-Type 设置错误会导致服务器拒绝服务
Redirect
urllib2 默认情况下会针对 HTTP 3XX 返回码自动进行 redirect 动作,无需人工配置。要检测是否发生了 redirect 动作,只要检查一下 Response 的 URL 和 Request 的 URL 是否一致就可以了。
import urllib2 response = urllib2.urlopen('http://www.google.cn') redirected = response.geturl() == 'http://www.google.cn'
如果不想自动 redirect,除了使用更低层次的 httplib 库之外,还可以自定义 HTTPRedirectHandler 类。
import urllib2 class RedirectHandler(urllib2.HTTPRedirectHandler): def http_error_301(self, req, fp, code, msg, headers): pass def http_error_302(self, req, fp, code, msg, headers): pass opener = urllib2.build_opener(RedirectHandler) opener.open('http://www.google.cn')
Cookie
urllib2 对 Cookie 的处理也是自动的。如果需要得到某个 Cookie 项的值,可以这么做:
import urllib2 import cookielib cookie = cookielib.CookieJar() opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookie)) response = opener.open('http://www.google.com') for item in cookie: if item.name == 'some_cookie_item_name': print item.value
使用 HTTP 的 PUT 和 DELETE 方法
urllib2 只支持 HTTP 的 GET 和 POST 方法,如果要使用 HTTP PUT 和 DELETE ,只能使用比较低层的 httplib 库。虽然如此,我们还是能通过下面的方式,使 urllib2 能够发出 PUT 或 DELETE 的请求:
import urllib2 request = urllib2.Request(uri, data=data) request.get_method = lambda: 'PUT' # or 'DELETE' response = urllib2.urlopen(request)
这种做法虽然属于 Hack 的方式,但实际使用起来也没什么问题。
得到 HTTP 的返回码
对于 200 OK 来说,只要使用 urlopen 返回的 response 对象的 getcode() 方法就可以得到 HTTP 的返回码。但对其它返回码来说,urlopen 会抛出异常。这时候,就要检查异常对象的 code 属性了:
import urllib2 try: response = urllib2.urlopen('http://restrict.web.com') except urllib2.HTTPError, e: print e.code
Debug Log
使用 urllib2 时,可以通过下面的方法把 debug Log 打开,这样收发包的内容就会在屏幕上打印出来,方便调试,有时可以省去抓包的工作。
import urllib2 httpHandler = urllib2.HTTPHandler(debuglevel=1) httpsHandler = urllib2.HTTPSHandler(debuglevel=1) opener = urllib2.build_opener(httpHandler, httpsHandler) urllib2.install_opener(opener) response = urllib2.urlopen('http://www.google.com')
cookielib模块的主要作用是提供可存储cookie的对象,以便于与urllib2模块配合使用来访问Internet资源。例如可以利用本模块的CookieJar类的对象来捕获cookie并在后续连接请求时重新发送。coiokielib模块用到的对象主要有下面几个:CookieJar、FileCookieJar、MozillaCookieJar、LWPCookieJar。其中他们的关系如下:
CookieJar()
The CookieJar class stores HTTP cookies. It extracts cookies from HTTP requests, and returns them in HTTP responses.CookieJar instances automatically expire contained cookies when necessary. Subclasses are also responsible for storing and retrieving cookies from a file or database.
管理HTTP cookie值、存储HTTP请求生成的cookie、向传出的HTTP请求添加cookie的对象。整个cookie都存储在内存中,对CookieJar实例进行垃圾回收后cookie也将丢失。
FileCookieJar (filename,delayload=None,policy=None)
A CookieJar which can load cookies from, and perhaps save cookies to, a file on disk. Cookies are NOT loaded from the named file until either the load() or revert() method is called.
创建FileCookieJar实例,检索cookie信息并将cookie存储到文件中。filename是存储cookie的文件名。delayload为True时支持延迟访问访问文件,即只有在需要时才读取文件或在文件中存储数据。
MozillaCookieJar (filename,delayload=None,policy=None)
A FileCookieJar that can load from and save cookies to disk in the Mozilla cookies.txt file format (which is also used by the Lynx and Netscape browsers).Also note that cookies saved while Mozilla is running will get clobbered by Mozilla.
创建与Mozilla浏览器cookies.txt兼容的FileCookieJar实例。
LWPCookieJar (filename,delayload=None,policy=None)
A FileCookieJar that can load from and save cookies to disk in format compatible with the libwww-perl library’s Set-Cookie3file format. This is convenient if you want to store cookies in a human-readable file.
创建与libwww-perl的Set-Cookie3文件格式兼容的FileCookieJar实例。
除了上面几个函数之外,下面几个函数也很重要:
1. FileCookieJar. save ( filename=None , ignore_discard=False , ignore_expires=False )
Save cookies to a file.This base class raises NotImplementedError. Subclasses may leave this method unimplemented.filename is the name of file in which to save cookies. If filename is not specified, self.filename is used (whose default is the value passed to the constructor, if any); if self.filename is None, ValueError is raised. ignore_discard: save even cookies set to be discarded. ignore_expires: save even cookies that have expiredThe file is overwritten if it already exists, thus wiping all the cookies it contains. Saved cookies can be restored later using the load() or revert() methods.
2. FileCookieJar. load ( filename=None , ignore_discard=False , ignore_expires=False )
Load cookies from a file.Old cookies are kept unless overwritten by newly loaded ones.Arguments are as for save().The named file must be in the format understood by the class, or LoadError will be raised. Also, IOError may be raised, for example if the file does not exist.
3. FileCookieJar. revert ( filename=None , ignore_discard=False , ignore_expires=False )
Clear all cookies and reload cookies from a saved file. revert() can raise the same exceptions as load(). If there is a failure, the object’s state will not be altered.
cookielib模块一般与urllib2模块配合使用,主要用在urllib2.build_oper()函数中作为urllib2.HTTPCookieProcessor()的参数。使用方法如下面登录人人网的代码:
#! /usr/bin/env python #coding=utf-8 import urllib2 import urllib import cookielib data={"email":"用户名","password":"密码"} #登陆用户名和密码 post_data=urllib.urlencode(data) cj=cookielib.CookieJar() opener=urllib2.build_opener(urllib2.HTTPCookieProcessor(cj)) headers ={"User-agent":"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1"} req=urllib2.Request("http://www.renren.com/PLogin.do",post_data,headers) content=opener.open(req) print content2.read().decode("utf-8").encode("gbk")
再举个简单的例子:
#获取CookieJar实例 cj = cookielib.CookieJar() #自定义opener,并将opener跟CookieJar实例绑定 opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj)) #安装自定义的opener,此后调用urlopen()时都会使用安装过的opener对象 urllib2.install_opener(opener) url = "www.baidu.com" urllib2.urlopen(url)
基本的urlopen()函数不支持验证、cookie或其他HTTP高级功能。要支持这些功能,必须使用build_opener()函数来创建自己的自定义Opener对象。
install_opener(opener) 安装opener作为urlopen()使用的全局URL opener,即意味着以后调用urlopen()时都会使用安装的opener对象。opener通常是build_opener()创建的opener对象。
一些复杂情况详细解决办法:
1. cookie处理
如果要管理HTTP cookie,需要创建添加了HTTPCookieProcessor处理程序的opener对象。默认情况下。HTTPCookieProcessor使用CookieJar对象,将不同类型的CookieJar对象作为HTTPCookieProcessor的参数提供,可支持不同的cookie处理。如下面代码:
mcj=cookielib.MozillaCookieJar("cookies.txt") cookiehand=HTTPCookieProcessor(mcj) opener=urllib2.build_opener(cookiehand) u=opener.open(http://www.baidu.com)
2. 代理
urllib2会自动检测代理设置,默认使用环境变量http_proxy 来设置 HTTP Proxy通常情况下,这是很有帮助的,因为也可能造成麻烦(因为通过代理获取本地URL资源时会被阻止,因此如果你正在通过代理访问Internet,那么使用脚本测试本地服务器时必须阻止urllib2模块使用代理)。因此,如果想在程序中明确Proxy的使用而不受环境变量的影响,可以通过创建ProxyHandler实例,并将实例作为build_opener()的参数来实现。如下面代码:
import urllib2 enable_proxy = True proxy_handler = urllib2.ProxyHandler({"http" : 'http://some-proxy.com:8080'}) null_proxy_handler = urllib2.ProxyHandler({}) if enable_proxy: opener = urllib2.build_opener(proxy_handler) else: opener = urllib2.build_opener(null_proxy_handler) urllib2.install_opener(opener)
3. 一个简单的模拟登录例子:
#模拟登录 cj = cookielib.CookieJar() #用户名和密码 post_data = urllib.urlencode({'username': '[nowamagic]', 'password': '[mypass]', 'pwd': '1'}) #登录路径 #path = 'http://www.xiaomi.com/pass/serviceLoginAuth2' path = 'http://www.nowamagic.net/' cookieHandle = urllib2.HTTPCookieProcessor(cj) opener = urllib2.build_opener(cookieHandle) #url = opener.open('http://www.baidu.com') #page = url.read() opener.addheaders = [('User-agent', 'Opera/9.23')] urllib2.install_opener(opener) req = urllib2.Request(path, post_data) conn = urllib2.urlopen(req) result = conn.geturl() #print path #message = { #"header": conn.info(), #"status": conn.getcode(), #"url": conn.geturl(), #} self.render("nowamagic.html",message=result)
几乎所有脚本语言都提供了方便的 HTTP 客户端处理的功能,Python 也不例外,使用 urllib 和 urllib2 可以很方便地进行 HTTP GET 和 POST 等各种操作。并且还允许以类似于插件的形式加入一些 handler ,来定制 request 和 response ,比如代理的支持和 cookie 的支持都是这样添加进来的。具体来说,通过如下方式构造一个 opener :
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor())
然后这个 opener 就可以处理 cookie 了,相当方便,并且可定制性也……好吧,总之,现在我希望能在客户端手动插入一些 cookie 值,但是不管是HTTPCookieProcessor 还是 cookielib 里的 CookieJar 都没有提供类似的方法可以来实现。
看起来,也并不是我一个人有这样的需求,因为我在查找解决方案的时候,还找到了有人给 Python 提交的这个 Patch,就是添加这个功能。不过看起来好像还没有被 accept 的样子,这样对标准库做暴力 patch 的方式可移植性似乎也不好。所以我还是另外找了解决方案,其实也很简单:看了 HTTPCookieProcessor 的实现代码之后,发现我可以做类似的事情,也就是在写一个 handler ,把我想要的 cookie 值强制放到 request 对象的 header 中去。
于是我查了 Python 的文档,对于 handler 的接口好像几乎没有描述,于是我就照着 HTTPCookieProcessor 来写了。这个 handler 应该放在正常的 cookie 处理 handler 的后面,然后检查已经存在的 cookie header ,再进行合并一下。不过比较诡异的是在 Python 的文档里并没有找到 Request 对象有 get_header 之类的方法可以得到已经存在的 header 项的值,觉得很诡异,于是直接查了源代码,才找到了,确实有这个方法。之前有听人说过 Ruby 的文档做得如何如何的烂,Python 的文档做得如何如何的好,我虽然没觉得 Ruby 的文档很烂,但是也觉得 Python 的文档确实不错,我最喜欢它末尾的 Examples 。两个文档系统倒是走的不同的路,Ruby 的文档是从代码中抽取(特定格式的)注释来自动生成的,类似于 javadoc ;而 Python 现在用的是独立于源代码的文档系统,人工写的,不过到头来居然连函数都漏掉了,可见人工维护文档的弊端还是很明显的。其实我见过的文档系统,最好用的应该还是属于 Emacs/Elisp 了吧。 不过,废话少讲,handler 如下:
class SimpleCookieHandler(urllib2.BaseHandler): def http_request(self, req): simple_cookie = 'cc98Simple=1' if not req.has_header('Cookie'): req.add_unredirected_header('Cookie', simple_cookie) else: cookie = req.get_header('Cookie') req.add_unredirected_header('Cookie', simple_cookie + '; ' + cookie) return req
然后,构造 opener 的时候加上这个 handler 就可以了:
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(), SimpleCookieHandler())
但总归是一个 workaround ,期待那个 patch 被加入到标准库中吧。
前面简单提到了 Python 模拟登录的程序,但是没写清楚,这里再补上一个带注释的 Python 模拟登录的示例程序。简单说一下流程:先用cookielib获取cookie,再用获取到的cookie,进入需要登录的网站。
# -*- coding: utf-8 -*- # !/usr/bin/python import urllib2 import urllib import cookielib import re auth_url = 'http://www.nowamagic.net/' home_url = 'http://www.nowamagic.net/'; # 登陆用户名和密码 data={ "username":"nowamagic", "password":"pass" } # urllib进行编码 post_data=urllib.urlencode(data) # 发送头信息 headers ={ "Host":"www.nowamagic.net", "Referer": "http://www.nowamagic.net" } # 初始化一个CookieJar来处理Cookie cookieJar=cookielib.CookieJar() # 实例化一个全局opener opener=urllib2.build_opener(urllib2.HTTPCookieProcessor(cookieJar)) # 获取cookie req=urllib2.Request(auth_url,post_data,headers) result = opener.open(req) # 访问主页 自动带着cookie信息 result = opener.open(home_url) # 显示结果 print result.read()
再附带几个示例程序:
1. 使用已有的cookie访问网站
import cookielib, urllib2 ckjar = cookielib.MozillaCookieJar(os.path.join('C:\Documents and Settings\tom\Application Data\Mozilla\Firefox\Profiles\h5m61j1i.default', 'cookies.txt')) req = urllib2.Request(url, postdata, header) req.add_header('User-Agent', \ 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)') opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(ckjar) ) f = opener.open(req) htm = f.read() f.close()
2. 访问网站获得cookie,并把获得的cookie保存在cookie文件中
import cookielib, urllib2 req = urllib2.Request(url, postdata, header) req.add_header('User-Agent', \ 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)') ckjar = cookielib.MozillaCookieJar(filename) ckproc = urllib2.HTTPCookieProcessor(ckjar) opener = urllib2.build_opener(ckproc) f = opener.open(req) htm = f.read() f.close() ckjar.save(ignore_discard=True, ignore_expires=True)
3. 使用指定的参数生成cookie,并用这个cookie访问网站
import cookielib, urllib2 cookiejar = cookielib.CookieJar() urlOpener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookiejar)) values = {'redirect':", 'email':'abc@abc.com', 'password':'password', 'rememberme':", 'submit':'OK, Let Me In!'} data = urllib.urlencode(values) request = urllib2.Request(url, data) url = urlOpener.open(request) print url.info() page = url.read() request = urllib2.Request(url) url = urlOpener.open(request) page = url.read() print page