介绍:
网络爬虫的名字很有意思,英文名称web spider。真得很形象,蜘蛛结网为了获取食物,而我们的爬虫程序,也是为了获取网络上的资源。这篇blog是本人学习过程中的记录。学习过程中,使用的语言是python2.7;python2.7有两个模块,urllib和urllib2,这两个模块提供了很好的网络访问的功能。下面会更好的体会。值得一提的时,在python3中,将urllib和urllib2这两个模块合为一个urllib。感兴趣的可以看这里
urllib和urllib2是python中功能强大得网络工作库,它们让你的网络访问像文件访问一样(例如,文件访问我们要先open()一个文件,它的操作也是类似的,后面就会看到例子)。之所以能够这么方便,因为这些模块的内部很好的使用不同的网络协议来完成这些功能,(学过网络应该了解,访问一个网页这个简单的过程,其实涉及到很多的网络协议,像http,dns等等,而urllib和urllib2封装了这些协议,让我们不用和它们打交道,只需要调用这些模块的方法来完成我们需要的功能)。同时,这些模块也提供一些稍微更复杂一点的借口来处理一些情形,例如用户认证,cookies和代理等等。下面让我们开始来学习它们吧。
从简单语句中开始:
前面说过,使用两个模块,访问网页变得就会像访问文件一样方便。在一般情况下,urllib2访问会更好些(效率上更好,不过urllib还是需要使用,后面会介绍需要urllib做一些事情),所以下面我们来看看使用urllib2的最简单的例子。
import urllib2; response = urllib2.urlopen("http://www.zhonghuan.info"); html = response.read(); print html;
在终端下下输入命令行 python test.py > zhonghuan.html ,
打开文件后显示的是我的个人blog首页的html代码:
这是最简单的一个利用urllib2访问网页的例子,urllib2是根据URL中:前面的部分来判断是用什么协议访问的,例如,上面的例子我们用的时http,这里也可以换成ftp:,file:,等。。。我们可以不用去了解它内部是如何封装这些网络协议的。
urllib2中可以用一个镜像对象(Request Object)来表示我们http访问,它标示你想要访问的URL地址,我们来看一下下面的例子。
import urllib2 req = urllib2.Request('http://www.zhonghuan.info') response = urllib2.urlopen(req) the_page = response.read() print(the_page)
req变量就是一个Request对象。它确切的标示了你要访问的URL地址。(这里是http://www.zhonghuan.info);对于其它的形式的访问,例如ftp和file,形式也是类似的,具体可以看[这里][2];
其实,Request对象还能做两个额外的事情。
- 你可以发送数据给服务器。
- 你可以发送一些额外的信息(又叫元数据,描述数据的数据。一些语言里面的元类是生成类的类,如python就在这些语言中;所以元数据,顾名思义,描述数据的数据,那么这些被描述的数据是什么呢?上面中,还有request对象的一些信息,而这些描述被放在http头部发送出去了。有关http header,可以看这里);
传送数据给服务器
有时候,你需要发送数据给服务器,这个地址是URL表示的,通常呢,这个地址的指向是CGI(Common Gateway Interface)脚本或者是其它一些网络应用。(关于CGI脚本,可以看这里,简单的说就是处理上传数据的脚本程序)。在HTTP访问中,通常使用哪个POST方式将数据发送出去,就好像你填完了html中得表单,你需要把表单中得数据发送出去,通常这里使用post请求。当然,post使用还有其它的情况,不单单指的是表单这一种情况。
让我们先看下面的代码:
import urllib import urllib2 url = 'http://www.someserver.com/cgi-bin/register.cgi' values = {'name' : 'Michael Foord', 'location' : 'Northampton', 'language' : 'Python' } data = urllib.urlencode(values) #数据需要重新编码成合适的格式,这里使用的时urllib中得方法,因为urllib2中没有编码的方法 req = urllib2.Request(url, data) # #这里将需要上传的数据,传递给了equest对象,作为它的参数 response = urllib2.urlopen(req) the_page = response.read()
关于其它类型的数据上传,可以看这里
除了使用post方式上传数据外,还可以使用get方式上传数据,get上传和post上传明显的区别就是get上传的数据会在URL中得尾部显示出来。可以看下面的代码:
import urllib import urllib2 data = {} data['name'] = 'Somebody Here' data['location'] = 'Northampton' data['language'] = 'Python' url_values = urllib.urlencode(data) print url_values # 这里的顺序不一定 url = 'http://www.example.com/example.cgi' full_url = url + '?' + url_values data = urllib2.urlopen(full_url)
可以悄悄打印出来url_value的形式。
HTTP头—描述数据的数据
现在,我们来讨论一下HTTP头,来看看如何在你的HTTP的Request对象,增加一个HTTP头。
有一些网站,它比较智能,它不喜欢被程序访问(非人为的点击只会加重它服务器的负担)。或者有些网站更加智能点,对于不同的浏览器,会发送不同的网页数据。
可是呢,urllib2默认,会这样标示自己,Python-urllib/x.y
(其中,x和y分别是大小版本号,例如我现在使用的时Python-urllib/2.7
);而这些数据可能会让一些站点觉得迷惑,要是遇上了不喜欢被程序访问的网站,那么这样的访问可能会直接被忽视。所以,你可以构造一些身份,让站点不会拒绝你。看下面的例子。
import urllib import urllib2 url = 'http://www.someserver.com/cgi-bin/register.cgi' user_agent = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)' #user_agent用来标示你的浏览器的,向这里就是mozilla values = {'name' : 'Michael Foord', 'location' : 'Northampton', 'language' : 'Python' } headers = { 'User-Agent' : user_agent } data = urllib.urlencode(values) req = urllib2.Request(url, data, headers) response = urllib2.urlopen(req) the_page = response.read()
异常
异常时常有,要小心提防呐!想想一般文件操作的时候,会有什么异常呢?文件无法打开,什么权限不够啊,文件不存在啊等等异常。同样的,对于URL访问,也会遇到这些问题,(python一些内部异常,例如ValueError,TypeError等异常,也可能会发生)
URLError
先说说URLError,当没有网络连接,或者访问的服务器地址不存在的时候,在这种情况下,URLError会被抛出来,这个时候,URLError异常会有个“reason”属性,它是一个元组,包含error code(int型)和text error message(string型),看下面的代码
import urllib import urllib2 req = urllib2.Request('http://www.pretend_server.org') try: urllib2.urlopen(req) except urllib2.URLError as e: print e.reason
输出[Errno 8] nodename nor servname provided, or not known
;输出的内容就是reason,
HTTPError
每一个HTTP访问,会从服务器那儿获得一个“status code”(状态码),通常这些状态码告诉我们服务器无法满足一些访问(直白点说就是一些数据而已,只不过表示的是当前访问的状态,比如当前访问被拒绝了,status code可以告诉你,你哪些举动过分了,出格了,注意,咸猪手不可以有啊~~)。
不过呢,urllib2的默认处理器能够帮助你处理一些服务器的响应,比如说你当前访问的网址被服务器重定向了,就是说你的服务器给你一个新的URL访问了,处理器会帮你直接去访问新的URL。
但是默认的处理器毕竟功能有限,它不能帮助你解决所有问题,比如你访问的网站不存在了(对应404错误,我们有时候会看到这个错误),或者你的访问被禁止了(对应403错误,禁止的原因可能是因为你的权限不够啦等等),又或者是需要你验证啦(对应401)。具体的其它错误本文就不介绍啦,具体可以看这里
让我们看下面的程序,看一下当HTTPError的404错误,也就是页面不存在时候,它会输出点什么。
import urllib import urllib2 req = urllib2.Request('http://www.zhonghuan.info/no_way') try: urllib2.urlopen(req) except urllib2.HTTPError as e: print e.code; print e.read();
输出:
404
<!DOCTYPE html>
…
<title>Page not found · GitHub Pages</title>
…
处理异常
假设你想要捕捉HTTPError和URLError,有两种基本的方法,推荐第二种噢!
第一种:
from urllib2 import Request, urlopen, URLError, HTTPError req = Request(http://zhonghuan.info) try: response = urlopen(req) except HTTPError as e: print 'The server couldn\'t fulfill the request.' print 'Error code: ', e.code except URLError as e: print 'We failed to reach a server.' print 'Reason: ', e.reason else: # everything is fine
第一种方法,HTTPError一定要放在URLError前面,原因呢,和很多语言的异常处理机制一样,HTTPError是URLError的子类,如果发生了HTTPError,它可以被当做是URLError被捕捉。
第二种:
from urllib2 import Request, urlopen, URLError req = Request(someurl) try: response = urlopen(req) except URLError as e: if hasattr(e, 'reason'): print 'We failed to reach a server.' print 'Reason: ', e.reason elif hasattr(e, 'code'): print 'The server couldn\'t fulfill the request.' print 'Error code: ', e.code else: # everything is fine
info和geturl
这里介绍两个方法info()和geturl();
geturl():该方法会返回访问的页面的真实的URL,它的价值在于我们访问的网页可能会被重定向,所以导致访问的URL和我们输入的可能不一样。看下面的例子:
import urllib import urllib2 url = 'http://weibo.com/u/2103243911'; req = urllib2.Request(url); response = urllib2.urlopen(req) print "URL:",url; print "After redirection:",response.geturl();
以我的微博个人主页为例,其实真实访问被重定向了,真实的网址,从输出中可以看出:
URL: http://weibo.com/u/2103243911
After redirection: http://passport.weibo.com/visitor/visitor?a=enter&url=http%3A%2F%2Fweibo.com%2Fu%2F2103243911&_rand=1409761358.1794
info():可以得到描述页面的信息,返回的是一个httplib.HTTPMessage
实例,打印出来很像字典。看下面的代码:
import urllib import urllib2 url = 'http://zhonghuan.info'; req = urllib2.Request(url); response = urllib2.urlopen(req); print response.info(); print response.info().__class__;
输出:
Server: GitHub.com Content-Type: text/html; charset=utf-8 Last-Modified: Tue, 02 Sep 2014 17:01:39 GMT Expires: Wed, 03 Sep 2014 15:23:02 GMT Cache-Control: max-age=600 Content-Length: 4784 Accept-Ranges: bytes Date: Wed, 03 Sep 2014 16:38:29 GMT Via: 1.1 varnish Age: 5127 Connection: close X-Served-By: cache-lax1433-LAX X-Cache: HIT X-Cache-Hits: 1 X-Timer: S1409762309.465760,VS0,VE0 Vary: Accept-Encoding Class: httplib.HTTPMessage
Opener和Handler
这里介绍Opener和Handler。
什么是Opener呢?其实上面的例子我们一直在用Opener了,就是urlopen。这个是默认的opener,网络访问情况很多,你可以创建比较合适的opener,
什么是Handler呢?其实Opener会调用Handler来处理访问中得琐事,所以Handler很重要,对于特定的协议(例如FTP,HTTP),它知道如何如何处理访问,例如它会帮你处理重定向问题。
在访问的时候,你可能对于Opener有一些要求,例如,你希望得到的Opener能够处理cookie,或者你不希望Opener帮助你处理重定向。
我们如何生成需要得Opener呢?(这里插一下,个人觉得这里的Opener生成方式,和设计模式中得生成器欧式,又叫建造者模式,英文名称Builder Pattern;有些相似,不过不完全一样,但总觉得,在看下去之前,先了解一下这个模式会有好处,没有接触过的朋友可以看这篇Builder pattern);
要创建一个 opener,可以实例化一个OpenerDirector,
然后调用.add_handler(some_handler_instance)。
不过,可以使用build_opener,这是一个更加方便的函数,用来创建opener对象,他只需要一次函数调用。build_opener默认添加几个处理器,但提供快捷的方法来添加或更新默认处理器。
其他的处理器handlers你或许会希望处理代理,验证,和其他常用但有点特殊的情况。
刚刚提到handler会帮我们处理重定向,但是,如果我们不想要重定向呢,该怎么办,自定义一个handler。看下面的代码:
mport urllib import urllib2 class RedirectHandler(urllib2.HTTPRedirectHandler):# 这个RedirectHandler继承了HTTPRedirectHandler,不过,它覆盖了父类的方法,让它什么都不做,失去了重定向的功能。 def http_error_301(self, req, fp, code, msg, headers): pass def http_error_302(self, req, fp, code, msg, headers): pass webo = "http://weibo.com/u/2103243911"; #访问的是我的微博页面,因为正常情况下,访问时会发生重定向 opener = urllib2.build_opener(RedirectHandler) #这里,我们自定义了一个opener,添加了一个重定向时处理的自定义handler response = opener.open(webo);# response = urllib2.urlopen(webo); print response.geturl(); urllib2.install_opener(opener); #安装自定义的opener,以后调用urllib2的时候,返回的就是这个opener。
输出结果是:
urllib2.HTTPError: HTTP Error 302: Moved Temporarily
之所以发生http error 302,是因为本来访问我的微博个人主页的时候,它应该发生重定向的,可是我们自己的重定向Handler什么都不做,结果就是发生异常了。
可以看看下面的urllib2关于创建自定义Opener的类图
Basic Authentication
如果一个网站,他提供注册登入这些功能,那么一般它有用户名/密码,如果你访问的页面,系统要求你提供用户名/密码,这个过程叫做Authentication,实在服务器那端所做的操作。它给一些页面提供了安全保护。
一个基本的Authentication(验证)过程是这样的:
- 客户端提出请求访问某些页面。
- 服务器返回一个错误,要求进行身份验证。
- 客户端把用户名/密码(一般这样)编码后发给服务器。
- 服务器检查这对用户名/密码是否正确,然后返回用户请求的页面或者是一些错误。
上面的过程,还有可能是其它形式,这里只是举个比较普遍的。
通常服务器返回的是401错误,表明访问的网页未授权,同时,返回的response的header内有形如
WWW-Authenticate: SCHEME realm="REALM".
的内容,例如,你想要访问cPanel的管理应用程序,你会收到这样的header:WWW-Authenticate: Basic realm="cPanel"
(cPanel 是一套在网页寄存业中最享负盛名的商业软件,其基于 Linux 和 BSD 系统及以 PHP 开发且性质为闭源软件;cPanel 主要是面向客户权级的控制系统)
当我们访问页面的时候,opener会调用handler来处理各种情况,而处理Authentication的handler是urllib2.HTTPBasicAuthHandler,同时需要一个用户密码管理器urllib2.HTTPPasswordMgr。
不幸的时,HTTPPasswordMgr有一个小问题,就是在获取网页前,你需要知道它的realm。幸运的是,它有一个表兄弟HTTPPasswordMgrWithDefaultRealm,这个表兄弟可以事先不知道realm,在realm参数位置上,可以传一个None进去,它更友好的被使用。
下面参考下面的代码:
import urllib2 url = 'http://www.weibo.com'#分别对应域名,账号,密码 username = 'zhonghuan' password = 'forget_it' passman = urllib2.HTTPPasswordMgrWithDefaultRealm() #创建密码管理器 passman.add_password(None, url, username, password)# 参数形式(realm,URL,UserName,Password) authhandler = urllib2.HTTPBasicAuthHandler(passman)#创建Authentication的handler opener = urllib2.build_opener(authhandler) urllib2.install_opener(opener) #和上面介绍的一样,install_opener后,每次调用urllib2的urlopen,返回的就是这个opener pagehandle = urllib2.urlopen(url)
代理
有时候,我们本机不能直接访问,需要代理服务器去访问。urllib2对这个设置代理支持的还不错,可以直接实例化ProxyHandler,它的参数是一个map,key值是代理的访问协议名称,value值是代理的地址。看下面的代码实现。
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)
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)
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
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')