作者:范传辉
如需转载请联系华章科技
接下来从网络爬虫的观点、用途与代价和构造等三个方面,让大家对网络爬虫有一个基本的理解。

1. 网络爬虫及其运用
随着网络的迅速发展,万维网成为大量信息的载体,如何有效地提取并利用这些信息成为一个巨大的寻衅,网络爬虫应运而生。网络爬虫(又被称为网页蜘蛛、网络机器人),是一种按照一定的规则,自动地抓取万维网信息的程序或者脚本。下面通过图3-1展示一下网络爬虫在互联网中起到的浸染:
▲图3-1 网络爬虫
网络爬虫按照系统构造和实现技能,大致可以分为以下几种类型:通用网络爬虫、聚焦网络爬虫、增量式网络爬虫、深层网络爬虫。实际的网络爬虫系统常日是几种爬虫技能相结合实现的。
搜索引擎(Search Engine),例如传统的通用搜索引擎baidu、Yahoo和Google等,是一种大型繁芜的网络爬虫,属于通用性网络爬虫的范畴。但是通用性搜索引擎存在着一定的局限性:
不同领域、不同背景的用户每每具有不同的检索目的和需求,通用搜索引擎所返回的结果包含大量用户不关心的网页。通用搜索引擎的目标是尽可能大的网络覆盖率,有限的搜索引擎做事器资源与无限的网络数据资源之间的抵牾将进一步加深。万维网数据形式的丰富和网络技能的不断发展,图片、数据库、音频、视频多媒体平分歧数据大量涌现,通用搜索引擎每每对这些信息含量密集且具有一定构造的数据无能为力,不能很好地创造和获取。通用搜索引擎大多供应基于关键字的检索,难以支持根据语义信息提出的查询。为理解决上述问题,定向抓取干系网页资源的聚焦爬虫应运而生。
聚焦爬虫是一个自动下载网页的程序,它根据既定的抓取目标,有选择地访问万维网上的网页与干系的链接,获取所须要的信息。与通用爬虫不同,聚焦爬虫并不追求大的覆盖,而将目标定为抓取与某一特定主题内容干系的网页,为面向主题的用户查询准备数据资源。
说完了聚焦爬虫,接下来再说一下增量式网络爬虫。增量式网络爬虫是指对已下载网页采纳增量式更新和只爬行新产生的或者已经发生变革网页的爬虫,它能够在一定程度上担保所爬行的页面是尽可能新的页面。
和周期性爬行和刷新页面的网络爬虫比较,增量式爬虫只会在须要的时候爬行新产生或发生更新的页面,并不重新下载没有发生变革的页面,可有效减少数据下载量,及时更新已爬行的网页,减小韶光和空间上的耗费,但是增加了爬行算法的繁芜度和实现难度。
例如:想获取赶集网的招聘信息,以前爬取过的数据没有必要重复爬取,只须要获取更新的招聘数据,这时候就要用到增量式爬虫。
末了说一下深层网络爬虫。Web页面按存在办法可以分为表层网页和深层网页。表层网页是指传统搜索引擎可以索引的页面,以超链接可以到达的静态网页为主构成的Web页面。深层网络是那些大部分内容不能通过静态链接获取的、隐蔽在搜索表单后的,只有用户提交一些关键词才能得到的Web页面。
例如用户登录或者注册才能访问的页面。可以想象这样一个场景:爬取贴吧或者论坛中的数据,必须在用户登录后,有权限的情形下才能获取完全的数据。
2. 网络爬虫构造
下面用一个通用的网络爬虫构造来解释网络爬虫的基本事情流程,如图3-4所示。
▲图3-4 网络爬虫构造
网络爬虫的基本事情流程如下:
首先选取一部分精心挑选的种子URL。将这些URL放入待抓取URL行列步队。从待抓取URL行列步队中读取待抓取行列步队的URL,解析DNS,并且得到主机的IP,并将URL对应的网页下载下来,存储进已下载网页库中。此外,将这些URL放进已抓取URL行列步队。剖析已抓取URL行列步队中的URL,从已下载的网页数据等分析出其他URL,并和已抓取的URL进行比较去重,末了将去重过的URL放入待抓取URL行列步队,从而进入下一个循环。02 HTTP要求的Python实现通过上面的网络爬虫构造,我们可以看到读取URL、下载网页是每一个爬虫必备而且关键的功能,这就须要和HTTP要求打交道。接下来讲解Python中实现HTTP要求的三种办法:urllib2/urllib、httplib/urllib以及Requests。
1. urllib2/urllib实现
urllib2和urllib是Python中的两个内置模块,要实现HTTP功能,实现办法因此urllib2为主,urllib为辅。
1.1 首先实现一个完全的要求与相应模型
urllib2供应一个根本函数urlopen,通过向指定的URL发出要求来获取数据。最大略的形式是:
import urllib2response=urllib2.urlopen('http://www.zhihu.com')html=response.read()print html
实在可以将上面对http://www.zhihu.com的要求相应分为两步,一步是要求,一步是相应,形式如下:
import urllib2# 要求request=urllib2.Request('http://www.zhihu.com')# 相应response = urllib2.urlopen(request)html=response.read()print html
上面这两种形式都是GET要求,接下来演示一下POST要求,实在大同小异,只是增加了要求数据,这时候用到了urllib。示例如下:
import urllibimport urllib2url = 'http://www.xxxxxx.com/login'postdata = {'username' : 'qiye', 'password' : 'qiye_pass'}# info 须要被编码为urllib2能理解的格式,这里用到的是urllibdata = urllib.urlencode(postdata)req = urllib2.Request(url, data)response = urllib2.urlopen(req)html = response.read()
但是有时会涌现这种情形:纵然POST要求的数据是对的,但是做事器谢绝你的访问。这是为什么呢?问题出在要求中的头信息,做事器会考验要求头,来判断是否是来自浏览器的访问,这也是反爬虫的常用手段。
1.2 要求头headers处理
将上面的例子改写一下,加上要求头信息,设置一下要求头中的User-Agent域和Referer域信息。
import urllibimport urllib2url = 'http://www.xxxxxx.com/login'user_agent = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)'referer='http://www.xxxxxx.com/'postdata = {'username' : 'qiye', 'password' : 'qiye_pass'}# 将user_agent,referer写入头信息headers={'User-Agent':user_agent,'Referer':referer}data = urllib.urlencode(postdata)req = urllib2.Request(url, data,headers)response = urllib2.urlopen(req)html = response.read()
也可以这样写,利用add_header来添加要求头信息,修正如下:
import urllibimport urllib2url = 'http://www.xxxxxx.com/login'user_agent = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)'referer='http://www.xxxxxx.com/'postdata = {'username' : 'qiye', 'password' : 'qiye_pass'}data = urllib.urlencode(postdata)req = urllib2.Request(url)# 将user_agent,referer写入头信息req.add_header('User-Agent',user_agent)req.add_header('Referer',referer)req.add_data(data)response = urllib2.urlopen(req)html = response.read()
对有些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:做事器有时候会检讨防盗链。1.3 Cookie处理
urllib2对Cookie的处理也是自动的,利用CookieJar函数进行Cookie的管理。如果须要得到某个Cookie项的值,可以这么做:
import urllib2import cookielibcookie = cookielib.CookieJar()opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookie))response = opener.open('http://www.zhihu.com')for item in cookie: print item.name+':'+item.value
但是有时候会碰着这种情形,我们不想让urllib2自动处理,我们想自己添加Cookie的内容,可以通过设置要求头中的Cookie域来做:
import urllib2opener = urllib2.build_opener()opener.addheaders.append( ( 'Cookie', 'email=' + \公众xxxxxxx@163.com\公众 ) )req = urllib2.Request( \"大众http://www.zhihu.com/\"大众 )response = opener.open(req)print response.headersretdata = response.read()
1.4 Timeout设置超时
在Python2.6之前的版本,urllib2的API并没有暴露Timeout的设置,要设置Timeout值,只能变动Socket的全局Timeout值。示例如下:
import urllib2import socketsocket.setdefaulttimeout(10) # 10 秒钟后超时urllib2.socket.setdefaulttimeout(10) # 另一种办法
在Python2.6及新的版本中,urlopen函数供应了对Timeout的设置,示例如下:
import urllib2request=urllib2.Request('http://www.zhihu.com')response = urllib2.urlopen(request,timeout=2)html=response.read()print html
1.5 获取HTTP相应码
对付200 OK来说,只要利用urlopen返回的response工具的getcode()方法就可以得到HTTP的返回码。但对其他返回码来说,urlopen会抛出非常。这时候,就要检讨非常工具的code属性了,示例如下:
import urllib2try: response = urllib2.urlopen('http://www.google.com') print responseexcept urllib2.HTTPError as e: if hasattr(e, 'code'): print 'Error code:',e.code
1.6 重定向
urllib2默认情形下会针对HTTP 3XX返回码自动进行重定向动作。要检测是否发生了重定向动作,只要检讨一下Response的URL和Request的URL是否同等就可以了,示例如下:
import urllib2response = urllib2.urlopen('http://www.zhihu.cn')isRedirected = response.geturl() == 'http://www.zhihu.cn'
如果不想自动重定向,可以自定义HTTPRedirectHandler类,示例如下:
import urllib2class RedirectHandler(urllib2.HTTPRedirectHandler): def http_error_301(self, req, fp, code, msg, headers): pass def http_error_302(self, req, fp, code, msg, headers): result = urllib2.HTTPRedirectHandler.http_error_301(self, req, fp, code, msg, headers) result.status = code result.newurl = result.geturl() return resultopener = urllib2.build_opener(RedirectHandler)opener.open('http://www.zhihu.cn')
1.7 Proxy的设置
在做爬虫开拓中,必不可少地会用到代理。urllib2默认会利用环境变量http_proxy来设置HTTP Proxy。但是我们一样平常不采取这种办法,而是利用ProxyHandler在程序中动态设置代理,示例代码如下:
import urllib2proxy = urllib2.ProxyHandler({'http': '127.0.0.1:8087'})opener = urllib2.build_opener([proxy,])urllib2.install_opener(opener)response = urllib2.urlopen('http://www.zhihu.com/')print response.read()
这里要把稳的一个细节,利用urllib2.install_opener()会设置urllib2的全局opener,之后所有的HTTP访问都会利用这个代理。这样利用会很方便,但不能做更细粒度的掌握,比如想在程序中利用两个不同的Proxy设置,这种场景在爬虫中很常见。比较好的做法是不该用install_opener去变动全局的设置,而只是直接调用opener的open方法代替全局的urlopen方法,修正如下:
import urllib2proxy = urllib2.ProxyHandler({'http': '127.0.0.1:8087'})opener = urllib2.build_opener(proxy,)response = opener.open(\"大众http://www.zhihu.com/\公众)print response.read()
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要求的示例,如下所示:
import httplibconn =Nonetry: conn = httplib.HTTPConnection(\公众www.zhihu.com\公众) conn.request(\"大众GET\"大众, \"大众/\"大众) response = conn.getresponse() print response.status, response.reason print '-' 40 headers = response.getheaders() for h in headers: print h print '-' 40 print response.msgexcept Exception,e: print efinally: if conn: conn.close()
POST要求的示例如下:
import httplib, urllibconn = Nonetry: params = urllib.urlencode({'name': 'qiye', 'age': 22}) headers = {\公众Content-type\公众: \"大众application/x-www-form-urlencoded\公众 , \"大众Accept\"大众: \"大众text/plain\"大众} conn = httplib.HTTPConnection(\"大众www.zhihu.com\公众, 80, timeout=3) conn.request(\"大众POST\公众, \"大众/login\公众, params, headers) response = conn.getresponse() print response.getheaders() # 获取头信息 print response.status print response.read()except Exception, e: print e finally: if conn: conn.close()
3. 更人性化的Requests
Python中Requests实现HTTP要求的办法,是本人极力推举的,也是在Python爬虫开拓中最为常用的办法。Requests实现HTTP要求非常大略,操作更加人性化。
Requests库是第三方模块,须要额外进行安装。Requests是一个开源库,源码位于:
GitHub: https://github.com/kennethreitz/requests
希望大家多多支持作者。
利用Requests库须要前辈行安装,一样平常有两种安装办法:
利用pip进行安装,安装命令为:pip install requests,不过可能不是最新版。直接到GitHub高下载Requests的源代码,下载链接为:https://github.com/kennethreitz/requests/releases将源代码压缩包进行解压,然后进入解压后的文件夹,运行setup.py文件即可。如何验证Requests模块安装是否成功呢?在Python的shell中输入import requests,如果不报错,则是安装成功。如图3-5所示。
▲图3-5 验证Requests安装
3.1 首先还是实现一个完全的要求与相应模型
以GET要求为例,最大略的形式如下:
import requestsr = requests.get('http://www.baidu.com')print r.content
大家可以看到比urllib2实现办法的代码量少。接下来演示一下POST要求,同样是非常简短,更加具有Python风格。示例如下:
import requestspostdata={'key':'value'}r = requests.post('http://www.xxxxxx.com/login',data=postdata)print r.content
HTTP中的其他要求办法也可以用Requests来实现,示例如下:
r = requests.put('http://www.xxxxxx.com/put', data = {'key':'value'})r = requests.delete('http://www.xxxxxx.com/delete')r = requests.head('http://www.xxxxxx.com/get')r = requests.options('http://www.xxxxxx.com/get')
接着讲解一下轻微繁芜的办法,大家肯定见过类似这样的URL:
http://zzk.cnblogs.com/s/blogpost?Keywords=blog:qiyeboy&pageindex=1
便是在网址后面紧随着“?”,“?”后面还有参数。那么这样的GET要求该如何发送呢?肯定有人会说,直接将完全的URL带入即可,不过Requests还供应了其他办法,示例如下:
import requests payload = {'Keywords': 'blog:qiyeboy','pageindex':1}r = requests.get('http://zzk.cnblogs.com/s/blogpost', params=payload)print r.url
通过打印结果,我们看到终极的URL变成了:
http://zzk.cnblogs.com/s/blogpost?Keywords=blog:qiyeboy&pageindex=1
3.2 相应与编码
还是从代码入手,示例如下:
import requestsr = requests.get('http://www.baidu.com')print 'content-->'+r.contentprint 'text-->'+r.textprint 'encoding-->'+r.encodingr.encoding='utf-8'print 'new text-->'+r.text
个中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,这是一个非常精良的字符串/文件编码检测模块。安装办法如下:
pip install chardet
安装完成后,利用chardet.detect()返回字典,个中confidence是检测精确度,encoding是编码形式。示例如下:
import requestsr = requests.get('http://www.baidu.com')print chardet.detect(r.content)r.encoding = chardet.detect(r.content)['encoding']print r.text
直接将chardet探测到的编码,赋给r.encoding实现解码,r.text输出就不会有乱码了。
除了上面那种直接获取全部相应的办法,还有一种流模式,示例如下:
import requestsr = requests.get('http://www.baidu.com',stream=True)print r.raw.read(10)
设置stream=True标志位,使相应以字节流办法进行读取,r.raw.read函数指定读取的字节数。
3.3 要求头headers处理
Requests对headers的处理和urllib2非常相似,在Requests的get函数中添加headers参数即可。示例如下:
import requestsuser_agent = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)'headers={'User-Agent':user_agent}r = requests.get('http://www.baidu.com',headers=headers)print r.content
3.4 相应码code和相应头headers处理
获取相应码是利用Requests中的status_code字段,获取相应头利用Requests中的headers字段。示例如下:
import requestsr = requests.get('http://www.baidu.com')if r.status_code == requests.codes.ok: print r.status_code# 相应码 print r.headers# 相应头 print r.headers.get('content-type')# 推举利用这种获取办法,获取个中的某个字段 print r.headers['content-type']# 不推举利用这种获取办法else: r.raise_for_status()
上述程序中,r.headers包含所有的相应头信息,可以通过get函数获取个中的某一个字段,也可以通过字典引用的办法获取字典值,但是不推举,由于如果字段中没有这个字段,第二种办法会抛出非常,第一种办法会返回None。
r.raise_for_status()是用来主动地产生一个非常,当相应码是4XX或5XX时,raise_for_status()函数会抛出非常,而相应码为200时,raise_for_status()函数返回None。
3.5 Cookie处理
如果相应中包含Cookie的值,可以如下办法获取Cookie字段的值,示例如下:
import requestsuser_agent = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)'headers={'User-Agent':user_agent}r = requests.get('http://www.baidu.com',headers=headers)# 遍历出所有的cookie字段的值for cookie in r.cookies.keys(): print cookie+':'+r.cookies.get(cookie)
如果想自定义Cookie值发送出去,可以利用以下办法,示例如下:
import requestsuser_agent = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)'headers={'User-Agent':user_agent}cookies = dict(name='qiye',age='10')r = requests.get('http://www.baidu.com',headers=headers,cookies=cookies)print r.text
还有一种更加高等,且能自动处理Cookie的办法,有时候我们不须要关心Cookie值是多少,只是希望每次访问的时候,程序自动把Cookie的值带上,像浏览器一样。Requests供应了一个session的观点,在连续访问网页,处理登录跳转时特殊方便,不须要关注详细细节。利用方法示例如下:
import RequestsoginUrl = 'http://www.xxxxxxx.com/login's = requests.Session()#首先访问登录界面,作为游客,做事器会先分配一个cookier = s.get(loginUrl,allow_redirects=True)datas={'name':'qiye','passwd':'qiye'}#向登录链接发送post要求,验证成功,游客权限转为会员权限r = s.post(loginUrl, data=datas,allow_redirects= True)print r.text
上面的这段程序,实在是正式做Python开拓中碰着的问题,如果没有第一步访问登录的页面,而是直接向登录链接发送Post要求,系统会把你当做造孽用户,由于访问登录界面时会分配一个Cookie,须要将这个Cookie在发送Post要求时带上,这种利用Session函数处理Cookie的办法之后会很常用。
3.6 重定向与历史信息
处理重定向只是须要设置一下allow_redirects字段即可,例如:
r=requests.get('http://www.baidu.com',allow_redirects=True)
将allow_redirects设置为True,则是许可重定向;设置为False,则是禁止重定向。如果是许可重定向,可以通过r.history字段查看历史信息,即访问成功之前的所有要求跳转信息。示例如下:
import requestsr = requests.get('http://github.com')print r.urlprint r.status_codeprint r.history
打印结果如下:
https://github.com/200(<Response [301]>,)
上面的示例代码显示的效果是访问GitHub网址时,会将所有的HTTP要求全部重定向为HTTPS。
3.7 超时设置
超时选项是通过参数timeout来进行设置的,示例如下:
requests.get('http://github.com', timeout=2)
3.8 代理设置
利用代理Proxy,你可以为任意要求方法通过设置proxies参数来配置单个要求:
import requestsproxies = { \"大众http\"大众: \公众http://0.10.1.10:3128\"大众, \"大众https\"大众: \"大众http://10.10.1.10:1080\"大众,}requests.get(\"大众http://example.org\公众, proxies=proxies)
也可以通过环境变量HTTP_PROXY和HTTPS_PROXY?来配置代理,但是在爬虫开拓中不常用。你的代理须要利用HTTP Basic Auth,可以利用http://user:password@host/语法:
proxies = { \公众http\公众: \公众http://user:pass@10.10.1.10:3128/\"大众,}03 小结
本文紧张讲解了网络爬虫的构造和运用,以及Python实现HTTP要求的几种方法。希望大家对本文中的网络爬虫事情流程和Requests实现HTTP要求的办法重点接管消化。
关于作者:范传辉,资深网虫,Python开拓者,参与开拓了多项网络运用,在实际开拓中积累了丰富的实战履历,并长于总结,贡献了多篇技能文章广受好评。研究兴趣是网络安全、爬虫技能、数据剖析、驱动开拓等技能。
本文摘编自《Python爬虫开拓与项目实战》,经出版方授权发布。
延伸阅读《Python爬虫开拓与项目实战》
推举语:零根本学习爬虫技能,从Python和Web前端根本开始讲起,由浅入深,包含大量案例,实用性强。