aiohttp 与原生协程 (async/await)
这是一个典型的模式,可以完成你想要做的事情。 (Python 3.7+。)
一个重大变化是您需要从为同步 IO 构建的 requests 迁移到专门为与 async/await 一起使用而构建的包,例如 aiohttp(本机协程) ):
import asyncio
import aiohttp # pip install aiohttp aiodns
async def get(
session: aiohttp.ClientSession,
color: str,
**kwargs
) -> dict:
url = f"https://api.com/{color}/"
print(f"Requesting {url}")
resp = await session.request('GET', url=url, **kwargs)
# Note that this may raise an exception for non-2xx responses
# You can either handle that here, or pass the exception through
data = await resp.json()
print(f"Received data for {url}")
return data
async def main(colors, **kwargs):
# Asynchronous context manager. Prefer this rather
# than using a different session for each GET request
async with aiohttp.ClientSession() as session:
tasks = []
for c in colors:
tasks.append(get(session=session, color=c, **kwargs))
# asyncio.gather() will wait on the entire task set to be
# completed. If you want to process results greedily as they come in,
# loop over asyncio.as_completed()
htmls = await asyncio.gather(*tasks, return_exceptions=True)
return htmls
if __name__ == '__main__':
colors = ['red', 'blue', 'green'] # ...
# Either take colors from stdin or make some default here
asyncio.run(main(colors)) # Python 3.7+
这有两个不同的元素,一个是协程的异步方面,一个是在指定任务容器(期货)时引入的并发:
- 您创建了一个协程
get,它使用await 和两个awaitables:第一个是.request,第二个是.json。这是异步方面。 await 处理这些 IO 绑定响应的目的是告诉事件循环,其他 get() 调用可以轮流运行相同的例程。
- 并发方面封装在
await asyncio.gather(*tasks) 中。这会将等待的get() 调用映射到您的每个colors。结果是返回值的聚合列表。请注意,此包装器将等到所有您的回复进来并致电.json()。或者,如果您想在它们准备好时贪婪地处理它们,您可以循环 asyncio.as_completed:返回的每个 Future 对象代表剩余等待对象集中最早的结果。
最后,请注意asyncio.run() 是 Python 3.7 中引入的高级“瓷器”函数。在早期版本中,您可以(大致)模仿它:
# The "full" versions makes a new event loop and calls
# loop.shutdown_asyncgens(), see link above
loop = asyncio.get_event_loop()
try:
loop.run_until_complete(main(colors))
finally:
loop.close()
限制请求
有多种方法可以限制并发率。例如,请参阅 asyncio.semaphore in async-await function 或 large numbers of tasks with limited concurrency。