urllib2 源码小剖
urllib2 源码小剖
2013-08-25 23:38 by 捣乱小子, 272 阅读, 0 评论, 收藏, 编辑
两篇小剖已经完成:
urllib2 对 urllib 改进了好些,整个思路都改了,变得更加灵活,有“高内聚,低耦合”的味道。
看看 urllib2 的新特性:
- 将对 url 的处理单独成一个 request 类
- URLopener 和 FancyURLopener 都下架,取而代之的是 OpenerDirector
- 另添加了 N 多 handlers,这些 handlers 主要对 HTTP 连接,HTTP request 或者 HTTP response 的处理,譬如往 HTTP request 中添加几个特定的 cookies 或者状态码处理,所有你能想到的 HTTP request 的预处理或者 HTTP response 的善后处理,稍后展开。
OpenerDirector
介绍完特性,应该能够想到 OpenerDirector 可以安装多个 handlers,想做什么就做什么,甚至可以自定义自己的 handler,我在项目中就遇到源码无法满足的需求,得“靠自己的双手”。
跟上篇 urllib 的套路一样,
1
2
3
|
用法一模一样。
urlopen 也新建一个对象,不过是一个 OpenerDirector 对象;它内部是通过调用 build_opener 方法来创建的:
urlopen 和 build_opener 方法的源码:
1
2
3
4
5
6
7
8
9
|
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) def build_opener( * handlers): 可以安装多个 handler ...... return opener |
目测这里可以传递多个 handlers 安装好后返回 opener。
_opener 是一个全局的对象,它调用 open() 它的作用和 urllib 一样,但做法是不同的。
从类 OpenerDirector 说起:
addheaders 添加 HTTP headers 的方法
handlers 各种处理工具类对象,里面可以包含上面提到的预处理和善后处理
handle_open 一个 key-value_list,存着发起连接的 handler,一般只有一个;key 是协议,http,https;value_list 就是方法集了
hander_error 一个 key-value_list 同上;根据状态码进行各种处理
process_response 一个 key-value_list 同上;预处理
process_request 一个 key-value_list 同上;善后处理
这四个成员变量很重要,因为各种 handler 内的方法会根据方法名安置在这个四个 key-value_list 中,在不同的时机调用。
add_handler 添加各种处理工具类对象,它会自动扫描整个对象中的所有方法,根据方法的名字添加到不同的 key-value_list 中。
_call_chain 这个取名叫链式调用,就是把 key-value_list 中的方法依次调用
open 关键来了:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
def open ( self , fullurl, data = None , timeout = socket._GLOBAL_DEFAULT_TIMEOUT): # accept a URL or a Request object if isinstance (fullurl, basestring ): req = Request(fullurl, data) else : req = fullurl if data is not None : req.add_data(data) req.timeout = timeout protocol = req.get_type() # pre-process request 预处理 meth_name = protocol + "_request" # http_request ftp_request for processor in self .process_request.get(protocol, []): meth = getattr (processor, meth_name) # method method req = meth(req) 进行各种处理 response = self ._open(req, data) # post-process response 回复可能要进行修饰 meth_name = protocol + "_response" # http_respond ftp_respond for processor in self .process_response.get(protocol, []): meth = getattr (processor, meth_name) 进行各种对答复处理 response = meth(req, response) return response 返回 |
先构造 Request 对象;设置超时时间;预处理,连接,善后处理,一目了然。
_open 从 open 中得知,它在预处理和善后处理之间,应该负责连接访问服务器也就是发起连接的,从源码来看也没有说明秘密:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
def _open( self , req, data = None ): # 如果有默认的处理,就用默认的处理方法 result = self ._call_chain( self .handle_open, 'default' , 'default_open' , req) if result: return result # 如果没有默认处理,试着用 handle_open list 中方法的 http_open protocol = req.get_type() result = self ._call_chain( self .handle_open, protocol, protocol + '_open' , req) if result: return result # error return self ._call_chain( self .handle_open, 'unknown' , 'unknown_open' , req) |
error 各种错误处理,或者处理各种状态码,从成员变量 handle_error 中选择方法
至此 OpenerDirector 介绍到这里
build_opener 这是一个全局方法,上面提到过,能创建 OpenerDirector 对象,并安装多个 handlers
这里他用的策略:默认安装 default_handlers,default_handlers 是内部指定的,倘若用户继承了 default handler,会用子类替代父类。
内置 handlers
我们知道,OpenerDirector 可以安装自定义的 handler,那就必须入乡随俗,自定义的 handler 必须被规范定义。
下面说到的是一堆内置的 handlers:
BaseHandler 所有 handler 都必须继承这个。
HTTPErrorProcessor HTTP 错误处理,内部有一个方法:
1
2
3
4
5
6
7
8
9
10
|
def http_response( self , request, response): code, msg, hdrs = response.code, response.msg, response.info() # According to RFC 2616, "2xx" code indicates that the client's # request was successfully received, understood, and accepted. if not ( 200 < = code < 300 ): # good response = self .parent.error( 'http' , request, response, code, msg, hdrs) return response |
如果状态码不是 200-300,就调用父类的 error,error 能调遣各种错误处理或者状态码处理 handers,交由他们处理。
HTTPDefaultErrorHandler 万能的,不想理会的错误就用它
HTTPRedirectHandler 重定向处理,状态码是 30X 的时候会用到
ProxyHandler 如果开了代理,会用到这个 handler
HTTPPasswordMgr 秘密管理器,它里面会根据不同的连接,不同的 realm 管理密码
AbstractBasicAuthHandler HTTP 认证抽象类
HTTPBasicAuthHandler 继承自 AbstractBasicAuthHandler 类,HTTP 基本认证
AbstractBasicAuthHandler 继承自 AbstractBasicAuthHandler 类;如果开了代理,需要用这个认证类
AbstractHTTPHandler HTTP 处理抽象类,其实不抽象了;它内部有一个 do_open() 方法,是 HTTP 连接的核心,它返回上一节提到过的 addinfourl 对象,动作也和上一节提到的 open_http() 大同小异。
HTTPHandler 这个类里有个 http_open() 方法,它会被安置在 OpenerDirector 对象的 handle_open list 中,很明显它是管理 HTTP 连接的,发送或者接受数据,其内部调用上面提到的 do_open() 方法。
HTTPSHandler HTTPS
HTTPCookieProcessor 里面有 http_request 和 http_response 方法,分别是 cookies 的预处理和善后处理,用作设置和提取 cookies
UnknownHandler 里面定义了 unknown_open 方法;当遇到无法理解的 url 时候,就会被调用
FileHandler 处理本地文件或者 ftp,这要视被传入的 url 而定
FTPHandler 封装了 ftp 的处理,会创建上一节提到的 ftpwrapper。
CacheFTPHandler 带缓存的 FTPHandler,里面主要记录最近 ftp 连接的信息(实际上是 ftpwrapper 对象),并各自设置了过期时间。
自定义 handlers
上面提到可以自定义 handlers,我们从处理 cookies 的类 HTTPCookieProcessor 看起,依葫芦画瓢:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
class HTTPCookieProcessor(BaseHandler): def __init__( self , cookiejar = None ): import cookielib if cookiejar is None : cookiejar = cookielib.CookieJar() self .cookiejar = cookiejar def http_request( self , request): # add to request self .cookiejar.add_cookie_header(request) return request def http_response( self , request, response): # extract from response 从 response 中导出 self .cookiejar.extract_cookies(response, request) return response https_request = http_request 一样 https_response = http_response |
tips:结合上面的 open() 方法,http_request 中传入的参数 request 是 Request 对象;http_response 中传入的参数 response 是 addinfourl 对象。
很简单,我们试着写一个,作用是打印 HTTP reques 和 HTTP response 的头部:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
from urllib2 import BaseHandler, build_opener class HTTPHeaderPrint(BaseHandler): def __init__( self ): pass def http_request( self , request): print request.headers return request def http_response( self , request, response): print response.info() return response https_request = http_request https_response = http_response if __name__ = = '__main__' : opener = build_opener(HTTPHeaderPrint) |
结果是:
{}
Date: Sun, 25 Aug 2013 03:06:56 GMT
Server: BWS/1.0
Content-Length: 10433
Content-Type: text/html;charset=utf-8
Cache-Control: private
BDPAGETYPE: 1
BDUSERID: 0
BDQID: 0x8355f67f035cabd2
Set-Cookie: BDSVRTM=1; path=/
Set-Cookie: H_PS_PSSID=3194_1443_3139_2975_2981_2702; path=/; domain=.baidu.com
Set-Cookie: BAIDUID=5300395E1E4ACB08A7C131F72AD493B6:FG=1; expires=Sun, 25-Aug-43 03:06:56 GMT; path=/; domain=.baidu.com
Expires: Sun, 25 Aug 2013 03:06:56 GMT
P3P: CP=" OTI DSP COR IVA OUR IND COM "
Connection: Close
可以看到,自定义的 handler 已经被安装成功,即 http_request 方法已被安置在 process_request list 中; http_response 方法已被安置在 process_response list 中。从结果看,request 的 HTTP 头并没有任何东西,因为我们并没有添加任何额外的头部,但在连接的时候,是有添加一些 HTTP 头部信息,但并没有被记录;返回的 HTTP response 中就有了。
总结
总结一下 urllib2:urlopen 是最高层的封装,很简单的一句话就可以爬数据了。其内部创建 OpenerDirector 对象并调用 open() 方法,open 方法会根据 url 构造 Request 对象,然后根据 process_request 内的方法进行预处理,根据 handle_open 内的方法,进行连接处理,根据 process_response 内的方法进行善后处理;当 url 不成功时候或者 HTTP response 状态码需要进一步处理,则会调用相应的错误或者状态码处理。
至此,urllib 和 urllib2 的小剖完毕,期中的秘密也不算多,但从 urllib 到 urllib2 的改进是值得我们在软件设计过程的值得深思的,明显 urllib2 比 urllib 会更具伸缩性。另外,urllib2 用到的应该是某种设计模式,记得的童鞋不忘留言告诉我。
捣乱 2013-08-25
urllib2 源码小剖的更多相关文章
- urllib 源码小剖
urllib 源码小剖 urllib 是 python 内置的网络爬虫模块,如果熟悉 python 一定能很快上手使用 urllib. 写这篇文章的目的是因为用到了它,但因为用的次数较多,又或者是具体 ...
- Django 源码小剖: 响应数据 response 的返回
响应数据的返回 在 WSGIHandler.__call__(self, environ, start_response) 方法调用了 WSGIHandler.get_response() 方法, 由 ...
- Django 源码小剖: 初探 WSGI
Django 源码小剖: 初探 WSGI python 作为一种脚本语言, 已经逐渐大量用于 web 后台开发中, 而基于 python 的 web 应用程序框架也越来越多, Bottle, Djan ...
- Django 源码小剖: Django 对象关系映射(ORM)
引 从前面已经知道, 一个 request 的到来和一个对应 response 的返回的流程, 数据处理和数据库离不开. 我们也经常在 views.py 的函数定义中与数据库打交道. django O ...
- Django 源码小剖: Django 中的 WSGI
Django 其内部已经自带了一个方便本地测试的小服务器, 所以在刚开始学习 Django 的时候并不需搭建 apache 或者 nginx 服务器. Django 自带的服务器基于 python w ...
- Django 源码小剖: Django ORM 查询管理器
ORM 查询管理器 对于 ORM 定义: 对象关系映射, Object Relational Mapping, ORM, 是一种程序设计技术,用于实现面向对象编程语言里不同类型系统的数据之间的转换.从 ...
- Django 源码小剖: 更高效的 URL 调度器(URL dispatcher)
效率问题 django 内部的 url 调度机制说白了就是给一张有关匹配信息的表, 这张表中有着 url -> action 的映射, 当请求到来的时候, 一个一个(遍历)去匹配. 中, 则调用 ...
- Django 源码小剖: URL 调度器(URL dispatcher)
在刚开始接触 django 的时候, 我们尝试着从各种入门文档中创建一个自己的 django 项目, 需要在 mysite.urls.py 中配置 URL. 这是 django url 匹配处理机制的 ...
- Django 源码小剖: 初探中间件(middleware)
因为考虑到文章的长度, 所以 BaseHandler 的展开被推迟了. 在 BaseHandler 中隐藏着中间件的信息, 较常见的 SessionMiddleware 就已经默认安装. BaseH ...
随机推荐
- 【百度地图API】如何利用自己的数据制作社交地图?只显示可视区域内的标注
原文:[百度地图API]如何利用自己的数据制作社交地图?只显示可视区域内的标注 摘要:如果你自己的数据已经超过1万个,如何进行合理的显示?除了聚合marker外,还有一个办法.那就是,只显示可视区域内 ...
- UI 收集
semantic http://www.semantic-ui.com.cn/modules/reveal.html sbadmin http://startbootstrap.com/templat ...
- 表现层及ASP.NET MVC介绍(一)
表现层及ASP.NET MVC介绍(一) 本文将介绍表现层及ASP.NET MVC的一些要点,特别是ASP.NET MVC的一些抽象和封装技巧,如果你对MVC还不了解,可以参考<ASP.NET ...
- C语言第12轮:指针
C语言第12轮:指针 [学习目标] 1. 指针 2. 指针与数组 A: 指针的概念 内存存储单元按字节排序.每一个字节编有序号.我们称之为地址.因为能够通过地址就能够找到所 ...
- 学习的例子gcc+gdb+make
1 小侃GCC 在正式使用gcc之前,我们先来侃侃gcc是啥玩意儿? 历史 如今的GCC是GNU Compiler Collection的简称.既然是Collection,就是指一些工具链的集合. 最 ...
- 在 VS2013的ASPNET站点开发中用 xheditor v1.1.13 作为HTML编辑器
要用vs2013开发一个博客站点,.net framework 4,须要一个HTML编辑器作为写文章的工具.经多方试用,排除了dotnettextbox.kceditor.认为xheditor ...
- SQL点滴10—使用with语句来写一个稍微复杂sql语句,附加和子查询的性能对比
原文:SQL点滴10-使用with语句来写一个稍微复杂sql语句,附加和子查询的性能对比 今天偶尔看到sql中也有with关键字,好歹也写了几年的sql语句,居然第一次接触,无知啊.看了一位博主的文章 ...
- OpenWrt的UCI系统
http://wiki.openwrt.org/doc/uci UCI是Unified Configuration Interface的缩写,翻译成中文就是统一配置接口,用途就是为OpenWrt提供一 ...
- js实现是倒计时功能
工作中经常用到倒计时的功能,最近在整理之前做的项目的时候,发现自己写过一个倒计时的功能的效果,这里和大家分享下!实现这个功能是用原生js写的,不需要加载额外的库文件!功能比较简单,但是可以在此基础上扩 ...
- nginx 502 Bad Gateway 错误问题收集
nginx 502 Bad Gateway 错误问题收集 (2010-11-18 13:51:37) 转载▼ 标签: 杂谈 分类: 工作 nginx 502 Bad Gateway 错误问题收集 因为 ...