目录
一. 协程的概念
1)什么是协程?
协程指的是⼀个CPU下由程序实现多个任务并发进⾏,它是⼀种异步执行方式即把本来由操作系统控制任务切换+保存状态,直接在应用程序⾥实现了;
2)为何用协程?
把单个线程的io降到最低,最⼤限度地提升单个线程的执行效率;
3)多协程:
多协程就是利⽤程序控制多个爬⾍任务⽆须等待的交替执⾏,从⽽省去等待的时间和切换任务的时间。
二. 同步与异步
就以我们运行爬虫代码为例 :
同步任务: 第⼀个网站爬取完了,代码接收到完成信息了,把完成信息通知给第⼆个网站,第⼆个网站接到通知后,开始准备爬取; 异步任务: ⼏个网站同时预备爬取,⼀个完成了就下⼀个上,两两之间不需要通信, 也就是多个协程任务以异步执⾏的⽅式,可以省去等待和切换任务时的时间,使得执⾏得更快了;
以看电影的例子帮助大家更好的理解这个概念:
我们使用python运⾏代码时,总是⼀个函数结束之后再进⾏下⼀个函数,⼀个任务等着⼀个任务执行完,才开始,等待的过程和切换任务都是非常耗费时间的,这样⼦我 们要爬⼤量数据时,就太麻烦了。 而我们如果使用gevent,就可以让多个任务交替实现,不用等待,不用切换任务,异步执 行。这其实也就是同步和异步的区别。
三. 使用gevent实现多协程
我先把我之前使用过的一个利用gevent实现多线程的代码给大家展示一下:
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)
#打印程序最终所需时间
以下我将代码分开给大家讲解以下
1)插入猴子补丁:
from gevent import monkey
#从gevent库里导入monkey模块。
monkey.patch_all()
#monkey.patch_all()能把程序变成协作式运行,就是可以帮助程序实现异步。
猴子补丁:猴子补丁的主要目的是为了给其他模块补充内容,能够变成协作运行的执行方式;(注意:猴子补丁必须放在被打补丁的前面,如time、requests模块之前)
2)定义⼀个爬虫任务函数:
def crawler(url):
#定义一个crawler()函数。
r = requests.get(url)
#用requests.get()函数爬取网站。
print(url,time.time()-start,r.status_code)
#打印网址、请求运行时间、状态码。
3)利用循环,将⼀个个爬虫函数和函数需要的变量,放进gevent.spawn()中,从而创建任务task,再把任务task放进任务列表中:
for url in url_list:
#遍历url_list。
task = gevent.spawn(crawler,url)
#用gevent.spawn()函数创建任务。
tasks_list.append(task)
#往任务列表添加任务。
4)使用gevent.joinall就可以把任务列表中的任务启动执行:
gevent.joinall(tasks_list)
#执行任务列表里的所有任务,就是让爬虫开始爬取网站。
task爬虫任务开始执⾏,接下来就由程序控制task爬⾍任务异步执行即可。
5)总结:
其实这里只是个模板⽽已,我们只需要创建爬取⽹站列表,创建爬虫函数,将爬虫函数交给 gevent.spawn分配任务,最后由gevent.joinall执行任务。 使用时只需要将修改网站和爬取的代码即可。
四. queue模块
1)为什么需要要用queue模块
假设我们要爬取1000个模块,我们可以用我们刚刚学的gevent语法,我们可以用gevent.spawn()创建1000个爬取任务,再用gevent.joinall()执⾏这1000个任务。但这种⽅法会有问题:执行1000个任务,就是⼀下⼦发起1000次请求,这样⼦的恶意请求, 会拖垮网站的服务器。
所以我们还需要结合队列queue,我们利⽤queue模块来存储任务,让任务都变成⼀条整⻬的队列,就像银⾏窗⼝的排号做法。因为queue其实是⼀种有序的数据结构,可以⽤来存取数据。
2)结合队列queue的方法
老规矩,将结合queue之后的代码再展示一下:
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()函数可以把网址都放进队列里。
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)
- 导入模块queue:
from gevent.queue import Queue #从gevent库里导入queue模块
- 导⼊模块,设置爬取的⽹址,然后就开始创建队列 :
work = Queue() #创建队列对象,并赋值给work。 for url in url_list: #遍历url_list work.put_nowait(url) #用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) #打印网址、队列长度、抓取请求的状态码。
-
通过判断队列是不是空,来判断任务是否完成,以控制是否继续执行任务,最后⼜是熟悉的操作,创建爬虫任务放进gevent.spawn,再使⽤gevent.joinall 将任务执行:
for x in range(2): #相当于创建了2个爬虫 task = gevent.spawn(crawler) #用gevent.spawn()函数创建执行crawler()函数的任务。 tasks_list.append(task) #往任务列表添加任务。 gevent.joinall(tasks_list) #用gevent.joinall方法,执行任务列表里的所有任务,就是让爬虫开始爬取网站。
这里有⼀点要注意:我们创建了多少个爬虫任务,就会开启多少个协程程序,比如这⾥是两次循环,所以创建了两个task = gevent.spawn(crawler) 爬虫任务,通过两个爬虫任务,从队列中获取网址开始爬取内容,直到队列中没有网址url了。
五. 总结
程序员写代码并不是从0开始的,我们也是需要借助多个模板拼接,使得代码能够实现我们的想法,而且也并非默写出来,毕竟学习编程是开卷学习,开卷使用,就比如这个多协程队列爬取内容,我们只需要将url_list修改成我们要爬取的网站。
以上就是今天要讲的内容,本文介绍了爬虫多线程使用的一些相关知识。
欢迎大家留言一起讨论问题~~~
注:本文是参考风变编程课程资料(已经授权)及部分百度资料整理所得,系博主个人整理知识的文章,如有侵权,请联系博主,感谢~