文章目录
- 1 urllib 库的使用
- 1.1 request模块
- 1.1.1 urlopen类
- 1.1.1.1 最简单的爬虫-爬取百度首页
- 1.1.1.2 urlopen方法的参数
- 1.1.1.2.1 data参数
- 1.1.1.2.2 timeout参数
- 1.1.1.2.3 其他参数
- 1.1.2 Request 类
- 1.1.3 Handler
- 1.2 error模块
- 1.2.1 URLError 类
- 1.2.2 HTTPError类
- 1.2.3 比较好的捕获异常的方法
- 1.3 parse模块
- 1.3.1 urlparse()
- 1.3.1.1 urlparse()的基本解析方式
- 1.3.1.2 urlparse()的API
- 1.3.2 urlunparse()
- 1.3.3 urlsplit()
- 1.3.4 urlunsplit()
- 1.3.5 unjoin()
- 1.3.6 urlencode()
- 1.3.7 parses_qs()
- 1.3.8 parse_qsl
- 1.3.9 quote()
- 1.3.10 unquote()
- 1.4 robotparser模块
- 1.4.1 Robots协议
- 1.4.2 爬虫名称
- 1.4.3 robotparser
1 urllib 库的使用
urllib
库是python内置的HTTP请求库,可以实现HTTP请求的发送。有了urllib
库,我们就无需深入到底层去了解到底是怎样传输和通信的。
使用urllib
库,通过几行代码就可以完成一次请求和响应的处理过程
urllib
库包含4个模块
- request模块:是最基本的HTTP请求模块,可以模拟请求的发送。
- error模块:异常处理模块,可以捕捉请求过程中出现的异常,并进行处理。
- parse模块:一个工具模块,提供了许多URL的处理方法,例如拆分、分解、合并等等。
- robotparser模块:主要是用来识别网站的
robots.txt
文件,判断哪些网站可以爬,哪些不可以爬,实际用的比较少。
1.1 request模块
使用
urllib.request
可以发送请求并得到响应。urllib.request
提供了最基本的构造HTTP请求的方法,利用这个模块可以模拟浏览器的请求发起过程,同时还具有处理授权验证(Authentication)、重定向(Redirection)、浏览器Cookie以及其他一些功能。
1.1.1 urlopen类
1.1.1.1 最简单的爬虫-爬取百度首页
以爬取百度首页为例,看一下如何使用
request
模块,爬取网页信息。
代码如下:
def urlopen_1():
import urllib.request
response = urllib.request.urlopen('https://www.baidu.com')
print(response.read().decode('utf-8')) # read()可以得到响应的网页的内容,并将其使用'utf-8'编码方式解码
print(type(response)) # <class 'http.client.HTTPResponse'>
print(response.status) # 可以直接获得返回响应的状态码 200
print(response.getheaders()) # 可以直接获取返回响应的全部头信息
print(response.getheader('Server')) # 获取响应头中的Server的值 BWS/1.1
print(response.read().decode('utf-8'))
输出结果如下图所示,可以看出有完整的HTML页面结构,并且格式规整。下图是
print(response.read()
打印输出的结果,可以看出,其中也是完整的HTML页面,但是存在许多的转义字符。因此如果想要得到页面的格式化输出,并且便于操作,我们需要对其进行解码。
print(type(response))
的输出结果是:
<class 'http.client.HTTPResponse'>
response
的类型是
HTTPResponse
类型的对象,有很多的方法和属性。
下图是response.getheaders()
序列化输出的结果,可以看出,是响应头中的全部信息。
response.getheaders('Server')
是获取响应头中
Server
的值,从上图中看出 应该返回
BWS/1.1
,实际运行代码,打印输出的也是一致的。
1.1.1.2 urlopen方法的参数
urlopen()
的API如下:urllib.request.urlopen(url, data=None,[timeout]*, cafile=None, capath=None, cadefault=False, context=None)
下面具体说明各个参数的用法
1.1.1.2.1 data参数
data
参数是可选的。是用来传递POST
方法中的表单的。
这个参数,需要使用bytes
将表单信息转化为字节流编码格式的内容。
一旦使用了这个参数,这个请求方法就是POST
bytes(str, encoding)
第一个参数是字符串,第二个参数是采用何种编码方式编码成bytes
类型
由于我们在提交表单时,以键值对(字典)的方式较多,因此还需要通过urllib.parse
模块中的urlencode
方法将字典参数转化为字符串。以测试网站上提交POST请求为例,代码如下:
def urlopen_post():
import urllib.request
import urllib.parse
data = bytes(urllib.parse.urlencode({'name': 'this is a test', 'code': 300}), encoding='utf-8')
response = urllib.request.urlopen('https://www.httpbin.org/post', data=data)
print(response.read().decode('utf-8'))
代码执行的结果如下图所示:可以看出我们传递的参数在form
参数中进行显示,也就是我们的参数data
实质上是提交的表单信息
1.1.1.2.2 timeout参数
timeout
参数是用来设置超时时间的。如果在设置时长内没有得到回应信息,就会报网络错误。
为了显示效果,将时间设置为0.01s
,但是在实际爬虫过程中,一般设置为3-5s
。以请求百度首页为例,设置超时时间为
0.01s
代码如下:
def urlopen_timeout():
import urllib.request
response = urllib.request.urlopen('https://www.baidu.com', timeout=0.01)
print(response.read())
代码运行的结果如下图所示:> 从上图可以看出,超时是
urllib.error.URLError
类型的错误。
因此我们可以对
urllib.error.URLError
进行
try...except..
捕捉,并输出响应的提示信息。
代码如下:
def urlopen_timeout():
import urllib.request
import urllib.error
try:
response = urllib.request.urlopen('https://www.baidu.com', timeout=0.01)
print(response.read())
except urllib.error.URLError as e:
print('TIME OUT')
代码执行的结果如下图所示:
1.1.1.2.3 其他参数
urlopen
除了data
参数、timeout
参数,urlopen
还有context
、cafile
、capath
、cadefault
context
:是用来指定SSL
的设置,该参数必须是ssl.SSLContext
类cafile
:用来指定CA
,在请求HTTPS
链接时用capath
:用来指定CA
证书的路径,在请求HTTPS
链接时用cadefault
:已经弃用,默认是False
1.1.2 Request 类
当我们需要在请求中加入
Headers
等信息的时候,就需要Request
类来构建请求体。
我们在发送请求时,还是通过urlopen
来进行请求urlopen
方法的第一个url
参数,不再传入一个url
链接,而是传入一个Request
类型的对象
Request
类型的对象构造方法如下:urllib.request.Request(url, data=None, headers={}, origin_req_host=None, unverifiable=False, method=None)
url
:用于请求的URL
,这个参数是必传参数
data
:同urlopen
中的data
一样,需要传入bytes
类型数据,如果是数据是字典,需要先编码再转。headers
:是一个字典,是请求头。经常在里面添加User-Agent
来伪装浏览器origin_req_host
:用来指定请求方的host
名称或者IP
地址unverifiable
:表示请求是否是无法验证的,默认取值是False
,method
:表示请求使用的方法,如GET
,POST
,PUT
。这个参数是字符串。以测试网站为例,构建
Request
类,发送POST
请求。
代码如下:
def request_class():
import urllib.request
import urllib.parse
url = 'https://www.httpbin.org/post'
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36',
'Host': 'www.httpbin.org'
}
dict = {'name': 'this is a test', 'code': 300}
data = bytes(urllib.parse.urlencode(dict), encoding='utf-8')
request = urllib.request.Request(url=url, data=data, headers=headers, method='POST')
response = urllib.request.urlopen(request)
print(response.read().decode('utf-8'))
代码运行的结果如下图所示:通过观察输出的form
与Request
对象中我们传入的参数data
一致,可以知道Request
对象中我们传入的参数data
是表单数据,用法和效果与urlopen
中data
一样。
1.1.3 Handler
Handler
可以理解为各种处理器,有专门处理登录验证的、处理Cookie的,处理代理设置的。
利用这个Handler
,我们几乎可以实现HTTP
请求中所有的功能
urllib.request
模块里有一个BaseHandler
类,这是所有Handler
类的父类。
提供了最基本的方法,例如default_open
、protocol_request
等会有各种
Handler
子类继承BaseHandler
类
HTTPDefaultErrorHandler
:用于处理HTTP
响应错误,所有的错误都会抛出HTTPError
类型的异常HTTPRedirectHandler
:用于处理重定向HTTPCookieProcessor
:用于处理Cookie
ProxyHandler
:用于设置代理,默认是空HTTPPasswordMgr
:用于管理密码,维护着用户名密码的对照表HTTPBasicAuthHandler
:用于管理认证。比较重要的类除了
BaseHandler
之外,还有一个OpenerDirector
,我们称之为Opener
。
我们使用的urlopen
就是urllib
库提供的一种Opener
。
当需要实现更高级的功能,深入一层进行配置时,就使用到了Opener
。
1.2 error模块
urllib库中的
error
模块定义了由request
模块产生的异常。
当出现问题时,request
模块便会抛出error
模块中定义的异常Error模块中常用的类有:
URLError
:继承自OSError
,是error异常模块的基类HTTPError
:是URLError
的子类,专门处理HTTP请求错误,如认证请求失败等
1.2.1 URLError 类
URLError
类来自urllib
库的error
模块,继承自OSError
类
是error
异常模块的基类,
由request
模块产生的异常都可以通过捕获这个类来处理
URLError
有一个属性是reason
,
可以在捕获到这个异常时,对异常处理,并打印其reason。
这样就可以防止因为报错而终止运行以超时为例,捕捉异常,并打印原因
代码如下:
def error_except():
import urllib.request
import urllib.error
try:
request = urllib.request.urlopen('https://www.baidu.com', timeout=0.001)
print(request.read().decode('utf-8'))
except urllib.error.URLError as e:
print(type(e.reason))
print(e.reason)
代码执行的结果如下图所示: 程序没有直接报错,而是输出了错误原因,这样就可以避免程序异常终止,同时异常得到了有效处理。
reason
属性返回的不一定是字符串也可能是一个对象
上述代码中打印的type(e.reason)
,其输出结果是TimeoutError
类
因此我们可以使用isinstance
方法来判断其类型改进后的代码如下:
def error_except_urlerror():
import urllib.request
import urllib.error
try:
request = urllib.request.urlopen('https://www.baidu.com', timeout=0.001)
print(request.read().decode('utf-8'))
except urllib.error.URLError as e:
# print(type(e.reason)) # <class 'TimeoutError'>
# print(e.reason)
if isinstance(e.reason, TimeoutError):
print('TIME OUT')
代码执行的结果如下图所示:
只是输出了‘TIME OUT’
1.2.2 HTTPError类
HTTPError
类是URLError
类的子类,是专门用来处理HTTP请求错误的类HTTPError类有三个属性
reason
:同父类一样,用于返回错误的原因code
:返回HTTP状态码headers
: 返回请求头以打开一个不存在的网页为例,对异常进行捕获
并输出其code、reason、headers
代码如下:
def error_except_httperror():
import urllib.request
import urllib.error
try:
request = urllib.request.urlopen('https://www.baidu.com/bucunzai')
print(request.read().decode('utf-8'))
except urllib.error.HTTPError as e:
print(e.code, e.reason, e.headers, sep='\n')
代码执行的结果如下:
1.2.3 比较好的捕获异常的方法
由于
URLError
是HTTPError
的父类,因此可以选择先捕获子类的错误,再捕获父类的错误。对上述代码进行修改,修改后的代码如下:
def error_except_all():
import urllib.request
import urllib.error
try:
request = urllib.request.urlopen('https://www.baidu.com/bucunzai')
print(request.read().decode('utf-8'))
except urllib.error.HTTPError as e:
print(e.code, e.reason, e.headers, sep='\n')
except urllib.error.URLError as e:
print(e.reason)
else:
print('successful ')
修改后的代码 可以先捕获
HTTPError
,获取其错误原因、状态码、请求头等信息
如果不是HTTPError
,就会捕获URLError
,输出错误原因
最后使用else
来处理正常的逻辑。修改后的代码执行的效果同上。
1.3 parse模块
前文在传递data参数的时候,使用urllib.parse模块对字典进行格式转换
urllib.parse
模块定义了处理URL的标准接口,
例如:实现URL各部分的抽取、合并以及链接转换
parse 支持大部分协议的URL处理
parse
中常用的方法有:
urlparse
:可以实现URL的识别和分段urlunparse
:将接收到的参数构造为一个链接,其参数是一个可迭代对象,其长度必须为6urlsplit
:可以实现URL的识别和分段,只返回5个结果。urlunsplit
:将链接各个部分组合成完整链接的方法,其参数是一个可迭代对象,参数长度为5urljoin
:是生成链接的一种方法,并且没有长度要求urlencode
:将字典类型的请求参数转化为GET请求参数parse_qs
:将一串GET请求参数转回字典类型parse_qsl
:将参数转化为由元组组成的列表quote
:可以将内容转化为URL编码的格式,尤其是URL中带有中文参数的时候unquote
:可以进行URL解码,
1.3.1 urlparse()
urlparse()
可以实现URL的识别和分段
1.3.1.1 urlparse()的基本解析方式
使用
urlparse
对网址进行解析
代码如下:
def urlparse_base():
from urllib.parse import urlparse
url = 'https://www.baidu.com/index.html;user?id=5#comment'
result = urlparse(url)
print(type(result))
print(result)
代码执行的结果如下图所示:
从输出的结果可以看到:
解析的结果是一个RarseResult类型的对象,包含6部分:scheme
、netloc
、path
、params
、query
、fragment
对照实例中的URL可以发现:
- //前面的内容就是
scheme
,代表是协议- 第一个/符号前面的是
netloc
, 代表域名- 后面是
path
,代表访问路径- 分号‘;’后面是
params
,代表参数- 问号‘?’后面是查询条件
query
,一般用作GET类型的URL- 井号‘#’后面是锚点
fragment
,用于直接定位页面内部的下拉位置
将上述合并在一起可以得到一个标准的URL格式scheme://netloc/path;params?query#fragment
1.3.1.2 urlparse()的API
urlparse()的API如下:
urlparse(urlstring, scheme=' ',allow_fragments=True)
urlparse()有三个参数
urlstring
:这是必填项,是待解析的URLscheme
:这是默认的协议,如果待解析的URL没有带协议信息,就会将这个作为默认协议allow_fragments
:是否忽略fragment。如果此项被设置为False,那么fragment部分就会被解析为path、params或者query的一部分,而fragment部分为空urlparse()返回的
ParseResult
实际上是一个元组,
可以通过其属性名获取其内容,
也可以使用索引来顺序获取代码如下:
def urlparse_senior():
from urllib.parse import urlparse
url = 'www.baidu.com/index.html;user?id=5#comment'
result = urlparse(url, scheme='https', allow_fragments=False)
print(result)
print('通过属性名获取:', result.path, sep='\t')
print('通过索引获取:', result[4], sep='\t')
代码执行的结果如下图所示:
1.3.2 urlunparse()
urlunparse()
用于构造URL,
其参数是一个可迭代对象,如:列表、元组、或者特定的数据结构
其参数的长度必须是6,否则会抛出参数数量不足或或多的问题分别定义三个列表:一个长度为6,一个长度为7,一个长度为5,
对正常的结果进行输出,对异常进行捕捉。
代码如下:
def urlunparse_test():
from urllib.parse import urlunparse
data_normal = ['https', 'www.baidu.com/index.html', 'user', 'id=5', 'comment']
data_over = ['https', 'www.baidu.com/index.html', 'user', 'id=5', 'comment', 'duoyige']
data_less = ['https', 'www.baidu.com/index.html', 'user', 'id=5']
try:
result_normal = urlunparse(data_normal)
print(result_normal)
result_over = urlunparse(data_over)
print(result_over)
except ValueError as e:
print(e)
try:
result_less = urlunparse(data_less)
print(result_less)
except ValueError as e:
print(e)
代码执行的结果如下:
1.3.3 urlsplit()
urlsplit()
与urlparse()
非常相似,
区别就是urlsplit()
没有params
这一部分,而是将params合到path中
只返回5个结果其返回结果是
SplitResult
类型的对象
同样的SplitResult
也是一个元组,可以通过属性名获取值,也可以根据索引获取
其用法与urlparse()
类似。
1.3.4 urlunsplit()
urlunsplit()
同urlunparse()
类似,
区别在于urlunsplit()
传入的可迭代参数,其长度必须是5。
其用法同urlunparse()
类似。
1.3.5 unjoin()
urlunparse()
和urlunsplit()
都可以完成链接的合并,但是对其可迭代对象参数的长度有限制,链接的每一部分都要清楚的分开。
urljoin()
也是一种生成链接的方法。
该方法有两个参数,第一个参数是基础链接base_url,第二个参数是新的链接urljoin()
会分析base_url
中的scheme
、neltoc
和path
三个部分的内容,并对新链接缺失的部分进行补充。为便于理解,代码如下:
def urljoin_test():
from urllib.parse import urljoin
print('1. 这个新链接没有scheme、netloc: ',
urljoin('https://www.baidu.com', 'newpage.html'), sep='\t')
print('2. 这个新链接什么都不缺,基础链接没有path: ',
urljoin('https://www.baidu.com', 'https://newlink/newpage.html'), sep='\t')
print('3. 这个新链接和基础链接什么都不缺: ',
urljoin('https://www.baidu.com/base.html', 'https://newlink/newpage.html'), sep='\t')
print('4. 这个基础链接什么都不缺,新链接多了一个query: ',
urljoin('https://www.baidu.com/base.html', 'https://newlink/newpage.html?query=2'), sep='\t')
print('5. 这个基础链接少了path,多了一个query,新链接什么都不缺: ',
urljoin('https://www.baidu.com?query=1', 'https://newlink/newpage.html'), sep='\t')
print('6. 这个基础链接少了path;新链接多了query、fragment,少了scheme、netloc、path: ',
urljoin('https://www.baidu.com', '?query=2#comment'), sep='\t')
print('7. 这个基础链接没有scheme、path;新链接多了query、fragment,少了scheme、netloc、path: ',
urljoin('https://www.baidu.com/base.html', '?query=2#comment'), sep='\t')
print('8. 这个基础链接没有scheme、path,多了fragment;新链接多了query,少了scheme、netloc、path: ',
urljoin('https://www.baidu.com#comment', '?query=2'), sep='\t')
运行的结果如下图所示:可以发现 base_url提供了 scheme、netloc和path
如果新链接里面不存在这三项,就用base_url中的进行补充,否则,就使用新链接的
1.3.6 urlencode()
可以理解为将字典的参数序列化为链接中的一部分
在构造GET参数的时候很有用代码如下:
def urlencode_test():
from urllib.parse import urlencode
data = {'name': 'getcanshu', 'type': 'dict'}
baseurl = 'https://www.baidu.com?'
url = baseurl + urlencode(data)
print(url)
代码执行结果如下图所示:
1.3.7 parses_qs()
parse_qs()
就是反序列化,
将GET请求参数转回字典代码如下:
def parse_qs_test():
from urllib.parse import parse_qs
print(parse_qs('name=getcanshu&type=dict'))
运行结果如下图所示:
1.3.8 parse_qsl
parse_qsl()
将参数转化为由元组组成的列表
代码如下:
def parse_qsl_test():
from urllib.parse import parse_qsl
print(parse_qsl('name=getcanshu&type=dict'))
运行结果如下:
可以看出结果是一个列表,列表中每一个元素都是元组,
元组的第一个是参数名,后面的是参数值
1.3.9 quote()
quote()
可以将内容转化为URL编码的格式
当URL中带有中文参数时,有可能会导致乱码问题,
此时可以使用quote()
将中文字符转化为URL编码实例如下:
def quote_test():
from urllib.parse import quote
key = '测试中文参数'
url = 'https://www.baicu.com/s?wd=' + quote(key)
print(url)
运行的结果如下图所示
1.3.10 unquote()
unquote()
是quote()
的逆操作,
可以进行URL解码实例如下:
def unquote_test():
from urllib.parse import unquote
print(unquote('https://www.baicu.com/s?wd=%E6%B5%8B%E8%AF%95%E4%B8%AD%E6%96%87%E5%8F%82%E6%95%B0'))
运行的结果如下图所示:
1.4 robotparser模块
robotparser
模块可以分析网站的Robots
协议
1.4.1 Robots协议
Robots
协议 也称为爬虫协议、机器人协议。全名为网络爬虫排除标准
是用来告诉爬虫和搜索引擎哪些页面课可以抓取,哪些不可以。
通常是一个叫做robots.txt文件,一般放在网站的根目录下搜索爬虫在访问一个站点时,首先会检查这个站点根目录下是否存在
robots.txt
文件
如果存在,就会根据其中定义的爬取范围来爬取。
如果没有找到这个文件,搜索爬虫就会访问所有可直接访问的页面
robots.txt
样例如下:
User-agent: *
Disallow: /
Allow: /public/
User-agent
描述搜索爬虫的名称,设置为* ,代表Robots协议对所有爬虫都有效
如果设置为User-agent: Baiduspider
这代表设置的规则对百度爬虫是有效的。
如果有多条 User-agent记录,意味着有多个爬虫会受到爬取限制
但是至少要指定一条
Disallow
指定了不允许爬虫爬取的目录
设置为 / 代表不允许爬取所有页面
Allow
一般不会单独使用,会和Disallow
一起使用,用来排除某些限制,
上例中Allow设置为/public/,结合Disallow
的设置,
表示所有的页面都不可以进行爬取,但是可以爬取public目录。
1.4.2 爬虫名称
User-Agent
中设置的是爬虫名称,爬虫名称是什么?
爬虫时有固定名字的,常见搜索爬虫的名称及对应得网站如下表所示
爬虫名称 | 网站名称 |
BaiduSpider | 百度 |
Googlebot | 谷歌 |
360Spider | 360搜索 |
YoudaoBot | 有道 |
is_archiver | Alexa |
Scooter | altavista |
Bingbot | 必应 |
1.4.3 robotparser
robotparser
提供了一个类RobotFileParser
,
它可以根据某网站的robots.txt
文件判断一个爬虫是否有权限爬取这个网页
该类的用法很简单,只需要在构造方法里传入robots.txt
文件的链接即可。
当然可以不在声明时传入robots.txt
文件的链接,让其默认为空,后面再使用set_url()
进行设置。
RobotFileParser
类常用的方法:
set_url()
:用来设置robots.txt文件的链接,当然如果在创建RobotFileParser对象时传入了链接,就不需要进行设置了。read()
:读取robots.txt文件并进行分析。如果不调用这个方法,后面的判断都会为Falseparse()
:用来解析robots.txt文件,传入的参数是robots.txt文件中某些行的内容,会按照robots.txt的语法规则来分析这些内容。can_fetch()
:有两个参数,第一个是User-Agent
,第二个参数是要抓取的URL
,返回的结果是布尔类型
。表示User-Agent指示的搜索引擎是否可以抓取这个URL。mtime()
:返回上次抓取和分析robots.txt文件的时间,对于长时间分析和抓取robots.txt文件的搜索爬虫来说,可能需要定期检查以抓取最新的robots.txt文件。modified()
:同样对长时间分析和抓取的搜索爬虫很有帮助,可以将当前时间设置为上次抓取和分析robots.txt文件的时间。
def robot_parser():
from urllib.robotparser import RobotFileParser
rp = RobotFileParser('https://www.baidu.com/robots.txt') # 创建一个RobotFileParser对象实例
rp.read() # 读取和分析robots.txt文件
base_url = 'https://www.baidu.com'
homepage_url = base_url + '/homepage/'
print(rp.can_fetch('Baiduspider', base_url)) # 判断能否通过Baiduspider 爬取百度首页
print(rp.can_fetch('Googlebot', base_url)) # 判断能否通过Googlebot 爬取百度homepage页面
print(rp.can_fetch('Baiduspider', homepage_url)) # 判断能否通过Baiduspider 爬取百度homepage页面
print(rp.can_fetch('Googlebot', homepage_url)) # 判断能否通过Baiduspider 爬取百度homepage页面
运行的结果如下图所示
从运行结果可以看出,不能通过
Googlebot
爬取homepage
页面。
打开百度的robots.txt,可以看到如下信息
从上图可以看出,百度的
robot.txt
文件没有限制Baiduspider
对百度homepage
页面的抓取
但是限制了Googlebot
对homepage
页面的抓取。