本节书摘来自华章计算机《python爬虫开发与项目实战》一书中的第3章,第3.2节,作者:范传辉著,更多章节内容可以访问云栖社区“华章计算机”公众号查看
通过上面的网络爬虫结构,我们可以看到读取url、下载网页是每一个爬虫必备而且关键的功能,这就需要和http请求打交道。接下来讲解python中实现http请求的三种方式:urllib2/urllib、httplib/urllib以及requests。
3.2.1 urllib2/urllib实现
urllib2和urllib是python中的两个内置模块,要实现http功能,实现方式是以urllib2为主,urllib为辅。
1.?首先实现一个完整的请求与响应模型
urllib2提供一个基础函数urlopen,通过向指定的url发出请求来获取数据。最简单的形式是:
其实可以将上面对<code>http://www.zhihu.com</code>的请求响应分为两步,一步是请求,一步是响应,形式如下:
上面这两种形式都是get请求,接下来演示一下post请求,其实大同小异,只是增加了请求数据,这时候用到了urllib。示例如下:
但是有时会出现这种情况:即使post请求的数据是对的,但是服务器拒绝你的访问。这是为什么呢?问题出在请求中的头信息,服务器会检验请求头,来判断是否是来自浏览器的访问,这也是反爬虫的常用手段。
2.?请求头headers处理
将上面的例子改写一下,加上请求头信息,设置一下请求头中的user-agent域和referer域信息。
也可以这样写,使用add_header来添加请求头信息,修改如下:
对有些header要特别留意,服务器会针对这些header做检查,例如:
user-agent:有些服务器或proxy会通过该值来判断是否是浏览器发出的请求。
content-type:在使用rest接口时,服务器会检查该值,用来确定http body中的内容该怎样解析。在使用服务器提供的restful或soap服务时,content-type设置错误会导致服务器拒绝服务。常见的取值有:application/xml(在xml rpc,如restful/soap调用时使用)、application/json(在json rpc调用时使用)、application/x-www-form-urlencoded(浏览器提交web表单时使用)。
referer:服务器有时候会检查防盗链。
3.?cookie处理
urllib2对cookie的处理也是自动的,使用cookiejar函数进行cookie的管理。如果需要得到某个cookie项的值,可以这么做:

