1.异常处理
在已经掌握了基本的爬虫技能,但是如果再发送请求中出现异常,如网络不好,请求被拒等情况,就可能出现报错而终止运行程序。
urllib的error模块定义了由request模块产生的异常。如果出现了问题,request模块便会爆出error模块中定义的异常。现在就使用error模块来处理各种异常。
1.1URLErrror
URLError类来自urllib的error模块,它继承自OSError类,是error异常模块的基类,由request模块所产生的异常都可以通过它来捕获。
它的一个重要属性reason,返回错误原因,有助于我们判断异常类型。
from urllib import request,error
try:
request.urlopen('https://blog.csdn.net/Watson_Ashin/nothispage')
except error.URLError as e:
print(e.reason)
==> Not Found
上面我们访问了一个不存在的地址,最终返回的是"Not Found",且程序并没有报错,就是说我们成功处理了异常,而程序依然再运行。
1.2HTTPERROR
它是URLError的子类,专门用来处理HTTP请求错误,比如认证请求失败等问题。它有如下三个属性:
- code: 返回HETTP状态码,比如404表示网页不存在,500表示服务器内部错误等。
- reason:返回错误原因
- headers:返回请求头
from urllib import request,error
try:
request.urlopen('https://blog.csdn.net/Watson_Ashin/nothispage')
except error.HTTPError as e:
print(e.reason,e.code,e.headers,sep='\n')
==>输出如下
Not Found
404
Server: openresty
Date: Mon, 10 Feb 2020 07:36:31 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 15600
Connection: close
Vary: Accept-Encoding
Set-Cookie: uuid_tt_dd=10_18823982490-1581320191409-585554; Expires=Thu, 01 Jan 2025 00:00:00 GMT; Path=/; Domain=.csdn.net;
Set-Cookie: dc_session_id=10_1581320191409.605227; Expires=Thu, 01 Jan 2025 00:00:00 GMT; Path=/; Domain=.csdn.net;
ETag: "5e3b798b-3cf0"
上面的代码就返回了错误原因,错误码和请求头。因为URLError是HTTPerror的父类,所以根据规范应该先捕获子类,再捕获父类,所以代码如下:
from urllib import request,error
try:
request.urlopen('https://blog.csdn.net/Watson_Ashin/nothispage')
except error.HTTPError as e:
print(e.reason,e.code,e.headers,sep='\n')
except error.URLError as e:
print(e.reason)
else:
print('successful')
==>输出如下
Not Found
404
Server: openresty
Date: Mon, 10 Feb 2020 07:40:00 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 15600
Connection: close
Vary: Accept-Encoding
Set-Cookie: uuid_tt_dd=10_18823982490-1581320400576-704001; Expires=Thu, 01 Jan 2025 00:00:00 GMT; Path=/; Domain=.csdn.net;
Set-Cookie: dc_session_id=10_1581320400576.261739; Expires=Thu, 01 Jan 2025 00:00:00 GMT; Path=/; Domain=.csdn.net;
ETag: "5e3b798b-3cf0"
这样就可以做到先捕获HTTPError,获取它的错误状态码、原因、 headers 等。
如果不是 HTTPError 异常,就会捕获URLError异常,输出错误原因。
最后,用 else 来处理正常的逻辑。 这是一个饱满的的异常处理写法。
有时候,reason返回的是一个对象而非实例的情况下。
import socket
import urllib.request
import urllib.error
try:
response = urllib.request.urlopen('https://www.baidu.com',timeout=0.01)
except urllib.error.URLError as e:
print(type(e))
if isinstance(e.reason,socket.timeout):
print('TIME OUT')
==>
<class 'urllib.error.URLError'> TIME OUT
这里我们直接设置超时时间来强制抛出 timeout 异常。
可以发现, reason 属性的结果是socket.timeout 类。 所以,这里我们可以用 isinstance()方法来 判断它的类型,作出更详细的异常判断。
所以为了让爬虫程序更加稳健,这需要好好处理各种错误!
2.链接解析
urllib 库里还提供了parse模块,它定义了处理URL的标准接口,例如实现URL各部分的抽取、合并以及链接转换。 它支持如下协议的 URL 处理:file、fip、gopher、hdl、http、https、imap、mailto、mms、news、nntp、prospero、rsync、rtsp、rtspu、sftp、sip、sips、snews、svn、svn+ssh、telnet和 wais。、
其实就是构建URL,但是其实普通的字符串拼接也可以达到同样的效果,而使用parse模块可以高效得替换参数,另外一个就是解决编码问题。
2.1urlparse()
该方法可以实现URL的识别和分段
from urllib.parse import urlparse
result = urlparse('https://www.baidu.com/index.htrp;user?id=5#comment ')
print(type(result),result,sep='\n')
<class 'urllib.parse.ParseResult'>
ParseResult(scheme='https', netloc='www.baidu.com', path='/index.htrp', params='user', query='id=5', fragment='comment ')
可以看到,返回结果是一个ParseResult 类型的对象,它包含 6个部分,分别是scheme、 netloc、 path、 params 、 query 和 fragment。 观察一下该实例的 URL: http://www.baidu.com/index.html;user?id=S#comment ,urlparse()方法将其拆分成了 6个部分。 大体观察可以发现,解析时有特定的分隔符。 ://前面的就是scheme,代表协议; 第一个/符号前面便是netloc,即域名, 后面是 path,即访问路径; 分号;后面params,代表参数; 问号?后面是查询条件 query,一般用作GET类型的URL(以key-value形式); 井号#后面是锚点,用于直接定位页面内部的下拉位置。
2.urlunparse()
urlunparse()是urlparsed()的对立方法,他接受的参数是一个可迭代的对象,但是长度必须为6。其实就是简单的字符拼接,以构造URL。
from urllib.parse import urlunparse
data = ['htttp','www.baidu.com','index.html','user','a=6','comment']
print(urlunparse(data))
==> htttp://www.baidu.com/index.html;user?a=6#comment
3.urlsplit()
这个方法和urlparse()方法非常相似,只不过他不再单独解析params这以部分,只返回五个结果。且可以通过索引获取相应的数值。
from urllib.parse import urlunparse
data = ['htttp','www.baidu.com','index.html','user','a=6','comment']
print(urlunparse(data))
==>SplitResult(scheme='http', netloc='www.baidu .com', path='/index.html;user', query='id=5', fragment='comment')
==>http
4.urlunsplit()
与urlunparse()类似,用于构造URL,且传入参数也需要为一个可迭代对象,但是长度必须为5,即params这个参数不传入。
5.urljoin()
有了 urlunparse()和 urlulisplit()方法,我们可以完成链接的合井,不过前提必须要有特定长度的对象,链接的每一部分都要清晰分开。 此外,生成链接还有另一个方法,那就是urljoin()方法。 我们可以提供一个基础链接(base_ur)作为第一个参数,将新的链接作为第二个参数,该方法会分析base_url的scheme、 netloc和path这3个内容并对新链接缺失的部分进行补充,最后返回结果。
from urllib.parse import urljoin
print(urljoin('http://www.baidu.com','FAQ.html'))
print(urljoin('http://www.baidu.com ','https://cuiqingcai.com/FAQ.html'))
print(urljoin('http://www.baidu.com/about.html', 'https://cuiqingcai.com/FAQ.html'))
print(urljoin('http://www.baidu.com/about.html','https://cuiqingcai.com/FAQ.html?question=2'))
print(urljoin('http://www.baidu.com?wd=abc','https://cuiqingcai.com/index.php'))
print(urljoin('http://www.baidu.com','?category=2#comment'))
print(urljoin('www.baidu.com','?category=2#comment'))
print(urljoin('www.baidu.com#comment','?category=2'))
http://www.baidu.com/FAQ.html
https://cuiqingcai.com/FAQ.html
https://cuiqingcai.com/FAQ.html
https://cuiqingcai.com/FAQ.html?question=2
https://cuiqingcai.com/index.php
http://www.baidu.com?category=2#comment
www.baidu.com?category=2#comment
www.baidu.com?category=2
base_url 提供了三项内容scheme,netloc和path。 如果这3项在新的链接里不存在, 就予以补充;如果新的链接存在,就使用新的链接的部分。而base_url中的 params,query和fragment 是不起作用的。
6.urlencode()
!!!画重点,这是一个常用的方法 urlencode(),它在构造 GET 请求参数的时候非常有用。!!!
from urllib.parse import urlencode
params = { 'name':'watson',
'age' : 22}
base_url = 'http://www.baidu.com?'
url = base_url + urlencode(params)
print(url)
==>http://www.baidu.com?name=watson&age=22
这里参数通过字典来传递给urlencode()方法,然后将其序列化成URL(这里只能用于GET请求),这在爬取多页面的是适合会经常用到,但是其实底层就是简单的字符串替代逻辑。
7.parse_qs() 与parse_qsl()
有了序列化,必然就有反序列化。 如果我们有一串 GET 请求参数,利用 parse_qs()或者parse_qsl()方法,就可以将其转回字典或者元组组成的列表。
from urllib.parse import parse_qs
query= 'name=germey&age=22'
print(parse_qs(query))
==>{'name': ['germey'], 'age': ['22']}
from urllib.parse import parse_qsl
query= 'name=germey&age=22'
print(parse_qsl(query))
==>[('name', 'germey'), ('age', '22')]
8.quote()
该方法可以将内容转化为 URL 编码的格式。 URL 中带有中文参数时,有时可能会导致乱码的问题,此时用这个方法可以将中文转化为 URL 编码
from urllib.parse import quote
keyword ='哪吒'
url = 'https://www.baidu.com/s?wd='+ quote(keyword)
print(url)
==>https://www.baidu.com/s?wd=%E5%93%AA%E5%90%92
这里在搜索类爬虫的适合经常回用到该方法,除了淘宝网,在旅游类也是经常需要字符转化。
9.unquote()
那么既然又编码就要解码,unquote就是将URL编码字符解码。
from urllib.parse import unquote
print(unquote('%E5%93%AA%E5%90%92'))
==> 哪吒