从今天起开始进入爬虫的世界。网络爬虫这种东西都了解能干嘛,我第一次知道时感觉特别棒,希望自己能写一个,不管功能强大与否。Python语言就是写爬虫的得力工具。我预计通过两篇博文来介绍一下Python基础库:urllib和requests(后者为重)。然后爬取豆瓣Top250电影信息。
在Python2中实现发送请求的库有urllib和urllib2,到了Python3这两个库统一为了urllib。urllib包含如下4个模块:
- request:它是最基本的HTTP请求模块,可以用来模拟发送请求。给予库方法URL和参数就可以顺利的发送请求,跟人工操作一样。(重点)
- **error:**异常处理模块。使用它我们可以捕获爬取过程中产生的异常,从而防止程序的意外终止。
- **parse:**工具模块。提供URL处理方法,譬如拆分,解析、合并等等。
- **robotparser:**它主要用来识别robot.txt文件,判断哪些网站我们可以爬取,哪些又不可以。但是我们都是硬上的,即使设置了不可爬取也爬,任性。
urllib.request
urlopen()
**作用:**模拟浏览器发送一个请求,实现对目标url的访问。
语法:urllib.request.urlopen(url, data=None, [timeout, ]*, cafile=None, capath=None, cadefault=False, context=None)
参数:
- **url参数:**目标资源在网络中的位置。可以是一个表示URL的字符串(如:http://www.python.org/),也可以是一个urllib.request对象。
- data参数:data用来指明发往服务器请求中的额外的信息。HTTP是python中实现的众多网络通信http、https、ftp等协议中,唯一一个使用data参数的,也就是说只有打开的是http网址的时候,自定义data参数才会有作用。另外,官方API手册介绍指出:
- data必须是一个字节数据对象(Python的bytes object);
- data必须符合标准the standard application/x-www-form-urlencoded format,怎么得到这种标准结构的data呢?使用urllib.parse.urlencode()将自定义的data转换成标准格式,而这个函数所能接收的参数类型是pyhon中的mapping object(键/值对,如dict) or a sequence of two-element tuples(元素是tuple的列表);
- data也可以是一个可迭代的对象,这种情况下就需要配置response对象中的Conten-length,指明data的大小;
- data默认是None,此时以GET方式发送请求;当用户给出data参数的时候,改为POST方式发送请求。
- **timeout参数:**设置超时时间,单位为秒。如果请求超出了设置的数据还没得到响应就好抛出异常。如果不指定该参数则使用全局默认时间。
- **cafile、capath、cadefault 参数:**用于实现可信任的CA证书的HTTP请求。(基本上很少用)
- **context参数:**实现SSL加密传输。(基本上很少用)
小试牛刀
现在我来爬取Python官网,把它的首页爬取下来。import urllib.request
response = urllib.request.urlopen("https://python.org")
print(response.read().decode("UTF-8"))#使用utf-8对网页解码,如果出现错误,再加上"ignore"参数
部分结果:
urlopen返回值是<class ‘http.client.HTTPResponse’>
。它是HTTPResponse对象,主要包含read()、readinto()、getheader(name)、getheaders()、fileno()等方法,以及msg、version、status、reason、debuglevel、closed等属性。通过read()得到返回的内容,访问status属性得到返回码,200代表成功,404代表网页未找到。常用返回码可以查看:http://www.runoob.com/http/http-status-codes.html。
示例:
import urllib.request
response = urllib.request.urlopen("https://python.org")
print("返回码:",response.status)
print("获取所有请求头信息:",response.getheaders())
print("获取指定请求头信息:",response.getheader("Server"))
timeout参数示例
设置一个特别短的时间,制造异常:import urllib.request
response = urllib.request.urlopen("https://python.org",timeout=0.1)#设置0.1s,这一定会超时
print(response.status)
有时候由于网络的不稳定等原因会导致请求超时,如果不设置对应的处理方法程序就终止了。我们可以使用try except语句捕获超时异常,从而跳过某一网站的爬取或重复爬取。
import urllib.request
from urllib.error import URLError
import socket
try:
response = urllib.request.urlopen("https://python.org",timeout=0.1)
except URLError as e:
if isinstance(e.reason,socket.timeout):
print("请求超时,做其他处理")
传递Request对象
基础用法
事实上,上面介绍的那个参数传递方法是不会使用的,除非你爬取的网站压根就不反爬。再不济我们都要给请求加上Headers等信息,这就需要Request类来构建。 首先将 小试牛刀代码改为:import urllib.request
req = urllib.request.Request("https://python.org")#构建一个Request对象
response = urllib.request.urlopen(req)#传递Request对象
print(response.read().decode("UTF-8"))
还是同样的方法,只是不同的配方,传递Request对象了。
urllib.request.Request()详情如下:
语法:urllib.request.Request(url, data=None, headers={},origin_req_host=None, unverifiable=False,method=None)
参数:
- **url:**必选参数。用于请求的URL。
- **data:**传递的数据。如果要传必须传递bytes(字节流)类型的。如果它是字典类型我们可以使用urllib.parse模块里的urlencode()编码。
- **headers:**设置请求头信息,这个参数我们会经常接触。如果不在这里设置我们也可以在后续使用add_header()方法添加。在请求头信息中我们最常设置的是User-Agent,默认的User-Agent是Python-urllib,这相当于自报家门:嘿!我是爬虫。我们可以修改它来伪装浏览器。比如我们要伪装火狐,我们可以设置为:
Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36 QIHU 360SE
- **origin_req_host:**设置请求方的host名称或IP地址。
- **unverifiable:**表示该请求是否是无法验证的,默认是False。
- **method:**表示这次请求使用的方法,比如GET、POST和PUT等。
现在我们伪装成浏览器发送POST请求,并且传递data:
import urllib.request
import urllib.parse
url = "https://httpbin.org/post" #https://httpbin.org可以供我们测试http请求
headers = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36 QIHU 360SE'}#伪装成浏览器
dataDic = {'name':'Tom','age':20}
data = bytes(urllib.parse.urlencode(dataDic),'UTF-8')
req = urllib.request.Request(url,data=data,headers=headers,method='POST')#构建一个Request对象
response = urllib.request.urlopen(req)#传递Request对象
print(response.read().decode("UTF-8"))
高级用法
- 如果爬虫使用某一IP爬取次数过多有的网站就认为是爬虫在爬取数据,狠点的直接将IP封禁了,那就凉了,对应的应对手段是使用代理IP爬取。
- 有的网站我们是需要登录才能爬取的,我们需要爬虫能帮我们自动登录,借助HTTPBasicAuthHandler就可以完成。
- 登录成功后一般会使用Cookie保存用户登录信息,所以我们需要保存cookie。
以上三种情况的处理我们都需要使用Handler来处理,Python提供了许多的Handler来帮助我们完成任务:
- **HTTPDefaultErrorHandler:**用于处理HTTP响应错误,错误都会抛出HTTPError类型的异常。
- **HTTPRedirectHandler:**用于处理重定向。
- **HTTPCookieProcessor:**用于处理Cookies。
- **ProxyHandler:**用于设置代理,默认代理为空。
- **HTTPPasswordMgr:**用于管理密码,它维护了用户名和密码的表。
- **HTTPBasicAuthHandler:**用于管理认证,如果一个链接打开时需要认证,那么可以用来解决认证问题。
还要很多的Handler,这就需要大家查看官方文档了。
现在有了Handler,但是我们如何使用呢?接下来再介绍一个重要的类-----OperDirector,或者Opener。Opener也可以调用open()方法,返回值和urlopen一样。记住一个要点:Handler构建Opener。
登录验证
![这里写图片描述](https://img-blog.csdn.net/20180812105503514?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzI1MzQzNTU3/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) 遇到这种情况我们就需要进行验证了,示例代码如下:from urllib.request import HTTPBasicAuthHandler,HTTPPasswordMgrWithDefaultRealm,build_opener
from urllib.error import URLError
import ssl
ssl._create_default_https_context = ssl._create_unverified_context#取消ssl验证
url="https://desktop-05hc07a:8443/svn/Test/"
username=""
passeord = ""
p = HTTPPasswordMgrWithDefaultRealm()#构建一个密码管理对象,用来保存需要处理的用户名和密码
p.add_password(None,url,user=username,passwd=passeord)#添加账户信息,第一个参数realm是与远程服务器相关的域信息,一般都是写None
auth_handler = HTTPBasicAuthHandler(p)#创建处理验证的Handler
opener = build_opener(auth_handler)#通过Handler构建opener
try:
result = opener.open(url)
html = result.read().decode("UTF-8",'ignore')#不是所有网页编码都是UTF-8的,我们可以使用开发者工具→控制台输入document.charset()查看编码
print(html)
except URLError as e:
print(e.reason)
获得验证后页面内容:
代理
以后的爬虫项目避免不了使用代理,否则我们自己的IP很容易被封掉。有许多免费的高匿代理,譬如[西刺代理](http://www.xicidaili.com/nn)。虽然不稳定。还是要花钱呐! 示例:from urllib.request import ProxyHandler,build_opener
from urllib.error import URLError
proxy_handler = ProxyHandler({'http':'219.141.153.41:80','https':'118.190.145.138:9001'})
opener = build_opener(proxy_handler)
try:
resp = opener.open("https://www.baidu.com")
print(resp.read().decode('utf-8'))
except URLError as e:
print(e.reason)
ProxyHandler传递一个字典类型,里面是我们设置的代理IP,我们可以添加许多代理IP。后面我们可以自己实现一个代理IP池,玩转代理。
Cookie
首先我们用一段示例演示获取cookie:import http.cookiejar,urllib.request
cookie = http.cookiejar.CookieJar()#声明CookieJar对象
handler = urllib.request.HTTPCookieProcessor(cookie)
opener = urllib.request.build_opener(handler)
resp = opener.open("https://www.baidu.com")
for item in cookie:
print(item.name +'='+item.value)
获取到cookie信息后可以将它以文本信息保存:
import http.cookiejar,urllib.request
filename = 'cookies.txt'
#原来的CookieJar要缓存MozillaCookisJar,它是CookieJar的子类,在生成文件时会用它,
#将cookie信息保存为Mozilla型浏览器格式
cookie = http.cookiejar.MozillaCookieJar(filename)
handler = urllib.request.HTTPCookieProcessor(cookie)
opener = urllib.request.build_opener(handler)
resp = opener.open('https://www.baidu.com')
cookie.save(ignore_discard=True,ignore_expires=True)#保存cookie直至废弃;保存cookie直至过期
保存的cookie文件:
我们可以使用load()方法获取保存的cookie文件:
import http.cookiejar,urllib.request
filename = 'cookies.txt'
cookie = http.cookiejar.MozillaCookieJar(filename)
cookie.load(filename,ignore_discard=True,ignore_expires=True)
handler = urllib.request.HTTPCookieProcessor(cookie)
opener = urllib.request.build_opener(handler)
resp = opener.open('https://www.baidu.com')
urllib.error
URLError
URLError类是OSError子类,是error异常模块的基类,有request模块产生的异常多可以通过捕获这个类来处理。它有一个reason属性,返回错误的原因。
from urllib import error,request
try:
request.urlopen("http://catwithwing.s1.natapp.cc/index.html")#访问一个不存在的网站
except error.URLError as e:
print(e.reason)
print("执行其他操作")
出现异常错误后程序没有崩掉,继续执行其他操作,这才是我们想要的结果。
HTTPError
HTTPError类是URLError类的子类,它用来专门处理HTTP请求错误,比如认证失败等。它有三个属性:
- **code:**返回HTTP状态吗。
- **reason:**返回错误原因。
- **headers:**返回请求头。
from urllib import error,request
try:
request.urlopen("http://catwithwing.s1.natapp.cc/index.html")#访问一个不存在的网站
except error.HTTPError as e:
print(e.code,e.reason,e.headers,sep='\n')
print("执行其他操作")
我们可以先捕获HTTPError,再捕获URLError。使程序更加健壮。
urllib.robotparser##
首先我们要知道一个协议-----Robots协议。它也称作爬虫协议,机器人协议,全称为网络爬虫排除标准(Robots Exclusion Protocol),它告诉爬虫或者搜索引擎哪些页面可以爬取,哪些不可以。它通常是一个robots.txt的文件,放在网站的根目录下。比如豆瓣的robots.txt就是https://www.douban.com/robots.txt
。
内容如下:
User-agent: *
Disallow: /subject_search
Disallow: /amazon_search
Disallow: /search
Disallow: /group/search
Disallow: /event/search
Disallow: /celebrities/search
Disallow: /location/drama/search
Disallow: /forum/
Disallow: /new_subject
Disallow: /service/iframe
Disallow: /j/
Disallow: /link2/
Disallow: /recommend/
Disallow: /trailer/
Disallow: /doubanapp/card
Sitemap: https://www.douban.com/sitemap_index.xml
Sitemap: https://www.douban.com/sitemap_updated_index.xml
# Crawl-delay: 5
User-agent: Wandoujia Spider
Disallow: /
User-Agent设置此协议对哪些爬虫有效,*则表示所有的爬虫都要接受这个协议。
Disallow设置哪些目录是不允许爬取的,与之相对的是Allow,它设置哪些目录可以爬取。
Sitemap,主要是因为很多网站的内容都没有其他链接,为了把这些链接更好的连接起来,让爬虫能抓取更多的资源。
看看豆瓣的这个协议我们就会发现啥都不可以爬了。所以我们一般是不听的。
既然看了我们就来个示例,解析一下robots.txt文件吧:
from urllib import robotparser
rp = robotparser.RobotFileParser()
rp.set_url("https://www.douban.com/robots.txt")#设置robots..txt文件的url
rp.read()#读取robots文件并进行分析,这一步是必须的,如果不调用这个方法,后续的一切判断多少False,即使可以爬取的网页也是
#can_fetch()判断能否爬取指定URL,第一个参数是User-Agent,第二个参数是URL
print(rp.can_fetch('*',"https://www.douban.com/location/drama/search"))#判断https://www.douban.com/location/drama/search能不能爬取
其他要点
有时候我们会在url上填写中文字符,如果我们不做任何处理是会导致乱码的,我们需要用quote()
方法将中文字符转化为URL编码。
import urllib.parse
url = "https://www.baidu.com/s?wd="+urllib.parse.quote("美女")
print(url)
将URL编码后的字符转为中文字符我们可使用unquote()
方法:
import urllib.parse
url = "https://www.baidu.com/s?wd=%E7%BE%8E%E5%A5%B3"
print(urllib.parse.unquote(url))