但是有时候会遇到这种情况,我们不想让urllib2自动处理,我们想自己添加cookie的内容,可以通过设置请求头中的cookie域来做:
4.?timeout设置超时
在python2.6之前的版本,urllib2的api并没有暴露timeout的设置,要设置timeout值,只能更改socket的全局timeout值。示例如下:
在python2.6及新的版本中,urlopen函数提供了对timeout的设置,示例如下:
5.?获取http响应码
对于200 ok来说,只要使用urlopen返回的response对象的getcode()方法就可以得到http的返回码。但对其他返回码来说,urlopen会抛出异常。这时候,就要检查异常对象的code属性了,示例如下:
6.?重定向
urllib2默认情况下会针对http 3xx返回码自动进行重定向动作。要检测是否发生了重定向动作,只要检查一下response的url和request的url是否一致就可以了,示例如下:
如果不想自动重定向,可以自定义httpredirecthandler类,示例如下:
7.?proxy的设置
在做爬虫开发中,必不可少地会用到代理。urllib2默认会使用环境变量http_proxy来设置http proxy。但是我们一般不采用这种方式,而是使用proxyhandler在程序中动态设置代理,示例代码如下:
这里要注意的一个细节,使用urllib2.install_opener()会设置urllib2的全局opener,之后所有的http访问都会使用这个代理。这样使用会很方便,但不能做更细粒度的控制,比如想在程序中使用两个不同的proxy设置,这种场景在爬虫中很常见。比较好的做法是不使用install_opener去更改全局的设置,而只是直接调用opener的open方法代替全局的urlopen方法,修改如下:
3.2.2 httplib/urllib实现
httplib模块是一个底层基础模块,可以看到建立http请求的每一步,但是实现的功能比较少,正常情况下比较少用到。在python爬虫开发中基本上用不到,所以在此只是进行一下知识普及。下面介绍一下常用的对象和函数:
创建httpconnection对象:class httplib.httpconnection(host[, port[, strict[, timeout[, source_address]]]])。
发送请求:httpconnection.request(method, url[, body[, headers]])。
获得响应:httpconnection.getresponse()。
读取响应信息:httpresponse.read([amt])。
获得指定头信息:httpresponse.getheader(name[, default])。
获得响应头(header, value)元组的列表:httpresponse.getheaders()。
获得底层socket文件描述符:httpresponse.fileno()。
获得头内容:httpresponse.msg。
获得头http版本:httpresponse.version。
获得返回状态码:httpresponse.status。
获得返回说明:httpresponse.reason。
接下来演示一下get请求和post请求的发送,首先是get请求的示例,如下所示:
post请求的示例如下:
3.2.3 更人性化的requests
python中requests实现http请求的方式,是本人极力推荐的,也是在python爬虫开发中最为常用的方式。requests实现http请求非常简单,操作更加人性化。
requests库是第三方模块,需要额外进行安装。requests是一个开源库,使用requests库需要先进行安装,一般有两种安装方式:
使用pip进行安装,安装命令为:pip install requests,不过可能不是最新版。
直接到github上下载requests的源代码,将源代码压缩包进行解压,然后进入解压后的文件夹,运行setup.py文件即可。
如何验证requests模块安装是否成功呢?在python的shell中输入import requests,如果不报错,则是安装成功。如图3-5所示。
1.?首先还是实现一个完整的请求与响应模型
以get请求为例,最简单的形式如下:
大家可以看到比urllib2实现方式的代码量少。接下来演示一下post请求,同样是非常简短,更加具有python风格。示例如下:
http中的其他请求方式也可以用requests来实现,示例如下:
接着讲解一下稍微复杂的方式,就是在网址后面紧跟着“?”,“?”后面还有参数。那么这样的get请求该如何发送呢?肯定有人会说,直接将完整的url带入即可,不过requests还提供了其他方式,示例如下:
通过打印结果,我们看到最终的url变成了:
2.?响应与编码
还是从代码入手,示例如下:
其中r.content返回的是字节形式,r.text返回的是文本形式,r.encoding返回的是根据http头猜测的网页编码格式。
输出结果中:“text-->”之后的内容在控制台看到的是乱码,“encoding-->”之后的内容是iso-8859-1(实际上的编码格式是utf-8),由于requests猜测编码错误,导致解析文本出现了乱码。requests提供了解决方案,可以自行设置编码格式,r.encoding='utf-8'设置成utf-8之后,“new text-->”的内容就不会出现乱码。但是这种手动的方式略显笨拙,下面提供一种更加简便的方式:chardet,这是一个非常优秀的字符串/文件编码检测模块。安装方式如下:
安装完成后,使用chardet.detect()返回字典,其中confidence是检测精确度,encoding是编码形式。示例如下:
直接将chardet探测到的编码,赋给r.encoding实现解码,r.text输出就不会有乱码了。
除了上面那种直接获取全部响应的方式,还有一种流模式,示例如下:
设置stream=true标志位,使响应以字节流方式进行读取,r.raw.read函数指定读取的字节数。
3.?请求头headers处理
requests对headers的处理和urllib2非常相似,在requests的get函数中添加headers参数即可。示例如下:
4.?响应码code和响应头headers处理
获取响应码是使用requests中的status_code字段,获取响应头使用requests中的headers字段。示例如下:
上述程序中,r.headers包含所有的响应头信息,可以通过get函数获取其中的某一个字段,也可以通过字典引用的方式获取字典值,但是不推荐,因为如果字段中没有这个字段,第二种方式会抛出异常,第一种方式会返回none。r.raise_for_status()是用来主动地产生一个异常,当响应码是4xx或5xx时,raise_for_status()函数会抛出异常,而响应码为200时,raise_for_status()函数返回none。
5.?cookie处理
如果响应中包含cookie的值,可以如下方式获取cookie字段的值,示例如下:
如果想自定义cookie值发送出去,可以使用以下方式,示例如下:
还有一种更加高级,且能自动处理cookie的方式,有时候我们不需要关心cookie值是多少,只是希望每次访问的时候,程序自动把cookie的值带上,像浏览器一样。requests提供了一个session的概念,在连续访问网页,处理登录跳转时特别方便,不需要关注具体细节。使用方法示例如下:
上面的这段程序,其实是正式做python开发中遇到的问题,如果没有第一步访问登录的页面,而是直接向登录链接发送post请求,系统会把你当做非法用户,因为访问登录界面时会分配一个cookie,需要将这个cookie在发送post请求时带上,这种使用session函数处理cookie的方式之后会很常用。
6.?重定向与历史信息
处理重定向只是需要设置一下allow_redirects字段即可,例如r=requests.get。将allow_redirects设置为true,则是允许重定向;设置为false,则是禁止重定向。如果是允许重定向,可以通过r.history字段查看历史信息,即访问成功之前的所有请求跳转信息。示例如下:
上面的示例代码显示的效果是访问github网址时,会将所有的http请求全部重定向为https。
7.?超时设置
超时选项是通过参数timeout来进行设置的,示例如下:
8.?代理设置
使用代理proxy,你可以为任意请求方法通过设置proxies参数来配置单个请求:
也可以通过环境变量http_proxy和https_proxy?来配置代理,但是在爬虫开发中不常用。你的代理需要使用http basic auth,可以使用·<code>http://user:password@host/</code>语法: