简介:urllib库(官方文档!),是Python内置的HTTP请求库,它包含如下4个模块:
1.request:它是最基本的HTTP请求模块,可以用来模拟发送请求。就像在浏览器里输入网址然后回车一样,
只需要给库方法传入URL以及额外的参数,就可以模拟实现这个过程了。
2.error:异常处理模块,如果出现请求错误,我们可以捕获这些异常,然后进行重试或其他操作以保证程序
不会意外终止。
3.parse:一个工具模块,提供了多种URL处理方法。比如:拆分,解析,合并等。
4.robotparser:主要是用来识别网站的robots.txt文件,然后判断哪些网站可以爬,哪些网站不可以爬,
其实用的很少!
1.发送请求
使用urllib的request模块,我们可以方便地实现请求的发送并得到响应。
1.urlopen()
urilib.request模块提供了最基本的构造HTTP请求的方法,利用它可以模拟浏览器的一个请求发起的过程,同时它还带有处理授权验证(authentication),重定向(redirection),浏览器Cookies以及其他内容。
(1)简单使用:
import urllib.request
response = urllib.request.urlopen("http://www.baidu.com")
print(type(response))
print("*"*50)
print(response.read().decode('utf-8'))
很容易的就完成了百度首页的抓取,输出了网页的源代码。同时,我们发现这个响应是一个HTTPResponse类型的对象,主要包含read(),readinto(),getheader(name),getheaders()等方法,以及msg,version,status,reason等属性。例如:调用read()方法可以得到返回的网页内容;调用getheaders()方法可以得到响应的头信息;调用getheader(name)方法可以得到响应头中的name值!
(2)高级使用:如果想给链接传递一些参数,就要用到urlopen()函数的其他参数!
# 源码如下:
def urlopen(url, data=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT,
*, cafile=None, capath=None, cadefault=False, context=None):
①data参数
data参数是可选的,如果需要添加该参数,要使用bytes()方法将参数转化为字节流编码格式的内容,即bytes类型。并且,此时它的请求方式也不再是GET方式,而是POST方式(我们传递的参数出现在form字段中,表明是模拟了表单提交的方式!)。
import urllib.request,urllib.parse
data = bytes(urllib.parse.urlencode({'word':'hello'}),encoding='utf-8')
response = urllib.request.urlopen('http://httpbin.org/post', data=data)
print(response.read())
使用bytes()方法,将参数转码为bytes(字节流)类型。该方法第一个参数需要是str(字符串)类型,需要用urllib.parse模块里的urlencode()方法来将参数字典转化为字符串;第二个参数指定编码格式。
②timeout参数
timeout参数用于设置超时时间,单位是秒,意思是如果请求超出了设置的这个时间,还么有得到响应,就会抛出URLError异常,该异常属于urllib.error模块,错误原因是超时。如果不指定,就会使用全局默认时间。
用武之地:
可以通过设置这个超时时间来控制一个网页如果长时间未响应, 就跳过它的抓取。这可以利用try except语句来实现。如下:
import socket
import urllib.request
import urllib.error
try:
response = urllib.request.urlopen('http://httpbin.org/get', timeout=0.01)
except urllib.error.URLError as e:
if isinstance(e.reason, socket.timeout): # 判断异常是socket.timeout类型(意思就是超时异常),从而得出它确实是因为超时而报错!
print('TIME OUT') # 按照常理来说。0.01秒内基本不可能得到服务器响应,因此输出了TIME OUT提示。
2.Request
urlopen()方法可以实现最基本请求的发起,但是不足以构建一个完整的请求。如果请求中需要加入Headers等信息,就要使用更强大的Request类来构建!
(1)简单使用
①初体验
import urllib.request
request = urllib.request.Request('https://www.baidu.com')
response = urllib.request.urlopen(request)
print(response.read().decode('utf-8'))
其实,依然使用的是urlopen()方法来发送这个请求,只不过这次该方法的参数不在是URL,而是一个Request类型的对象。通过构造这个数据结构,一方面我们可以将请求独立成一个对象,另一方面可以更丰富灵活的配置参数。
②灵活运用参数
第一部分:讲解!
# 源码如下:
def __init__(self, url, data=None, headers={},
origin_req_host=None, unverifiable=False,
method=None):
# 参数讲解:
1. url:用于请求URL,必传参数,其他都是可选参数。
2. data:如果要传,必须是bytes(字节流)类型的。
3. headers:是一个字典,他就是请求头。我们可以在构造请求时通过headers参数直接构造,也可以通过调用请求实例的add_header()方法添加。
4. origin_req_host:请求放的host名称或者IP地址。
5. unverifiable:表示这个请求是否是无法验证的,默认是False,意思就是说用户没有足够权限来选择接受这个请求的结果。
6. method:是一个字符串,用来指示请求使用的方法。
第二部分:实战!
import urllib.request,urllib.parse
from fake_useragent import UserAgent
url = 'http://httpbin.org/post'
headers = {
'User-Agent':UserAgent().random
}
dict = {
'name':'Peter'
}
data = bytes(urllib.parse.urlencode(dict), encoding='utf-8')
request = urllib.request.Request(url, data=data, headers=headers, method='POST')
response = urllib.request.urlopen(request)
print(response.read().decode('utf-8'))
(2)高级用法
上面我们虽然可以构造请求,但是对于一些更高级的操作(比如Cookies处理,代理设置等),该怎么办?
这就需要更强大的工具Handler!简而言之,我们可以把它理解为各种处理器,有专门处理登录验证的,有处理Cookies的,有处理代理设置的。利用他们,我们几乎可以做到一切HTTP请求中所有的事情。
第一部分:简介!
urllib.request模块里的BaseHandler类是所有其他Handler 的父类,提供了最基本的方法。下面是举例的各种继承了这个BaseHandler类的Handler子类:
另一个比较重要的类就是OpenerDirector,我们称为Opener。之前用过urlopen()这个方法,实际上就是urllib为我们提供的一个Opener。
需要通过Handler实现更高级的功能,就要深入一层进行配置,使用更底层的实例来完成操作,这就用到了Opener(简言之:就是利用Handler来构建Opener)。Opener可以使用open()方法,返回的类型和urlopen()一样。
第二部分:实战!
第一个:验证------有些网站在打开时就会弹出提示框,直接提示你输入用户名和密码,验证成功才能查看页面。
from urllib.request import HTTPPasswordMgrWithDefaultRealm,HTTPBasicAuthHandler,build_opener
from urllib.error import URLError
username = 'username'
password = 'password'
url = 'url'
p = HTTPPasswordMgrWithDefaultRealm()
p.add_password(None, url, username, password)
# 首先,实例化HTTPBasicAuthHandler对象,参数是HTTPPasswordMgrWithDefaultRealm对象,它利用add_password()添加进去用户名和密码,
# 这样就建立了一个处理验证的handler。
auth_handler = HTTPBasicAuthHandler(p)
# 然后,使用这个Handler并使用build_opener()方法构建一个Opener,这个Opener在发送请求时就相当于已经验证成功了!
opener = build_opener(auth_handler)
try:
# 最后,使用Opener的open()方法打开链接,就可以完成验证了!
result = opener.open(url)
html = result.read().decode('utf-8')
except URLError as e:
print(e.reason)
第二个:Cookies
首先,获取网站的Cookies。
import http.cookiejar,urllib.request
# 首先,声明一个CookieJar对象
cookie = http.cookiejar.CookieJar()
# 然后,利用HTTPCookieProcessor构建一个Handler
handler = urllib.request.HTTPCookieProcessor(cookie)
# 最后,利用build_opener构建Opener
opener = urllib.request.build_opener(handler)
# 执行open()函数即可!
response = opener.open('http://www.baidu.com')
for item in cookie:
print(item.name+"="+item.value)
拓展:输出成文件格式,即以文本形式保存!
import http.cookiejar,urllib.request
filename = 'cookies.txt'
# 此时,CookieJar就需要换成MozillaCookieJar,它在生成文件时会用到,是CookieJar的子类,
# 可以用来处理Cookie和文件相关的事件,比如读取和保存Cookie,可以将Cookies保存成
# Mozilla型浏览器的Cookie格式。
cookie = http.cookiejar.MozillaCookieJar(filename)
handler = urllib.request.HTTPCookieProcessor(cookie)
opener = urllib.request.build_opener(handler)
response = opener.open('http://www.baidu.com')
cookie.save(ignore_discard=True, ignore_expires=True)
“
如果改为
cookie = http.cookiejar.LWPCookirJar(filename)
那么会保存成libwww-perl(LWP)格式的Cookies文件!
”
那么,生成了Cookies文件后,怎样从文件中读取利用呢?(以LWPCookieJar格式为例)
import http.cookiejar,urllib.request
cookie = http.cookiejar.LWPCookieJar()
# 调用load()方法来读取本地的Cookies文件,获取到了Cookies的内容(注意前提一定要生成并保存了),然后获取Cookies之后使用同样的方法构建Handler和Opener即可!
cookie.load('cookies.txt', ignore_expires=True, ignore_discard=True)
handler = urllib.request.HTTPCookieProcessor(cookie)
opener = urllib.request.build_opener(handler)
response = opener.open('http://www.baidu.com')
print(response.read().decode('utf-8'))
2.处理异常
urllib的error模块定义 了由request模块产生的异常,如果出现了问题,request模块便会抛出error模块中定义的异常!
1.URLError
URLError类来自urllib库的error模块,它继承自OSError类,是error异常模块的基类,由request模块产生的异常都可以通过捕获这个类来处理。(属性reason:返回错误的原因。)
from urllib import request,error
try:
response = request.urlopen('http://www.baidu.com/peter')
except error.URLError as e:
print(e.reason)
我们打开一个不存在的页面,本来该报错,但是我们捕获了这个URLError异常。
2.HTTPError
它是URLError的子类,专门用来处理HTTP请求错误,比如认证请求失败等。它有如下三个属性:
1.code:返回HTTP状态码;
2.reason:同父类一样,返回错误的原因;
3.headers:返回请求头。
因为URLError是HTTPError的父类,所以可以先选择捕获子类的错误,如果不是HTTPError异常,再去捕获父类的错误:
from urllib import request,error
try:
response = request.urlopen('http://cuiqingcai.com/index.htm')
except error.HTTPError as e:
print(e.reason, e.code, e.headers, sep='\n')
except error.URLError as e:
print(e.reason)
else:
print('Request Successfully')
拓展:注意!有时候reason属性返回的不一定是字符串,也可能是一个对象,如下例子:
from urllib import request,error
import socket
try:
response = request.urlopen('http://www.baidu.com', timeout=0.01)
except error.URLError as e:
print(type(e.reason))
if isinstance(e.reason, socket.timeout):
print('TIME OUT !')
3.解析链接
urllib库里还提供了parse模块,它定义了处理URL的标准接口,例如:实现URL各部分的抽取、合并以及链接转换。
(1)urlparse()
①基本使用:
该方法可以实现URL的识别和分段。
from urllib.parse import urlparse
result = urlparse('http://www.baidu.com/index.html;user?id=5#comment')
print(type(result), result)
返回的结果是一个ParseResult类型的对象,urlparse()方法将其拆分成了6个部分:
????/前面的就是scheme,代表协议;第一个/符号前面的便是netloc,即域名;后面是path,即访问路径;分号;后面的是params,代表参数;问号?后面是查询条件query,一般用作GET类型的URL;井号#后面的是锚点,用于直接定位页面内部的下拉位置。
可得一个标准的链接格式:
scheme://netloc/path;params?query#fragment
②高级使用:
urlparse()的API用法:
# 源码:
def urlparse(url, scheme='', allow_fragments=True):
# 三个参数讲解:
“
1.url:必填项,即待解析的URL;
2.scheme:默认的协议(比如http或https等)。scheme参数只有在URL中不包含scheme信息时才生效。如果URL中有scheme信息,就会返回解析出的scheme。
3.allow_fragments:即是否忽略fragment。如果它被设置为False,fragment部分就会被忽略,会被解析为path,parameters或者query的一部分,而fragment部分为空。
”
举例看:
from urllib.parse import urlparse
result = urlparse('http://www.baidu.com/index.html;user?id=5#comment', allow_fragments=False)
print(result)
假设URL中不包含params和query,再试试:
可以发现,fragment会被解析为path的一部分;
而且,返回结果ParseResult实际上是一个元组,可以用索引顺序或者属性名获取!
from urllib.parse import urlparse
result = urlparse('http://www.baidu.com/index.html#comment', allow_fragments=False)
print(result)
print(result.scheme, result[0], sep='\n')
(2)urlunparse()
urlparse()的对立方法urlunparse(),接收的参数是一个可迭代对象,长度必须是6,否则会报错!
from urllib.parse import urlunparse
data = ['http', 'www.baidu.com', 'index.html', 'user', 'a=6', 'comment']
print(urlunparse(data))
此处的参数data用了列表类型,当然也可以使用其他类型,比如元组或者特定的数据结构!
(3)urlsplit()
和urlparse()方法非常相似,但是不再单独解析params,它只返回5个结果,上面例子中的params会合并到path中。(常用!)如下:
from urllib.parse import urlsplit
result = urlsplit('http://www.baidu.com/index.html;user?id=5#comment')
print(result)
# 它的返回结果是SplitResult,是一个元组类型,可以用属性及索引获取!
print('返回结果是:',type(result))
print(result.scheme, result[0], sep='\n')
(4)urlunsplit()
与urlunparse()类似,它也是将链接各个部分组合成完整链接的方法,传入的参数也是一个可迭代对象,唯一的区别是长度必须是5!
from urllib.parse import urlunsplit
data = ['http', 'www.baidu.com', 'index.html;user', 'a=6', 'comment']
print(urlunsplit(data))
(5)urljoin()
上述的urlunparse()和urlunsplit()方法可以完成链接的合并,但是前提是必须有特定长度的对象,链接的每一部分都要清晰分开。
这就要用到生成链接的另一个方法-----urljoin()方法。我们可以提供一个base_url(基础链接)作为第一个参数,将新的链接作为第二个参数,该方法会分析base_url的scheme,netloc和path这3个内容并对新链接缺失的部分进行补充,最后返回结果。
可以发现,base_url提供了三项内容scheme,netloc和path。如果这三项在新的链接里不存在,就予以补充;如果存在,就使用新链接里的!
(6)urlencode()
此方法在构造GET请求参数的时候很有用!
from urllib.parse import urlencode
params = {
'name':'peter',
'age':22
}
base_url = 'http://www.baidu.com?'
# 调用urlencode()犯法将字典序列化为GET请求参数
url = base_url + urlencode(params)
print(url)
(7)parse_qs()
有了序列化,必然就有反序列化。可将一串GET请求参数,利用parse_qs()方法将其转回字典!
from urllib.parse import parse_qs
query = 'name=peter&age=22'
print(parse_qs(query))
(8)parse_qsl()
用于将参数转化为元组组成的列表。
from urllib.parse import parse_qsl
query = 'name=peter&age=22'
print(parse_qsl(query))
(9)quote()
用于将内容转化为URL编码的格式。比如:将中文参数转化为URL编码。
from urllib.parse import quote
keyword = "壁纸"
url = 'http://www.baicu.com/s?wd=' + quote(keyword)
print(url)
(10)unquote()
用于将URL解码。
from urllib.parse import unquote
url = 'http://www.baicu.com/s?wd=%E5%A3%81%E7%BA%B8'
print(unquote(url))
4.分析Robots协议
利用urllib的robotparser模块,可以实现网站Robots协议的分析。
(1)Robots协议
Robots协议也称作爬虫协议,机器人协议,用来告诉爬虫和搜索引擎哪些页面可以抓取,哪些不可以抓取。通常是一个robots.txt的文本文件,一般放在网站的根目录下。
当搜索爬虫访问一个站点时,他首先会检查这个站点根目录下是否有robots.txt文件,如果存在,搜索爬虫会根据其中定义的爬取范围来爬取。如果没有,搜索爬虫便会访问所有可直接访问的页面。
举例:下述Robots协议实现了对所有搜索爬虫只允许爬取public目录的功能。
User-agent:*
Disallow: /
Allow: /public/
1.User-agent描述了搜索爬虫的名称,此处设置为*则代表该协议对任何爬取爬虫有效。可以有多条,至少有一条!
2.Disallow指定了不允许抓取的目录,此处设置为/则代表不允许抓取所有的页面。
3.Allow一般和Disallow一起使用,一般不单独使用,用来排除某些限制。此处设置为/public/,代表所有页面不允许抓取,但可以抓取public目录。
(2)爬虫名称
(3)robotparser
①简单讲解:
可以使用robotparser模块来解析robots.txt,该模块提供了一个类RobotFileParser,它可以根据某网站的robots.txt文件来判断一个爬取爬虫是否有权限来爬取这个网页。
使用方法,只需在构造方法里传入robots.txt的链接即可。当然,也可在声明的时候不传入,默认为空,最后再使用set_url()方法设置也可。下述是它的声明:
# 源码:
def __init__(self, url=''):
②灵活使用:
from urllib.robotparser import RobotFileParser
# 首先,创建RobotFileParser对象
rp = RobotFileParser()
# 然后,通过set_url()方法设置了robots.txt的链接
rp.set_url('http://www.jianshu.com/robots.txt')
rp.read()
# 利用can_fetch()方法判断了网页是否可以被抓取
print(rp.can_fetch('*', 'http://www.jianshu.com/p'))
print(rp.can_fetch('*', 'http://www.jianshu.com/p/b67554025d7dsdfsdf'))
同样可以使用parse()方法执行读取和分析:
from urllib.robotparser import RobotFileParser
from urllib.request import urlopen
# 首先,创建RobotFileParser对象
rp = RobotFileParser()
rp.parse(urlopen('http://www.jianshu.com/robots.txt').read().decode('utf-8').split('\n'))
# 利用can_fetch()方法判断了网页是否可以被抓取
print(rp.can_fetch('*', 'http://www.jianshu.com/p'))
print(rp.can_fetch('*', 'http://www.jianshu.com/p/b67554025d7dsdfsdf'))
本文基于《Python3网络爬虫开发实战》而做笔记!