安装:pip install gevent

协程定义

又名微线程,纤程。在不开辟线程的基础上完成多任务,即在单线程情况下完成多任务,多个任务按照一定顺序交替执行。可认为看到yield关键字就是协程。本质为是个单线程。
协程是实现多任务的一种方法。
生成.spec文件
缺点:无法利用多核资源
优点:提高执行效率————–无需线程的切换开销。
高可用+高扩展性+低成本——一个CPU可支持上万个协程(高并发);
不需要多线程的锁制作——–如生产者-消费者问题。传统方法需要使用两个线程,但如果使用在一个线程中使用关键字yield开辟协程便可解决。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
   import time
def consumer():
r = ''
while True:
n = yield r
if not n:
return
print('[CONSUMER] Consuming %s...' % n)
time.sleep(1)
r = '200 OK'

def produce(c):
c.next()
n = 0
while n < 5:
n = n + 1
print('[PRODUCER] Producing %s...' % n)
r = c.send(n)
print('[PRODUCER] Consumer return: %s' % r)
c.close()

if __name__=='__main__':
c = consumer()
produce(c)

执行结果:
[PRODUCER] Producing 1...
[CONSUMER] Consuming 1...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 2...
[CONSUMER] Consuming 2...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 3...
[CONSUMER] Consuming 3...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 4...
[CONSUMER] Consuming 4...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 5...
[CONSUMER] Consuming 5...
[PRODUCER] Consumer return: 200 OK
注:[代码来源参考](https://www.liaoxuefeng.com/wiki/897692888725344/923057403198272)

协程使用

使用安装:cmd下执行命令:pip install greenlet
使用一个简单例子解释greenlet用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import time,greenlet
def work1():
for i in range(5):
print('---work1-----')
time.sleep(0.5)
##切换协程
g2.switch()
def work2():
for i in range(5):
print('---work2-----')
time.sleep(1)
g1.switch()
if __name__ == "__main__":
g1=greenlet.greenlet(work1)
g2=greenlet.greenlet(work2)
g1.switch()

输出:
---work1-----
---work2-----
---work1-----
---work2-----

greenlet可以实现协程,但需要人工切换,很多场景不适用。因此更多时候我们使用一个更强大的库————Gevent。

Gevent

使用安装:在cmd中输入命令:pip install gevent
Gevent内部封装Greenlet,其原理相当于一个Greenlet遇到IO操作【网络、文件操作】时,自动切换到其他Greenlet,等到这个IO操作完成,在适当时候切换回来执行。

例子使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import gevent
def work(n):
for i in range(n):
##获取当前协程的属性
print(gevent.getcurrent(),i)
g1=gevent.spawn(work,2)
g2=gevent.spawn(work,3)
g1.join()
g2.join()

输出:
<Greenlet at 0x2177fb5ae18: work(2)> 0
<Greenlet at 0x2177fb5ae18: work(2)> 1
<Greenlet at 0x21700338048: work(3)> 0
<Greenlet at 0x21700338048: work(3)> 1
<Greenlet at 0x21700338048: work(3)> 2

由例子可得出程序是先完全执行完g1对象,再执行g2--------原因是在此程序中无IO操作,无需进行协程切换
注:spawn:引发、导致、造成

例子改写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import gevent,time
def work(n):
for i in range(n):
##获取当前协程的属性
print(gevent.getcurrent(),i)
gevent.sleep(1) ##让协程睡眠
g1=gevent.spawn(work,2)
g2=gevent.spawn(work,3)
##阻塞,程序资源不被释放
g1.join()
g2.join()
输出:
<Greenlet at 0x2177fb5ad08: work(2)> 0
<Greenlet at 0x2177fb5abf8: work(3)> 0
<Greenlet at 0x2177fb5ad08: work(2)> 1
<Greenlet at 0x2177fb5abf8: work(3)> 1
<Greenlet at 0x2177fb5abf8: work(3)> 2

可以发现这次实现了协程切换。。。遗憾的是,如果换成time.sleep(1)从本质上看也拥有了IO操作,但并不能触发协程,原因是Gevent无法识别这类IO操作。
解决方法:打补丁---------from gevent import monkey---------官方建议是程序开始就开始打补丁

例如在以上例子在程序开头加上:

1
2
from gevent import monkey
monkey.patch_all()

这样的话把上面例子的gevent.sleep(1)改为time.sleep(1)也是同样结果

想让程序处于阻塞状态,切换协程的两种方法:

1
2
3
4
5
6
7
8
if __name__ == "__main__":
g1=gevent.spawn(work1)
g2=gevent.spawn(work2)
while True:
print("hello word")
time.sleep(1)
#g1.join()
#g2.join()

①使用循环,在循环中进行IO操作 ②join函数阻塞

Gevent+爬虫 实战训练

爬虫流程: 找到资源链接-——获取资源————将资源数据写入到本地文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
from gevent import monkey
monkey.patch_all()
import time,gevent,urllib.request
def drowload_img(img_url,img_name):
try:
print(img_url)
reponse=urllib.request.urlopen(img_url)
with open(img_name,'wb') as img_file:
while True:
img_data=reponse.read(1024)
if img_data:
img_file.write(img_data)
else:
break
except Exception as e:
print("图片下载异常:",e)
else:
print('图片下载成功',img_name)
pass
if __name__ == "__main__":
img_url1 = 'http://p0.so.qhmsg.com/bdr/576__/t013ee81b64eb53f6f5.jpg'
img_url2 = "http://p2.so.qhimgs1.com/bdr/594__/t017ec94ec006189032.jpg"
img_url3 = "http://p3.so.qhmsg.com/bdr/864__/t01f9daf42a666bb408.jpg"
g1=gevent.spawn(drowload_img,img_url1,"img_url1.jpg")
g2=gevent.spawn(drowload_img,img_url2,"img_url2.jpg")
g3=gevent.spawn(drowload_img,img_url3,"img_url3.jpg")
gevent.joinall([g1,g2,g3])
结果:
图片下载成功 img_url1.jpg
图片下载成功 img_url3.jpg
图片下载成功 img_url2.jpg

本次每次写入1024个字节,img_url2.jpg图片最大,根据协程转换,一般会最后下载成功。(每次运行的结果可能不同)

实战升级————爬取360网络图片

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
from gevent import monkey
#打补丁,识别网路延迟
monkey.patch_all()
import gevent,requests#pip install requests
from urllib import request
#下载图片
def download_img(num):
#开启下载
print('start download')
#图片的url地址
url = 'http://image.so.com/zj?ch=beauty&sn=150&listtype=new&temp=1'
#模拟浏览器
headers={
'Referer': 'http://image.so.com/z?ch=beauty',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36'
}
#模拟浏览器访问服务器发送内容
str_data = '''h: beauty
sn: 30
listtype: new
temp: 1'''
#简单的数据清洗
send_data = {}
#进行一行分割
for data in str_data.splitlines():
line_data = data.split(': ')#返回一个列表
if len(line_data) == 2:#如果这个列表当中有两个数据
key,value = line_data# a,b = [1,2] #进行序列解包赋值!
if key and value:#如果两者key和 value都有值 我就进行 搭建send_data
send_data[key] = value
#end_data = {'h':'beauty','sn':'30'}
send_data['sn'] = eval(str(num)+'*'+'30') #eval('3 * 4')#z字典的修改
#requests 这个方法进行网略请求,模拟刘燃气访问服务器返回结果
response = requests.get(url,headers=headers,params=send_data)
#json()方法 转换为python可操作对象{‘a" ;1}
json_data = response.json()['list']
#利用么酷的方法,序列解包赋值
for index,src in enumerate(json_data):
#获取图片url地址
image_url = src['qhimg_url']
try:
#给定本地图片地址
image_name = './360_image/'+image_url[-8:]
#吧网路上的图片下载到本地
request.urlretrieve(url=image_url,filename=image_name)
except Exception as e:
print(e)
else:
#format格式化
print('{} is download'.format(image_name))
print('image is download')
if __name__ == '__main__':
num = int(input('你想要爬取的图片的组:'))
#列表推导式完成协程任务分发
gevent.joinall([gevent.spawn(download_img,i)for i in range(1,num+1)])
本部分主要偏于爬虫方面,需要使用到一定的html和网络通信协议这方面的知识

最后更新: 2019年08月22日 15:26

原始链接: https://LiYuanSh.github.io/2019/04/20/python——Gevent协程/

× 请我吃糖~
打赏二维码