协程主要用于多任务开发,任务之间来回切换,把一个方法作为一个任务,这样刚好符合爬虫的需求,请求是一个任务,解析是一个任务,保存是一个任务。
在python中的标准库,就有这么一个模块,叫做asyncio,从这个名字我们就知道,异步的io,就是为了解决大量的io操作提高效率而开发的。
import asyncio
"""
协程是异步任务,
并发是指一次处理多件事情
并行是指一次多件事
这个包使用事件循环驱动的协程 实现并发
所以说这是一个协程概念的实现方法库,他的英文名字叫郁金香
异步操作前面就得加上async
"""
# 一个协程函数
async def fun():
print("我是一个函数")
return "fun()"
def fun1():
return "fun1"
def run1(f):
event_loop = asyncio.get_event_loop()
# event_loop 执行协程对象,直到任务执行完毕
event_loop.run_until_complete(f)
print("简单封装")
if __name__ == "__main__":
# 执行协程需要一个 event_loop对象
f = fun() # 协程对象
print(f) # <coroutine object fun at 0x0000020AE97D57C0>
f1 = fun1()
print(f1) # fun1
# 下面三个部分只能一个部分执行,三个部分执行会报错
# --------------------------------------------------
# 拿到事件循环对象
# event_loop = asyncio.get_event_loop()
# # # event_loop 执行协程对象,直到任务执行完毕
# event_loop.run_until_complete(f)
# # 关闭事件循环
# event_loop.close()
# ----------------------------------------------------
# 简便写法,这个run方法里面就有上面两个操作
# 这个run方法里面传递的是一个异步任务(协程对象),是对上面两个操作的封装
asyncio.run(f) # 我是一个函数
# ----------------------------------------------------
# 简单封装
# run1(f)
IO容易引起线程阻塞,为了解决阻塞搞出来的东西,不涉及到线程切换,cpu切换的是任务,所以切换的时间也给节省了,在一个线程里面,任务的来回切换,这个是有程序来控制切换,而线程是由操作系统来控制的,要比线程池高很多很多。
返回值处理
在多线程的使用中我们有时候也会需要使用返回值,asyncio模块同样也提供了返回值的处理方式
具体如下看代码
import asyncio
async def faker1():
print("任务1开始")
await asyncio.sleep(1)
print("任务1完成")
return "任务1结束"
async def faker2():
print("任务2开始")
await asyncio.sleep(2)
print("任务2完成")
return "任务2结束"
async def faker3():
print("任务3开始")
await asyncio.sleep(3)
print("任务3完成")
return "任务3结束"
async def main():
# 生成任务列表
tasks = [
asyncio.create_task(faker3()),
asyncio.create_task(faker1()),
asyncio.create_task(faker2()),
]
# 方案一, 用wait, 返回的结果在result中
result, pending = await asyncio.wait(tasks)
for r in result:
print(r.result())
# 方案二, 用gather, 返回的结果在result中, 结果会按照任务添加的顺序来返回数据
# return_exceptions如果任务在执行过程中报错了. 返回错误信息.
result = await asyncio.gather(*tasks, return_exceptions=True)
for r in result:
print(r)
if __name__ == '__main__':
asyncio.run(main())
下面是一个下载图片的案例
原先的request模块和io操作都是同步的模块,所以我们需要异步的请求和异步的io操作,这里就有两个模块,aiofiles ,aiohttp
# aiohttp模块 aiofiles模块
import asyncio
import aiofiles
import aiohttp
"""
asyncio模块只支持tcp,udp协议
所以需要执行异步的http请求的话只能使用第三方http模块 aiohttp
打算交给asyncio处理的协程,需要使用asnyc装饰
"""
# 下载任务
async def download(url):
print("开始下载:", url)
file_name = '../data/img/' + url[29:35]
# 相当于request,因为这个req模块是同步的不是异步的,所以需要aiohttp
# async这个修饰表示这个代码里面的代码是异步的,可以等待
async with aiohttp.ClientSession() as session:
# 发送网络请求
async with session.get(url) as resp:
# await resp.text()
content = await resp.content.read() # =>resp.content()
# 写文件,
# 正因为下面有await这个修饰,所以这里需要这个async修饰才能当io时候交给主程序
async with aiofiles.open(file_name + ".jpg", mode="wb") as f:
# 阻塞的操作通过协程实现,通过await把职责交给协程,在流畅的python当中这里使用的是yield from
# 这里f.write(content)会有io操作会阻塞,所以需要使用await,当发生io时候,把程序的控制权交给主程序,去执行其他代码
# 等这里的io发送完毕后在回复这个协程
await f.write(content)
print("下载完毕:", url)
async def main():
url_lit = [
"https://img1.baidu.com/it/u=2813501359,388694820&fm=253&fmt=auto&app=138&f=JPEG?w=440&h=591",
"https://img1.baidu.com/it/u=429699237,3830680416&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=718",
"https://img0.baidu.com/it/u=3304588560,3276370823&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=677"
]
# 任务列表
tasks = []
# 将每个url创建成一个任务,添加到tasks列表中
# task对象用于驱动协程
for url in url_lit:
t = asyncio.create_task(download(url))
tasks.append(t)
# wait()里面需要的是task对象,wait方法是一个异步的不会阻塞的协程,协程函数,虽然函数的名称是await
# 等待传递给他的所有协程执行完毕后结束,
# 流畅的python书里面的原话是 ”asyncio.wait(...) 参数是由一个协程构造的可迭代对象,wait会把个协程封装成一个Task对象
# “
await asyncio.wait(tasks)
if __name__ == "__main__":
# 简便写法,这个run方法里面就有上面两个操作
# 这个run方法里面传递的是一个异步任务(协程对象)
asyncio.run(main())
欢迎关注作者公众号