urllib提供了较好的封装,可以很方便的读取http,ftp,file等协议数据,本篇只关注http。urllib的底层还是使用httplib模块,相比于httplib,urllib接口更加好用,功能更加强大。支持http代理,可从环境变量中获取代理信息,支持http basic auth,可自动处理302等。但也有不足,如不支持gzip等压缩编码,不支持摘要认证,NTML认证等。

urllib快速使用

urllib的urlopen方法很好用,代理使用如下:

import urllib
opener=urllib.urlopen('http://www.baidu.com/',proxies={'http':'http://root:root2@192.168.1.101:808'})
print opener.code
urlopen返回一个类文件对象,具有一些列方法,read(),readline(),readlines(),close()等,其中info()方法和readhers属性都是返回http.HTTPMessage实例。HTTPMessage是继承自mimetools.Message,具有读取状态码或邮件头这样格式的内容。

注意:http://www.baidu.com/需要在域名后加"/",不然会报"IOError: ('http protocol error', 0, 'got a bad status line', None)”,且代理站点需要添加http前缀。

urllib还有一个比较好用的下载方法,urlretrieve(url, filename=None, reporthook=None, data=None)
该函数可指定回调函数reporthook(blocknum, bs, size),默认下载1024*8字节回调一次,也就是bs大小,blocknum是块数量,其实就是回调的次数,size是下载文件总大小,通过"Content-Length"获取。data是需要post的值,filename是下载文件名。返回一个(本地filename,HTTPMessage)元组 。
import urllib
def reporthook(bk,bs,total):print bk*bs,'b'
filename,message=urllib.urlretrieve("http://ww.baidu.com/",None,reporthook)
print message.getheader('Content-Length')

返回结果:
8192 b
16384 b
11394

你会看到大小不太一样,难免的,因为最后一次可能没有bs大小可读了,但回调还是调用了。
urlretrieve是基于URLopener.retrieve的,在看当作也遇到了一点疑惑,在获取“Content-Length"头信息时,代码如下:
fp = self.open(url, data)
headers = fp.info()
if "content-length" in headers:size = int(headers["Content-Length"])

看的我疑惑了,这headers到底是什么类型啊,怎么那么像字典,还能自动处理大小写?
原来这是rfc822.Message实例,定义了__contains__和__getitem__,果然源码之下,没有秘密可言。

class Message:def __contains__(self, name):"""Determine whether a message contains the named header."""return name.lower() in self.dictdef __getitem__(self, name):"""Get a specific header, as from a dictionary."""return self.dict[name.lower()]

urllib源码分析

1.总体流程

  u rlopen其实是urllib.FancyURLopener的实例,且可全局缓存,避免了多次创建。FancyURLopener是继承自urlib.URLopener的。FancyURLopener更好的处理了一些特殊状态情况,如302,401,407等,如支持最大10次302跳转。而URLopener出现302等会直接报IOError状态码异常,如果为了获取真实返回状态码需要用URLopener,URLopener通样支持代理等基本特性,如:
当出现状态码异常时,URLopener将调用http_error方法,如果存在“ 'http_error_%d' % errcode”方法的话,http_error将调用。FancyURLopener定义了较多的特殊状态码处理函数,如http_error_302方法。所以说,如果想定制自己的urllib可以通用继承于URLopener或FancyURLopener,并定义自己的状态码处理函数。

2.http basic auth(基本认证)

   这是很简单的一种认证方式,每次访问通过base64加密将"用户名:密码”作为“Authorization”头发送,并没有作为cookie。下面是我简化的请求-响应过程信息。
GET / HTTP/1.1
Host: 127.0.0.1HTTP/1.1 401 Authorization Required
WWW-Authenticate: Basic realm="my site"----------------------------------------------------------GET / HTTP/1.1
Host: 127.0.0.1
Authorization: Basic cm9vdDpyb290HTTP/1.1 200 OK

我们可以简单配置一下apache,修改httpd.conf,在需要认证的的directory下,添加AllowOverride authconfig,

<Directory "c:/wamp/www/">AllowOverride authconfigOrder Deny,AllowDeny from allAllow from 127.0.0.1
</Directory>
然后在c:/wamp/www/目录下新建.htaccess,我的如下
authname "my site"
authtype basic
authuserfile c:/wamp/www/user.txt
require valid-user

其中authname也就是提示信息,会出现在“WWW-Authenticate”头的“realm"(以前一直很奇怪,realm到底是什么?)。authuserfile最好绝对路径。
我们知道了认证方式,那么编码也就简单了。

user_passwd, realhost = splituser(realhost) #realhost类似root:root@www.baiud.com
if user_passwd:import base64auth = base64.b64encode(user_passwd).strip()else:auth = Noneh = httplib.HTTP(host)if auth: h.putheader('Authorization', 'Basic %s' % auth)

当url中没有提供用户名:密码时,但站点的确需要认证时,将返回401状态码,这时将调用http_error_401,如果出现"www-authenticate"响应头,将判断是否是basic认证。如果是将调用retry_http_basic_auth,这个函数从缓存中寻找有无该站点的用户名:密码,如果没有将调用prompt_user_passwd,提示用户输入(如果在GUI环境下需要重写该函数),并将用户名:密码缓存,如下

