asyncio和aiohttp的用法


一、asyncio

asyncio 不是多进程, 也不是多线程, 单单是一个线程, 但是是在 Python 的功能间切换着执行. 切换的点用 await 来标记, 能够异步的功能用 async 标记.

asyncio里面主要有4个需要关注的基本概念

Eventloop
Eventloop可以说是asyncio应用的核心,是中央总控。Eventloop实例提供了注册、取消和执行任务和回调的方法。

把一些异步函数(就是任务,Task,一会就会说到)注册到这个事件循环上,事件循环会循环执行这些函数(但同时只能执行一个),当执行到某个函数时,如果它正在等待I/O返回,事件循环会暂停它的执行去执行其他的函数;当某个函数完成I/O后会恢复,下次循环到它的时候继续执行。因此,这些异步函数可以协同(Cooperative)运行:这就是事件循环的目标

Coroutine
协程(Coroutine)本质上是一个函数,特点是在代码块中可以将执行权交给其他协程

Future
接着说Future,它代表了一个「未来」对象,异步操作结束后会把最终结果设置到这个Future对象上。Future是对协程的封装,不过日常开发基本是不需要直接用这个底层Future类的

Task

1. 用法

import asyncio

async def a():
    print('Suspending a')
    await asyncio.sleep(0)
    print('Resuming a')

async def b():
    print('In b')

async def main():
    await asyncio.gather(a(), b())

if __name__ == '__main__':
    asyncio.run(main())

"""
Start job  1
Start job  2
Job  1  takes  1  s
Job  2  takes  2  s
Async total time :  2.001495838165283
"""

这里面有4个重要关键点:

  1. 协程要用async def声明,Python 3.5时的装饰器写法已经过时,我就不列出来了。
  2. asyncio.gather用来并发运行任务,在这里表示协同的执行a和b2个协程
  3. 在协程a中,有一句await asyncio.sleep(0),await表示调用协程,sleep 0并不会真的sleep(因为时间为0),但是却可以把控制权交出去了。
  4. asyncio.run是Python 3.7新加的接口,要不然你得这么写:
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
loop.close()

多种使用方式

async def a():
    print('Suspending a')
    await asyncio.sleep(3)
    print('Resuming a')

async def b():
    print('Suspending b')
    await asyncio.sleep(1)
    print('Resuming b')

async def c1():
    await asyncio.gather(a(), b())

async def c2():
    await asyncio.wait([a(), b()])

async def c3():
    task1 = asyncio.create_task(a())
    task2 = asyncio.create_task(b())
    await task1
    await task2

async def c4():
    task = asyncio.create_task(b())
    await a()
    await task
    
async def c5():
    task = asyncio.ensure_future(b())
    await a()
    await task

async def c6():
    loop = asyncio.get_event_loop()
    task = loop.create_task(b())
    await a()
    await task
    
def show_perf(func):
    print('*' * 20)
    start = time.perf_counter()
    asyncio.run(func())
    print(f'{func.__name__} Cost: {time.perf_counter() - start}')

结果都是3秒。
asyncio.create_task相当于把协程封装成Task, 是Python 3.7新增的高阶API

2. asyncio.gather 与 asyncio.wait

差异

asyncio.wait : 写法 await asyncio.wait(task_list)
返回两个值:done 和 pending,done 为已完成的协程 Task,pending 为超时未完成的协程 Task,需通过 future.result 调用 Task 的 result;
asyncio.gather : 写法 await asyncio.gather(*task_list)
返回的是所有已完成 Task 的 result,gather的意思是「搜集,也就是能够收集协程的结果 , 它会按输入协程的顺序保存的对应协程的执行结果;

In : done, pending = await asyncio.wait([a(), b()])
Suspending b
Suspending a
Resuming b
Resuming a

In : done
Out:
{<Task finished coro=<a() done, defined at <ipython-input-5-5ee142734d16>:1> result='A'>,
 <Task finished coro=<b() done, defined at <ipython-input-5-5ee142734d16>:8> result='B'>}

In : pending
Out: set()

In : task = list(done)[0]

In : task
Out: <Task finished coro=<b() done, defined at <ipython-input-5-5ee142734d16>:8> result='B'>

In : task.result()
Out: 'B'

asyncio.wait支持一个接收参数return_when,在默认情况下,asyncio.wait会等待全部任务完成(return_when=‘ALL_COMPLETED’),它还支持FIRST_COMPLETED(第一个协程完成就返回)和FIRST_EXCEPTION(出现第一个异常就返回)

async def a():
    print('Suspending a')
    await asyncio.sleep(3)
    print('Resuming a')
    return 'A'


async def b():
    print('Suspending b')
    await asyncio.sleep(1)
    print('Resuming b')
    return 'B'
    
done, pending = await asyncio.wait([a(), b()], return_when=asyncio.tasks.FIRST_COMPLETED)
Suspending a
Suspending b
Resuming b
Resuming b

In : done
Out: {<Task finished coro=<b() done, defined at <ipython-input-5-5ee142734d16>:8> result='B'>}

In : pending
Out: {<Task pending coro=<a() running at <ipython-input-5-5ee142734d16>:3> wait_for=<Future pending cb=[<TaskWakeupMethWrapper object at 0x108065e58>()]>>}

这次只有协程b完成了,协程a还是pending状态。

在大部分情况下,用asyncio.gather是足够的,如果你有特殊需求,可以选择asyncio.wait,举2个例子:

  1. 需要拿到封装好的Task,以便取消或者添加成功回调等
  2. 业务上需要FIRST_COMPLETED/FIRST_EXCEPTION即返回的

二、aiohttp

aiohttp 是一个Python 提供异步HTTP 客户端/服务端编程, 基于asyncio的异步库, 它既提供了服务端 , 又提供了客户端. 客户端就是用来发起请求 , 类似于 requests 来发起一个 HTTP 请求然后获得响应 , 但 requests 发起的是同步的网络请求, 而 aiohttp 则发起的是异步的。

1. 用法

import aiohttp

timeout = aiohttp.ClientTimeout(total=60)
cookies = {'cookies_are': 'working'}
headers = {'User-Agent': 'your agent'}

async def aoi_main():
	async with aiohttp.ClientSession(headers=headers) as sess:    # 自定义headers
    # async with aiohttp.ClientSession(timeout=timeout) as sess:  # 设置请求超时
    # async with aiohttp.ClientSession(cookies=cookies) as sess:  # 自定义cookie
        async with sess.get('http://httpbin.org/cookies') as resp:    # SSL验证警告问题,ssl=False
        	assert resp.status == 200
            print(resp.status)
            print(await resp.text())

scrape_index_tasks = [asyncio.ensure_future(aoi_main()) for _ in range(300)]
loop = asyncio.get_event_loop()
tasks = asyncio.gather(*scrape_index_tasks)
loop.run_until_complete(tasks)

小知识点: assert用于测试一个条件是否满足, 不满足时会抛出 AssertionError错误信息
加代理

# 第一种
async with aiohttp.ClientSession() as session:
    proxy_auth = aiohttp.BasicAuth('user', 'pass')
    async with session.get("http://python.org", proxy="http://proxy.com", proxy_auth=proxy_auth) as resp:
        print(resp.status)

# 第二种
session.get("http://python.org", proxy="http://user:pass@some.proxy.com")

参考: 文章1 文章2

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值