一、协程是什么
情景:
今天是周末,囤了一周的美剧没看,你想在早上出门吃早点的时候把电脑打开下载好几部美剧的新出剧集,你是一个接着一个下载,还是说让他们同时下载呢?
我们一般都是同时开始下载所有的新出剧集,哪一部先下载好了,就先看哪一部;没有下载完的剧集就接着下载。
用计算机语言表示:
在一个任务未完成时,就可以执行其他多个任务,彼此不受影响(在看第一集下载好的美剧时,其他美剧继续保持下载状态,彼此之间不受影响),叫异步。
- 既然有异步;
- 那也就有同步:同步就是一个任务结束才能启动下一个(类比你看完一集,才能去看下一集美剧)。
显然,异步执行任务会比同步更加节省时间,因为它能减少不必要的等待。如果你需要对时间做优化,异步是一个很值得考虑的方案。
每当我们爬取一个网页时,都要经过一系列的过程:
- 发送请求
- 服务器响应
- 返回网页
- 获取数据
因此当我们在同步爬取时,需要等待前一个网页爬取完再爬取下一个网页。而很多时候,由于网络不稳定,加上服务器自身也需要响应的时间,导致爬虫会浪费大量时间在等待上。这也是爬取大量数据时,爬虫的速度会比较慢的原因。
我们可以采取异步的爬虫方式,让多个爬取网页的任务互相独立, 互不打扰,免去等待时间。显然这样爬虫的效率和速度都会提高。那么,怎样采取异步的方式呢?
二、回顾计算机历史
每台计算机都靠着CPU(中央处理器)干活。
在过去,单核CPU的计算机在处理多任务时,会出现一个问题:每个任务都要抢占CPU,执行完了一个任务才开启下一个任务。CPU毕竟只有一个,这会让计算机处理的效率很低。
为了解决这样的问题,一种非抢占式的异步技术被创造了出来,这种方式叫多协程。
它的原理是:一个任务在执行过程中,如果遇到等待,就先去执行其他的任务,当等待结束,再回来继续之前的那个任务。在计算机的世界,这种任务来回切换得非常快速,看上去就像多个任务在被同时执行一样。
这就好比你可以边看美剧边吃零食一样,同一时间内即有了视觉上的享受,也有了味觉上的满足。多协程能够再同一段时间内做多件事情。
所以,要实现异步的爬虫方式的话,需要用到多协程。在它的帮助下,我们能实现前面提到的“让多个爬虫替我们干活”。
三、怎么使用多协程
我们先用之前同步的爬虫方式爬取这8个网站,并统计一下所用的时间,然后等下再和gevent异步爬取做一个对比。
import requests, time
# 记录程序开始时间
start = time.time()
# 将8个网址封装成列表
url_list = [
'https://www.baidu.com/',
'https://www.sina.com.cn/',
'http://www.sohu.com/',
'https://www.qq.com/',
'https://www.163.com/',
'http://www.iqiyi.com/',
'https://www.tmall.com/',
'http://www.ifeng.com/']
# 遍历url_list
for url in url_list:
requests.get(url)
# 计算程序运行时间
time_taken = time.time()-start
print(time_taken)
# 输出结果:
13.047908067703247
程序运行后,你会看到同步的爬虫方式,是依次爬取网站,并等待服务器响应(状态码为200表示正常响应)后,才爬取下一个网站。比如第一个先爬取了百度的网址,等服务器响应后,再去爬取新浪的网址,以此类推,直至全部爬取完毕。程序运行了13秒多。
如果运用多协程来运行程序又会怎样呢?需要运用到第三方库gevent。
- Windows系统:pip install gevent
- Mac系统:pip3 install gevent
# 从geven库导入monkey模块
from gevent import monkey
# monkey.path_all()能把程序变成协作式运行,帮助程序实现异步
monkey.patch_all()
# 导入gevent,requests,time
import gevent
import requests
import time
# 记录程序开始时间
start = time.time()
# 将8个网址封装成列表
url_list = [
'https://www.baidu.com/',
'https://www.sina.com.cn/',
'http://www.sohu.com/',
'https://www.qq.com/',
'https://www.163.com/',
'http://www.iqiyi.com/',
'https://www.tmall.com/',
'http://www.ifeng.com/']
# 定义一个crawler函数
def crawler(url):
response = requests.get(url)
print(url, time.time() - start, response.status_code)
# 创建空的任务列表
task_list = []
# 遍历url_list
for url in url_list:
# 使用gevent.spawn创建任务
task = gevent.spawn(crawler, url)
task_list.append(task)
# 执行任务列表中的所有任务,让爬虫爬取网站
gevent.joinall(task_list)
# 记录程序结束时间
end = time.time()
print(end - start)
# 输出结果:
https://www.163.com/ 0.7550866603851318 200
https://www.baidu.com/ 0.8124599456787109 200
http://www.ifeng.com/ 1.165616750717163 200
http://www.sohu.com/ 1.745849847793579 200
https://www.qq.com/ 1.8639347553253174 200
https://www.tmall.com/ 2.0335497856140137 200
https://www.sina.com.cn/ 2.3294148445129395 200
http://www.iqiyi.com/ 2.5574638843536377 200
2.5576677322387695
程序运行后,输出了网址、每个请求运行的时间、状态码和爬取8个网站最终所用时间。
通过每个请求运行的时间,我们能知道:爬虫用了异步的方式抓取了8个网站,因为每个请求完成的时间并不是按着顺序来的。比如在我测试运行这个代码的时候,最先爬取到的网站是网易,接着是百度,并不是百度、新浪……的顺序。
每个请求完成时间之间的间隔都非常短,你可以看作这些请求几乎是“同时”发起的。
通过对比同步和异步爬取最终所花的时间,用多协程异步的爬取方式,确实比同步的爬虫方式速度更快。
其实,我们案例爬取的数据量还比较小,不能直接体现出更大的速度差异。如果爬的是大量的数据,运用多协程会有更显著的速度优势。
from gevent import monkey
从gevent库里导入了monkey模块,这个模块能将程序转换成可异步的程序。
mokney.path_all()
monkey.patch_all(),它的作用就是让程序变成是异步模式。
def crawler(url):
response = request. get(url)
print(url,time.time()-start,response.status_code)
我们定义了一个crawler函数,只要调用这个函数,它就会执行【用requests.get()爬取网站】和【打印网址、请求运行时间、状态码】这两个任务。
task = gevent.spawn(crawler,url)
因为gevent只能处理gevent的任务对象,不能直接调用普通函数,所以需要借助gevent.spawn()来创建任务对象。
这里需要注意一点:gevent.spawn()的参数需为要调用的函数名及该函数的参数。比如,gevent.s