第二次请求是不需要用户名和密码的,用户名密码缓存在auth_cache字典中。(PS:写代码时缓存思想很重要)

3.代理

    代理其实和http basic认证一样,都是通过发送请求头来实现的,下面是我使用工具CCProxy设置本地代理后截取的请求--响应,如下:
GET / HTTP/1.1
Host: www.baidu.comHTTP/1.0 407 Unauthorized
Server: Proxy
Proxy-Authenticate: Basic realm="CCProxy Authorization"GET / HTTP/1.1
Host: www.baidu.com
Proxy-Authorization: Basic cm9vdDpyb290Mg==HTTP/1.1 200 OK

第一次访问百度,由于设置了代理,所以返回了407状态码,”其中Basic realm“就是网页认证对话标题。如果代理需要”用户名:密码“认证,和http基本认证一样,每次都将”用户名:密码“base64加密作为”Proxy-Authorization“。

URLopener构造函数如下:
def __init__(self, proxies=None, **x509):if proxies is None:proxies = getproxies()assert hasattr(proxies, 'has_key'), "proxies must be a mapping"self.proxies = proxies

我们看到,如果没有设置代理,将调用getproxies()函数获取本地环境变量中的代理。

def getproxies_environment():proxies = {}for name, value in os.environ.items():name = name.lower()if value and name[-6:] == '_proxy':proxies[name[:-6]] = valuereturn proxies

本地环境变量中代理一般设置如下,如在linux中.bashrc设置
export http_proxy="16.111.53.167:808"
export ftp_proxy="16.111.53.167:328"

如果设置代理,将向代理主机发送请求,其余和HTTP Basic认证相同。

urllib提供的有用函数

1. quote(s, safe = '/'):   url编码,将保留字符编码为%20格式,主要用来编码path前面信息

    其实这个函数主要用来给path,query编码用的,如果将整个url编码肯定有问题的, 因为默认只有数字+字母+'_.-/'不编码,如果需要传递整个url,可将第二个参数设置为"%/:=&?~#+!$,;'@()*[]|",其实urllib会主动编码的,代码如下:
fullurl = quote(fullurl, safe="%/:=&?~#+!$,;'@()*[]|")

增补:从python2.6版本才会主动编码,2.5以及低版本不会

2. unquote(s):   quote的逆过程。

3. quote_plus(s, safe = ''):  query字符串编码,与quote不同点就是将“空格”用“+"表示,主要用于urlencode函数中。

4. unquote_plus(s):   quote_plus的逆过程。

5.urlencode(query,doseq=0) :   将2元素的序列或字典编码成查询字符串,doseq允许第二元素也可以为序列,query字符串都是用quote_plus编码。

注意:这里有必要对quote,quote_plus和urlencode做个说明

quote用于url的编码,而quote_plus用于post的data编码,所以而urlencode内部是用quote_plus实现的,所以urlencode一般只用做post data使用,get时不用。

官方文档说明:

urllib.unquote(string)
Replace %xx escapes by their single-character equivalent.

Example: unquote('/%7Econnolly/') yields '/~connolly/'.

urllib.unquote_plus(string)
Like unquote(), but also replaces plus signs by spaces, as required for unquoting HTML form values.

urllib.urlencode(query[, doseq])
Convert a mapping object or a sequence of two-element tuples to a “percent-encoded” string, suitable to pass to urlopen() above as the optional data argument. This is useful to pass a dictionary of form fields to a POST request. The resulting string is a series of key=value pairs separated by '&' characters, where both key and value are quoted using quote_plus() above. When a sequence of two-element tuples is used as the query argument, the first element of each tuple is a key and the second is a value. The value element in itself can be a sequence and in that case, if the optional parameter doseq is evaluates to True, individual key=value pairs separated by '&' are generated for each element of the value sequence for the key. The order of parameters in the encoded string will match the order of parameter tuples in the sequence. The urlparse module provides the functions parse_qs() and parse_qsl() which are used to parse query strings into Python data structures.

6 .url2pathname:   将file协议url转换为本地路径

    以windows来说,file://host/dirs...,如何是本地可省略host,如
     
     看几个简单结果:
        
7. urllib提供了一些列解析url各部分的函数,和urlparse不同的是基本上都是用正则匹配的。
如获取协议的splittype函数,注意_typeprog正则只在第一次的编译,如果有类似情况可以仿写。不过,话说回来,如果用的比较多,并不在意导入该模块时花费一点时间
编译正则对象,完全可以在全局编译。
_typeprog = None
def splittype(url):"""splittype('type:opaquestring') --> 'type', 'opaquestring'."""global _typeprogif _typeprog is None:import re_typeprog = re.compile('^([^/:]+):')match = _typeprog.match(url)if match:scheme = match.group(1)return scheme.lower(), url[len(scheme) + 1:]return None, url

