python 是一个脚本语言, 里面编写的代码是从头一行一行的执行,所以一般我们要等到它全部执行完,才能拿到我们要的数据。
一个爬虫爬取大量数据要爬很久,那我们能不能让多个爬虫一起爬取?
爬虫每发起一个请求,都要等服务器返回响应后,才会执行下一步。而很多时候,由于网络不稳定,加上服务器自身也需要响应时间,导致爬虫会浪费大量时间在等待上,这也是爬取大量数据时,爬虫的速度会比较慢的原因。
每台计算机都靠着CPU(中央处理器)干活,单核CPU的计算机在处理多任务时,会出现一个问题:每个任务都要抢占CPU,执行完了一个任务才开启下一个任务。CPU毕竟只有一个,这会让计算机处理的效率很低。
为了解决这个问题,一种非抢占式的异步技术创造了出来,这种方式叫多协程。
多协程——gevent库
import requests,time
#导入requests和time
start = time.time()
#记录程序开始时间
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/']
#把8个网站封装成列表
for url in url_list:
#遍历url_list
r = requests.get(url)
#用requests.get()函数爬取网站
print(url,r.status_code)
#打印网址和抓取请求的状态码
end = time.time()
#记录程序结束时间
print(end-start)
#end-start是结束时间减去开始时间,就是最终所花时间。
#最后,把时间打印出来。
使用多协程的方式:
from gevent import monkey
#从gevent库里导入monkey模块。
monkey.patch_all()
#monkey.patch_all()能把程序变成协作式运行,就是可以帮助程序实现异步。
import gevent,time,requests
#导入gevent、time、requests。
start = time.time()
#记录程序开始时间。
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/']
#把8个网站封装成列表。
def crawler(url):
#定义一个crawler()函数。
r = requests.get(url)
#用requests.get()函数爬取网站。
print(url,time.time()-start,r.status_code)
#打印网址、请求运行时间、状态码。
tasks_list = [ ]
#创建空的任务列表。
for url in url_list:
#遍历url_list。
task = gevent.spawn(crawler,url)
#用gevent.spawn()函数创建任务。
tasks_list.append(task)
#往任务列表添加任务。
gevent.joinall(tasks_list)
#执行任务列表里的所有任务,就是让爬虫开始爬取网站。
end = time.time()
#记录程序结束时间。
print(end-start)
#打印程序最终所需时间。
代码注释:
从gevent库里导入了monkey模块,这个模块能将程序转换成可异步的程序。monkey.patch_all(),它的作用其实就像你的电脑有时会弹出“是否要用补丁修补漏洞或更新”一样。它能给程序打上补丁,让程序变成是异步模式,而不是同步模式。它也叫“猴子补丁”。
我们要在导入其他库和模块前,先把monkey模块导入进来,并运行monkey.patch_all()。
因为gevent只能处理gevent的任务对象,不能直接调用普通函数,所以需要借助gevent.spawn()来创建任务对象。
gevent.spawn()的参数需为要调用的函数名及该函数的参数。
比如,gevent.spawn(crawler,url)就是创建一个执行crawler函数的任务,参数为crawler函数名和它自身的参数url。
调用gevent库里的joinall方法,能启动执行所有的任务。gevent.joinall(tasks_list)就是执行tasks_list这个任务列表里的所有任务,开始爬取。
Queue模块:
用协程技术和队列爬取8个网站的完整代码:
from gevent import monkey
#从gevent库里导入monkey模块。
monkey.patch_all()
#monkey.patch_all()能把程序变成协作式运行,就是可以帮助程序实现异步。
import gevent,time,requests
#导入gevent、time、requests
from gevent.queue import Queue
#从gevent库里导入queue模块
start = time.time()
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/']
work = Queue()
#创建队列对象,并赋值给work。
for url in url_list:
#遍历url_list
work.put_nowait(url)
#用put_nowait()函数可以把网址都放进队列里。
#put_nowait()就是如果队列满了就不用等待队列释放空间
#直接结束往队列插入数据,往下继续进行
def crawler():
while not work.empty():
#当队列不是空的时候,就执行下面的程序。
url = work.get_nowait()
#用get_nowait()函数可以把队列里的网址都取出。
r = requests.get(url)
#用requests.get()函数抓取网址。
print(url,work.qsize(),r.status_code)
#打印网址、队列长度、抓取请求的状态码。
tasks_list = [ ]
#创建空的任务列表
for x in range(2):
#相当于创建了2个爬虫
task = gevent.spawn(crawler)
#用gevent.spawn()函数创建执行crawler()函数的任务。
tasks_list.append(task)
#往任务列表添加任务。
gevent.joinall(tasks_list)
#用gevent.joinall方法,执行任务列表里的所有任务,就是让爬虫开始爬取网站。
end = time.time()
print(end-start)
之所以说for x in range(2):相当于创建了2个爬虫,那是因为当执行task=gevent.spawn(crawler)
,创建任务时,并不是真的去执行crawler函数,而是先创建任务,可以看出这个地方是它创建了两个crawler任务,创建完任务,并添加到任务列表之后,再用gvent.joinall(tasks_list)
来开始执行任务,这个时候才会到crawler函数里,去遍历存有网页链接的列表,并一个个取出来,队列中的同一个元素不会被取出两次,所以当一个链接被第一个爬虫取走时,第个爬虫过来只能取下一个。所以两个爬虫交叉执行
两个爬虫交叉取出队列中的元素:
我们创建了两只可以异步爬取的爬虫。它们会从队列里取走网址,执行爬取任务。一旦一个网址被一只爬虫取走,另一只爬虫就取不到了,另一只爬虫就会取走下一个网址。直至所有网址都被取走,队列为空时,爬虫就停止工作。