爬虫urllib库的使用
urllib库概述
urllib库是Python内置的HTTP请求库,它可以看作处理URL的组件集合。urllib库包含四大模块:
(1)urllib.request:
请求模块
(2)urllib.error:
异常处理模块
(3)urllib.parse:
URL解析模块
(4)urllib.robotparser:
robots.txt协议
快速使用urllib库爬取网页
爬取网页其实就是通过URL获取网页信息,这段网页信息的实质就是一段附加了JavaScript和CSS的HTML代码。如果把网页比作一个人,那么HTML就是他的骨架,JavaScript是他的肌肉,CSS是他的衣服。由此看来,网页最重要的数据部分是存在于HTML中的。
快速爬取一个网页
urllib库的使用比较简单,下面是使用urllib快速爬取一个网页,具体代码如下:
import urllib.request
# 调用urllib.request库的urlopen()方法,并传入一个url
response = urllib.request.urlopen("https://www.baidu.com")
# 使用read()方法读取获取到的网页内容
html = response.read().decode('UTF-8')
# 打印网页内容
print(html)
上述代码就是一个简单的爬取网页案例,爬取的网页结果如下:
实际上,如果在浏览器上打开百度首页,右击选择“查看网页源代码”命令,就会发现刚才打印出来的内容一模一样。也就是说,上述案例仅仅用了几行代码就把百度首页的全部代码下载下来了。
分析urlopen()方法
上一小节在爬取网页的时候,有一句核心的爬虫代码,如下所示:
response = urllib.request.urlopen('http://www.baidu.com')
代码调用的是urllib.request模块中的urlopen()方法,他传入了一个百度首页的URL,用的协议是HTTP,这是urlopen()方法最简单的用法。其实urlopen()方法可以接受多个参数,该方法的定义格式如下:
urllib.request.urlopen(url,data=None,[timeout,]*,cafile=None,capath=None,cadefault=False,context=None)
上述方法定义中的参数详细介绍如下:
(1)url:
表示目标资源在网站中的位置,可以是一个表示URL地址的字符串,也可以是一个urllib.request对象。
(2)data:
用来指明向服务器发送请求的额外信息。HTTP协议是Python支持的众多网络通信协议(如HTTP、HTTPS、FIP等)中唯一使用data参数的。也就是说,只有打开http网址时,data参数才有作用。data默认为None,此时是以GET的方式发送请求,当用户设置data参数时,需要将发送的请求方式改为POST。
(3)timeout:
可选参数,该参数用于设置超时时间,单位是秒。
(4)cafile/capath/cadefault:
用于实现可信任的CA证书的HTTPS请求,这些参数很少使用。
(5)context:
实现SSL加密传输,该参数很少使用。
使用HTTPResponse对象
使用urllib.request模块中的urlopen()方法发送请求后,服务器返回的响应内容封装在一个HTTPResponse类型的对象中。示例代码如下:
import urllib.request
response = urllib.request.urlopen("http://www.baidu.com")
print(type(response))
执行示例代码,输出结果为:
从输出结果可以看出,HTTPResponse类属于Http.client模块,该类提供了获取URL、状态码、响应内容等一系列方法。常见的方法如下:
(1)geturl():用于获取响应内容的URL,该方法可以验证发送的HTTP请求是否被重新调配。
(2)info():返回页面的元信息。
(3)getcode():返回HTTP请求的响应状态码
下面使用一段代码演示这几个方法的使用,具体如下:
import urllib.request
response = urllib.request.urlopen("http://www.baidu.com")
# 获取响应信息对应的URL
print(response.geturl())
# 获取响应码
print(response.getcode())
# 获取页面元信息
print(response.info())
执行上述代码,其输出结果如下:
构造Request对象
当使用urlopen()方法发送一个请求时,如果希望执行更为复杂的操作(如增加HTTP报头),则必须创建一个Request对象来作为urlopen()方法的参数。下面同样以百度首页为例,演示如何使用Request对象来爬取数据。示例代码如下:
import urllib.request
# 将url作为Request()方法的参数,构造并返回一个Request对象
request = urllib.request.Request('http://www.baidu.com')
# 将Request对象作为urlopen()方法的参数,发送给服务器并接受响应
response = urllib.request.urlopen(request)
# 使用read()方法读取获取到的网页内容
html = response.read().decode('UTF-8')
# 打印网页内容
print(html)
在使用urllib库发送URL时,推荐使用构造Request对象的方法。因为在发送请求时,除了必须设置的url参数外,还可能会加入更多的内容,例如下面参数:
(1)data:
默认为空,该参数表示提交表单数据,同时HTTP请求方法将从默认的GET方式改为POST方式。
(2)header:
默认为空,该参数时一个字典类型,包含了需要发送的HTTP报头的键值对。
下面也是一个构造Request对象的案例,该案例在构造Request对象时传入data和headers参数(对于一些需要登录的网站,如果不是从浏览器发出的请求,是不能获得响应内容的。针对这种情况,需要将爬虫程序发出的请求伪装成一个浏览器发出的请求。伪装浏览器需要自定义请求报头,也就是在发送Request请求时,加入特定的Headers。可以通过查看网页,也可以通过调用Request.add_header()即可,如果想查看已有的Headers,可以通过调用Request_header()查看。)具体代码如下:
import urllib.request
import urllib.parse
url = 'http://www.baidu.com'
'''
# 添加自定义请求
# 调用Request.add_header()添加/修改一个特定的header
request.add_header('connection','keep-alive')
# 通过调用Request.get_header()查看header信息
request.get_header(header_name = 'connection')
'''
# 添加特定Headers---请求伪装
header = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36'}
dict_demo = {'name':'baidu'}
# 编码转换
data = bytes(urllib.parse.urlencode(dict_demo).encode('UTF-8'))
# 将url作为Request()方法的参数,构造并返回一个Request对象
request = urllib.request.Request(url,data=data,headers=header)
# 将Request对象作为urlopen()方法的参数,发送给服务器并接受响应
response = urllib.request.urlopen(request)
# 使用read()方法读取获取到的网页内容
html = response.read().decode('UTF-8')
# 打印网页内容
print(html)
上述案例可以实现百度首页的爬取。通过构造Request对象的方式,服务器会根据发送的请求返回对应的响应内容,这种做法在逻辑上也是非常清晰明确的。
代理服务器
很多网站会检测某段时间某个IP的访问次数,如果同一IP访问过于频繁,那么该网站会禁止来自该IP的访问。针对这个情况,可以使用代理服务器,每隔一段时间换一个代理。如果某个IP被禁止,可以换成其他IP继续爬取数据,从而可以有效解决被网站禁止访问的情况,代理多用于防止”防爬虫“机制。
简单的自定义opener
opener是urllib.request.OpenerDirector类的对象,之前一直使用搞得urlopen就是模块构建好的一个opener,但是它不支持代理、cookie等其他的HTTP/HTTPS高级功能。所以,如果想要设置代理,不能使用自带的urlopen,而是要自定义opener对象。自定义opener需要执行下列3个步骤:
- 使用相关的Handler处理器创建特定功能的处理器对象。
- 通过urllib.request.build_opener()方法使用这些处理器对象创建自定义的opener对象。
- 使用自定义的opener对象,调用open()方法发送请求。这里需要注意的是,如果程序中所有的请求都使用自定义opener,可以使用urllib2.install_opener()将自定义的opener对象定义为全局opener,表示之后凡是调用urlopen,都将使用自定义的opener.
下面实现一个最简单的自定义opener,具体代码如下:
import urllib.request
# 构建一个HTTPHandler处理器对象,支持处理HTTP请求
http_handler = urllib.request.HTTPHandler()
# 调用urllib2.build_opener()方法,创建支持处理HTTP请求的opener对象
opener = urllib.request.build_opener(http_handler)
# 构建Request请求
request = urllib.request.Request('http://www.baidu.com/')
# 调用自定义opener对象的open()方法,发送request请求
# 注意区别(不在通过urllib.request.urlopen()发送请求)
response = opener.open(request)
# 获取服务器响应内容
print(response.read())
上述方式发送请求得到的结果和使用urllib.request.urlopen发送的HTTP/HTTPS请求得到的结果是一样的。吐过在HTTPHandler()方法中增加参数debuglever = 1,会将DebugLog打开,这样程序在执行时,会把收包和发包的报头自动打印出来,以方便调式。示例代码如下:
# 构建一个HTTPHandler处理器对象,同时开启Debug Log,debuglevel值设置为1
http_handler = urllib.request.HTTPHandler(debuglevel=1)
设置代理服务器
用户可以使用urllib.request中的ProxyHandler()方法设置代理服务器,下面就通过示例说明如何使用自定义opener来设置代理服务器。代码如下:
import urllib.request
# 构建两个代理Handler,一个有代理IP(通过字典的形式),一个没有
httpproxy_handler = urllib.request.ProxyHandler({'http':'117.41.38.18'})
nullproxy_handler = urllib.request.ProxyHandler()
# 定义一个代理开关
proxy_switch = True
# 通过urllib.request.build_opener()方法使用代理Handler对象创建自定义opener对象
# 根据代理开关是否打开,使用不同的代理模式
if proxy_switch:
opener = urllib.request.build_opener(httpproxy_handler)
else:
opener = urllib.request.build_opener(nullproxy_handler)
request = urllib.request.Request('http://www.baidu.com')
# 返回响应
response = opener.open(request)
print(response.read())
获取免费开放的代理基本没有成本,用户可以在一些代理网站上手机这些免费的代理,测试后如果可以用,就把他收集起来用在爬虫上面。这里博主给推荐一个免费好用的高匿名HTTP免费代理IP!!!
超时设置
假设有个需求,要爬取1000个网站,如果其中有100个网站需要等待30s才能返回数据,如果要返回所有的数据,至少需要等待3000s,如此长时间的等待显然是不可能的,为此可以为HTTP请求设置超时时间,一旦超过这个时间,服务器还没有返回响应内容,就会抛出一个超时异常,这个异常需要使用try语句来捕获。
例如,使用快代理(一个开放代理网站)中的IP,他的响应速度需要2秒。此时,如果将超时时间设置为1s,程序就会抛出异常。具体代码如下:
import urllib.request
try:
url = 'http://www.baidu.com'
# timeout 设置超时时间
file = urllib.request.urlopen(url,timeout=1)
result = file.read()
print(result)
except Exception as error:
print(error)
运行程序后,输出结果为:
<urlopen error timed out>
常见的网络异常
当使用urlopen()方法发送HTTP请求时,如果urlopen()不能处理返回的响应内容,就会产生错误。这里将针对这两个常见的异常(URLError和HTTPError)以及对他们的错误处理进行简单的介绍。
URLError异常捕获
URLError产生的原因主要有以下几种:
- 没有连接网络。
- 服务器连接失败
- 找不到指定的服务器
代码演示如下:
import urllib.request
import urllib.error
request = urllib.request.Request('https://www.baidu.com')
try:
urllib.request.urlopen(request,timeout=5)
except urllib.error.URLError as error:
print(error)
运行程序后,输出结果:
<urlopen error [Error 11001] getaddrinfo failed>
上述报错信息是urlopen error,错误代码是11001。发生错误的原因是没有找到指定的服务器。
HttpError异常和捕获
每个服务器的HTTP响应都有一个数字响应码,这些响应码有些表示无法处理请求内容。如果无法处理,urlopen()会抛出HTTPError。HTTPError是URLError的子类,他的对象拥有一个整型的code属性,表示服务器返回的错误代码。例如:
import urllib.request
import urllib.error
request = urllib.request.Request('https://www.baidu.com/net')
try:
urllib.request.urlopen(request)
except urllib.error.HTTPError as error:
print(error.code)
输出结果为:
404
上述输出了404的错误代码,其含义是没有找到这个页面。这里需要说明的是,不同的响应码代表不同的含义,例如100 ~ 200范围的号码表示成功,而错误码的范围在400~599。