Python之美[从菜鸟到高手]--urllib源码分析相关推荐

  1. Python之美[从菜鸟到高手]--一步一步动手给Python写扩展(异常处理和引用计数)

    我们将继续一步一步动手给Python写扩展,通过上一篇我们学习了如何写扩展,本篇将介绍一些高级话题,如异常,引用计数问题等.强烈建议先看上一篇,Python之美[从菜鸟到高手]--一步一步动手给Pyt ...

  2. python之美_Python之美[从菜鸟到高手]--生成器之全景分析

    yield指令,可以暂停一个函数并返回中间结果.使用该指令的函数将保存执行环境,并且在必要时恢复. 生成器比迭代器更加强大也更加复杂,需要花点功夫好好理解贯通. 看下面一段代码: [python] d ...

  3. python unit test 访问开发代码_python unittest 源码分析

    unittest单元测试框架总结 unittest单元测试框架既可以适用于单元测试,也能够适用WEB自动化测试用例的开发与执行,该测试框架可组织执行测试用例,并且提供了丰富的断言方法,判断测试用例是否 ...

  4. Java之美[从菜鸟到高手演变]系列之博文阅读导航

    随着博文越来越多,为博客添加一个导航很有必要!本博客将相继开通Java.CloudFoundry.Linux.Ruby等专栏,都会设立目录,希望读者朋友们能更加方便的阅读! 在阅读的过程中有任何问题, ...

  5. Java之美[从菜鸟到高手演变]之Exception

    版权声明:本文为博主原创文章,未经博主允许不得转载. Exception这个东西,程序中必须会有的,尽管我们很不乐意看到它,可是从另一个角度考虑,有异常则说明程序有问题,有助于我们及时改正.有的时候程 ...

  6. Java之美[从菜鸟到高手演变]之字符串

    写程序就像生活,有酸甜苦辣,关键在于过程,任何事情的过程都是美好的,是值得我们回味的!有人说,编程是一种艺术,艺术出于生活却高于生活,每一个细节都值得细细品味...程序员无非就是两件事:学习和分享!独 ...

  7. Java之美[从菜鸟到高手演变]之集合类【吐血推荐!讲得太好了!!!】

    source: http://blog.csdn.net/zhangerqing/article/details/8122075 最近在找工作,目前还没有定下来,拿到了一个公司的offer,不过被当白 ...

  8. Java之美[从菜鸟到高手演变]之智力题【史上最全】

    智力题,每个正式的笔试.面试都会出,而且在面大企业的时候必然会问到,笔者曾在很多面试中,都被问到过,不过答得都不是很好,因为时间很短,加上我们有时候过于紧张,所以做出这类问题,还是有一定的难度,从这篇 ...

  9. Java之美[从菜鸟到高手演变]之智力题【史上最全】 .

    智力题,每个正式的笔试.面试都会出,而且在面大企业的时候必然会问到,笔者曾在很多面试中,都被问到过,不过答得都不是很好,因为时间很短,加上我们有时候过于紧张,所以做出这类问题,还是有一定的难度,从这篇 ...

最新文章

  1. php之static静态变量详解
  2. MySQL 报错MySQL server syntax to use near 'OPTION SQL_SELECT_LIMIT=DEFAULT'
  3. Binder 驱动详解(下)
  4. js Date对象总结
  5. php 开源 采集,迅睿CMS 火车头内容采集
  6. python functools.reduce_Python之functools.reduce使用
  7. VC 下加载 JPG / JPEG / GIF / PNG 图片最简单的方法
  8. WAMP安装curl扩展并发起https请求
  9. orangepi香橙派安装VNC Viewer远程桌面
  10. python计算正弦值_Python科学计算(二)——正弦信号的时域波形与频谱图
  11. 采用Zigbee和Raspberry Pi的太阳能/燃气热水器自动控制系统
  12. p系列服务器产品介绍,常用p系列服务器RS6000服务器产品号码对照表.doc
  13. sql 行政区划关联查询优化_民政部:四季度继续开展优化行政区划设置研究
  14. 本地系统盘放到服务器上,怎么把本地盘挂到云服务器
  15. 私有云搭建使用docker搭建
  16. word中方框中打钩
  17. filebeat报错error pipeline/output.go:100 failed to connect to backoff(async(tcp://xx.xx.xx.xx:))...
  18. 如何寻求知识产权的刑事救济
  19. coreldraw插件编写
  20. 师范类专业计算机和数学哪个好,师范类数学与应用数学专业考研考哪个方向好?是本专......

热门文章

  1. LaTeX分享【LaTeX中文字的基本使用】
  2. avro数据格式说明
  3. matlab resampc,MATLAB contourlet工具箱
  4. 入门力扣自学笔记52 C++ (题目编号929)
  5. 客户的管理及分类怎么做?
  6. 360问答公开采集百度知道
  7. 激光电视会是一个风口,我这话撂这
  8. 十分钟智商运动 李永乐 有趣的数学 待补充
  9. 社交直播app开发详细流程
  10. 我的世界怎么修改服务器跨版本,我的世界跨版本插件