Python协程asyncio极简入门与爬虫实战
作者:读者投稿
来源:早起Python
在了解了Python并发编程的多线程和多进程之后,我们来了解一下基于asyncio的异步IO编程协程01、协程简介
协程(Coroutine)又称微线程、纤程,协程不是进程或线程,其执行过程类似于Python函数调用,Python的asyncio模块实现的异步IO编程框架中,协程是对使用async关键字定义的异步函数的调用;
一个进程包含多个线程,类似于一个人体组织有多种细胞在工作,同样,一个程序可以包含多个协程。多个线程相对独立,线程的切换受系统控制。同样,多个协程也相对独立,但是其切换由程序自己控制。02、一个简单例子
我们来使用一个简单的例子了解协程,首先看看下面的代码:importtimedefdisplay(num):time。sleep(1)print(num)fornuminrange(10):display(num)
很容易看得懂,程序会输出0到9的数字,每隔1秒中输出一个数字,因此整个程序的执行需要大约10秒时间。值得注意的是,因为没有使用多线程或多进程(并发),程序中只有一个执行单元(只有一个线程在执行),而time。sleep(1)的休眠操作会让整个线程停滞1秒钟,
对于上面的代码来说,在这段时间里面CPU是闲置的没有做什么事情。
我们再来看看使用协程会发生什么:importasyncioasyncdefdisplay(num):在函数前使用async关键字,变成异步函数awaitasyncio。sleep(1)print(num)
异步函数不同于普通函数,调用普通函数会得到返回值,而调用异步函数会得到一个协程对象。我们需要将协程对象放到一个事件循环中才能达到与其他协程对象协作的效果,因为事件循环会负责处理子程序切换的操作。
简单的说就是让阻塞的子程序让出CPU给可以执行的子程序。
03、基本概念
异步IO是指程序发起一个IO操作(阻塞等待)后,不用等IO操作结束,可以继续其它操作;做其他事情,当IO操作结束时,会得到通知,然后继续执行。异步IO编程是实现并发的一种方式,适用于IO密集型任务
Python模块asyncio提供了一个异步编程框架,全局的流程图大致如下:
下面对每个函数都从代码层面进行介绍
async:定义一个方法(函数),这个方法在后面的调用中不会被立即执行而是返回一个协程对象;asyncdeftest():print(hello异步)test()调用异步函数输出:RuntimeWarning:coroutinetestwasneverawaited
coroutine:协程对象,也可以将协程对象添加到时间循环中,它会被事件循环调用;asyncdeftest():print(hello异步)ctest()调用异步函数,得到协程对象cprint(c)输出:coroutineobjecttestat0x0000023FD05AA360
eventloop:事件循环,相当于一个无限循环,可以把一些函数添加到这个事件中,函数不会立即执行,而是满足某些条件的时候,函数就会被循环执行;asyncdeftest():print(hello异步)ctest()调用异步函数,得到协程对象cloopasyncio。geteventloop()创建事件循环loop。rununtilcomplete(c)把协程对象丢给循环,并执行异步函数内部代码输出:hello异步
await:用来挂起阻塞方法的执行;importasynciodefrunning1():asyncdeftest1():print(1)awaittest2()print(2)asyncdeftest2():print(3)print(4)loopasyncio。geteventloop()loop。rununtilcomplete(test1())ifnamemain:running1()
输出:
task:任务,对协程对象的进一步封装,包含任务的各个状态;asyncdeftest():print(hello异步)ctest()调用异步函数,得到协程对象cloopasyncio。geteventloop()创建事件循环taskloop。createtask(c)创建task任务print(task)loop。rununtilcomplete(task)执行任务输出:Taskpendingcorotest()runningatD:xxxx。pytaskhello异步异步函数内部代码一样执行
future:代表以后执行或者没有执行的任务,实际上和task没有本质区别;这里就不做代码展示;
首先使用一般方式方法创建一个函数:deffunc(url):print(f正在对{url}发起请求:)print(f请求{url}成功!)func(www。baidu。com)
结果如下所示:正在对www。baidu。com发起请求:请求www。baidu。com成功04、基本操作
创建协程对象
通过async关键字定义一个异步函数,调用异步函数返回一个协程对象。
异步函数就是在函数执行过程中挂起,去执行其他异步函数,等待挂起条件(time。sleep(n))消失后,再回来执行,接着我们来修改上述代码:asyncdeffunc(url):print(f正在对{url}发起请求:)print(f请求{url}成功!)func(www。baidu。com)
结果如下:RuntimeWarning:coroutinefuncwasneverawaited
这就是之前提到的,使用async关键字使得函数调用得到了一个协程对象,协程不能直接运行,需要把协程加入到事件循环中,由后者在适当的时候调用协程;
创建task任务对象
task任务对象是对协程对象的进一步封装;importasyncioasyncdeffunc(url):print(f正在对{url}发起请求:)print(f请求{url}成功!)cfunc(www。baidu。com)函数调用的写成对象cloopasyncio。geteventloop()创建一个时间循环对象taskloop。createtask(c)loop。rununtilcomplete(task)注册加启动print(task)
结果如下:正在对www。baidu。com发起请求:请求www。baidu。com成功!Taskfinishedcorofunc()done,definedatD:datatest。py:10resultNone
future的使用
前面我们提及到future和task没有本质区别asyncdeffunc(url):print(f正在对{url}发起请求:)print(f请求{url}成功!)cfunc(www。baidu。com)函数调用的写成对象cloopasyncio。geteventloop()创建一个时间循环对象futuretaskasyncio。ensurefuture(c)print(futuretask,未执行)loop。rununtilcomplete(futuretask)注册加启动print(futuretask,执行完了)
结果如下:Taskpendingcorofunc()runningatD:datatest。py:10未执行正在对www。baidu。com发起请求:请求www。baidu。com成功!Taskfinishedcorofunc()done,definedatD:datatest。py:10resultNone执行完了
await关键字的使用
在异步函数中,可以使用await关键字,针对耗时的操作(例如网络请求、文件读取等IO操作)进行挂起,比如异步程序执行到某一步时需要很长时间的等待,就将此挂起,去执行其他异步函数importasyncio,timeasyncdefdosomework(n):使用async关键字定义异步函数print(等待:{}秒。format(n))awaitasyncio。sleep(n)休眠一段时间return{}秒后返回结束运行。format(n)starttimetime。time()开始时间corodosomework(2)loopasyncio。geteventloop()创建事件循环对象loop。rununtilcomplete(coro)print(运行时间:,time。time()starttime)
运行结果如下:等待:2秒运行时间:2。00131201744079605、多任务协程
任务(Task)对象用于封装协程对象,保存了协程运行后的状态,使用rununtilcomplete()方法将任务注册到事件循环;
如果我们想要使用多任务,那么我们就需要同时注册多个任务的列表,可以使用rununtilcomplete(asyncio。wait(tasks)),
这里的tasks,表示一个任务序列(通常为列表)
注册多个任务也可以使用rununtilcomplete(asyncio。gather(tasks))importasyncio,timeasyncdefdosomework(i,n):使用async关键字定义异步函数print(任务{}等待:{}秒。format(i,n))awaitasyncio。sleep(n)休眠一段时间return任务{}在{}秒后返回结束运行。format(i,n)starttimetime。time()开始时间tasks〔asyncio。ensurefuture(dosomework(1,2)),asyncio。ensurefuture(dosomework(2,1)),asyncio。ensurefuture(dosomework(3,3))〕loopasyncio。geteventloop()loop。rununtilcomplete(asyncio。wait(tasks))fortaskintasks:print(任务执行结果:,task。result())print(运行时间:,time。time()starttime)
运行结果如下:任务1等待:2秒任务2等待:1秒任务3等待:3秒任务执行结果:任务1在2秒后返回结束运行任务执行结果:任务2在1秒后返回结束运行任务执行结果:任务3在3秒后返回结束运行运行时间:3。002867698669433606、实战爬取LOL皮肤
首先打开官网:
可以看到英雄列表,这里就不详细展示了,我们知道一个英雄有多个皮肤,我们的目标就是爬取每个英雄的所有皮肤,保存到对应的文件夹里;
打开一个英雄的皮肤页面,如下所示:
黑暗之女,下面的小兔对应的就是该隐兄弟皮肤,然后通过查看network发现对应的皮肤数据在js文件里;
然后我们发现了英雄皮肤存放的url链接规律:url1https:game。gtimg。cnimageslolactimgjshero1。jsurl2https:game。gtimg。cnimageslolactimgjshero2。jsurl3https:game。gtimg。cnimageslolactimgjshero3。js
我们发现只有id参数是动态构造的,规律是:https:game。gtimg。cnimageslolactimgjshero{}。js。format(i)
但是这个id只有前面的是按顺序的,在展示全部英雄的页面找到对应英雄的id,
这里截取的是最后几个英雄的id,所以要全部爬取,需要先设置好id,由于前面的是按顺序的,这里我们就爬取前20个英雄的皮肤;
1。获取英雄皮肤ulr地址:
前面的英雄id是按顺序的所有可以使用range(1,21),动态构造url;defgetpage():pageurls〔〕foriinrange(1,21):urlhttps:game。gtimg。cnimageslolactimgjshero{}。js。format(i)print(url)pageurls。append(url)returnpageurls
2。请求每一页的url地址
并对网页进行解析获取皮肤图片的url地址:defgetimg():imgurls〔〕pageurlsgetpage()forpageurlinpageurls:resrequests。get(pageurl,headersheaders)resultres。content。decode(utf8)resdictjson。loads(result)skinsresdict〔skins〕forheroinskins:item{}item〔name〕hero〔heroName〕item〔skinname〕hero〔name〕ifhero〔mainImg〕:continueitem〔imgLink〕hero〔mainImg〕print(item)imgurls。append(item)returnimgurls
说明:resdictjson。loads(result):将得到的json格式字符串转化为字典格式;heroName:英雄名字(这个一定是一样的,方便我们后面根据英雄名创建文件夹);name:表示完整的名字,包括皮肤名(这个一定是不一样的)有的mainImg是空的,我们需要进行一个判断;
3。创建协程函数
这里我们根据英雄名创建文件夹,然后就是注意图片的命名,不要忘记,目录结构确立asyncdefsaveimg(index,imgurl):path皮肤imgurl〔name〕ifnotos。path。exists(path):os。makedirs(path)contentrequests。get(imgurl〔imgLink〕,headersheaders)。contentwithopen(。皮肤imgurl〔name〕imgurl〔skinname〕str(index)。jpg,wb)asf:f。write(content)
主函数:defmain():loopasyncio。geteventloop()imgurlsgetimg()print(len(imgurls))tasks〔saveimg(img〔0〕,img〔1〕)forimginenumerate(imgurls)〕try:loop。rununtilcomplete(asyncio。wait(tasks))finally:loop。close()
4。程序运行ifnamemain:starttime。time()main()endtime。time()print(endstart)
运行结果:
下载233张图花费了42s,可以看到速度还行,文件目录结果如下:
与requests对比
异步爬取图片之后,我们有必要使用requests去进行同步数据爬取,进行效率对比,所以在原有代码的基础上进行修改,这里直接略过,思路都是一样的,这是把一部当中的事件循环替换成循环即可:imgurlsgetimg()print(len(imgurls))fori,imgurlinenumerate(imgurls):saveimg(i,imgurl)
我们可以看到,使用协程的速度要比requests快了一些。
以上就是本文的全部内容,感兴趣的读者可以自己动手敲一遍代码~
智能锁保养攻略你知道吗?智能锁保养得好,使用寿命延长两三年完全没问题。不要以为智能锁不需要保养,里面机械部件,使用不当会容易造成损坏的。俗话说,保养是老样子,不保养是样子老。人如此,锁亦然。……
全自动洗碗机的优势洗碗机操作简单,易于理解。至于洗碗机洗碗是否够干净,高温水,高达6080度,加上各种各样的高压喷淋冲洗和消毒、烘干,恐怕手洗难以达到吧?自动洗碗机优势:1。节省劳动……
Hystrix高级属性配置在几篇的讲解和操作中,我们已经知道了服务与服务之间是通过Hystrix来实现服务熔断与降级的,Hystrix官网上除了介绍Hystrix外,还列出了一些Hystrix高级属性配……
风靡游戏中的活化石,大众Type2将出现油电混合动力版?【新车资讯】大吉大利,今晚吃鸡!一款由名不见经传的小公司蓝洞出品的绝地求生游戏,火遍全球!在经过腾讯接手开发了手游版和平精英后,人气更是居高不下。在吃鸡游戏中,载具的运用……
为什么有些人对外人很客气,对自己的亲人却很苛刻,这是什么心理怎么说呢,我觉得这其实是一种欺软怕硬的心理。比如,老舍一篇短篇小说里的主人公庄亦雅,此人是个与人为善的好人、老实人至少不是坏人。他在越来越沉迷字画的时候,手头逐渐拮据起来……
基于DSMA架构平台打造,起售9。39万,东风风神奕炫MAX在入门级紧凑家用轿车市场,9月1号正式上市的东风风神奕炫MAX吸引了很多年轻人,官方指导售价在9。3912。59万之间,一共推出6款车型,其中5款是燃油版,一款是混动车型。新款……
华谊兄弟对上非誠勿擾婚介所?三战三胜《非诚勿扰》这部电影相信很多人就算没有看过也是听过的,这部电影是由冯小刚执导,葛优、舒淇、胡可等人主演的一部爱情喜剧片,讲述了男主秦奋的天才发明被投资人高价买断,一夜暴富的他开……
电源能选长城巨龙吗?电源选购矿龙问题分析导读我相信很多和我一样的小伙伴,电脑只要够用就行,能不换就不换,能在省钱的基础上升级那就更不用说了,那么在之前的矿难当中,肯定有的小伙伴对长城巨龙电源毫不陌生,甚至都有点……
扣丁学堂告诉你适合编程入门的语言怎么选现在是互联网时代,从事互联网开发行业的人们不论是薪资还是工作环境都是很让人羡慕的,因此有不少的小伙伴想要学习编程语言进入互联网行业,本篇文章扣丁学堂小编告诉你适合编程入门的语言……
提升生活品质来苏宁嗨购五一厨电美容手机一网打尽五一长假马上就要到了,相信很多人已经拟定好计划了,有些人可能会宅在家中,有些人可能去旅游,有些人可能回家陪父母为了满足大家不同的计划,苏宁易购特意启动了嗨购五一,目前已经进入爆……
贵阳市连续七年举办大数据电子商务职工职业技能大赛12月8日,以智领未来数说贵阳为主题的贵阳市第七届大数据电子商务职工职业技能大赛开赛。本届大赛由贵阳市总工会、贵阳市大数据发展管理局联合主办,旨在积极推进落实《新时期贵州……
试驾体验上汽大众ID。4X一场科幻ID的探秘之旅大众ID。4是大众全新MEB电动车平台的首款电动车型,ID。4X(参数询价)作为上汽大众的首款电动紧凑型SUV,表现如何,这次就开着ID。4X来一场环球影城的探秘之旅,来和大家……