# 环境准备
python 版本 3.5 及以上,aiohttp 库
# 开始
# 基础了解
# 阻塞
阻塞是程序自身无法继续执行下一步的情况,即程序未得到所需计算资源而被挂起的状态
常见为网络 I/O 阻塞,磁盘 I/O 阻塞,以及使用者的输入阻塞等等
# 非阻塞
对比阻塞进行理解,即程序可以干别的事情,其是因阻塞的存在而存在的
# 同步
顾名思义,强制让不同的请求按顺序执行,即有序
# 异步
相对同步理解,即无序
# 多进程
利用 CPU 的多核,在同一时间内执行多个不同的任务
# 协程
协程本质上是单个进程,拥有自身的寄存器和栈,可以使用其实现异步操作
# 协程中的几个概念
event_loop:事件循环,可以将某些函数放到这上面,当运行条件满足时,就调用这个函数或者方法
coroutine:即协程,在 python 中,指代协程对象类型,可以将协程对象放到事件循环中。可以使用 async 关键字来定义一个方法,当调用这个方法时,并不会立即执行这个方法,而是返回一个协程对象
task:任务,是对协程对象的进一步封装,包含了协程对象的各个状态
future:即将执行或没有执行的任务结果,与 task 没有本质区别
# 举例
# 定义一个协程
import asyncio | |
async def add(x): | |
print(f'number:{x+1}') | |
coroutine=add(1) | |
print(f'1:{coroutine}') | |
print('now1') | |
event_loop=asyncio.new_event_loop() | |
event_loop.run_until_complete(coroutine) | |
print('now2') |
注:以上代码第九行 event_loop=asyncio.new_event_loop () 也可以换成 event_loop=asyncio.get_event_loop (), 但是后者在新版 python 中会收到警告 DeprecationWarning: There is no current event loop event_loop=asyncio.get_event_loop ()
输出结果如下:
1:<coroutine object add at 0x0000025B7FECF840>
now1
number:2
now2
首先我们直接调用了 add 方法并打印其调用,但是我们得到的并不是答案,而是一个协程对象,接着我们使用 new_event_loop 方法创建了一个事件循环 event_loop,并调用 event_loop 的 run_until_complete 方法,最终才看到 add 方法打印出的答案
此处也可以将 coroutine 封装成 task 对象(甚至可以不借助 event_loop 对象),此处不做解释,仅提供代码,请读者自行理解,如下
import asyncio | |
async def add(x): | |
print(f'number:{x+1}') | |
coroutine=add(1) | |
loop=asyncio.new_event_loop() | |
task=loop.create_task(coroutine) | |
print(f'1:{task}') | |
print('now1') | |
loop.run_until_complete(task) | |
print(f'2:{task}') | |
print('now2') | |
""" | |
运行结果 | |
1:<Task pending name='Task-1' coro=<add() running at F:\WorkSpace\py-case\main.py:3>> | |
now1 | |
number:2 | |
2:<Task finished name='Task-1' coro=<add() done, defined at F:\WorkSpace\py-case\main.py:3> result=None> | |
now2 | |
""" |
import asyncio | |
async def add(x): | |
print(f'number:{x+1}') | |
coroutine=add(1) | |
task=asyncio.ensure_future(coroutine) | |
print(f'1:{task}') | |
print('now1') | |
loop=asyncio.get_event_loop() | |
loop.run_until_complete(task) | |
print(f'2:{task}') | |
print('now2') | |
""" | |
运行结果 | |
1:<Task pending name='Task-2' coro=<add() running at F:\WorkSpace\py-case\main.py:18>> | |
now1 | |
number:2 | |
2:<Task finished name='Task-2' coro=<add() done, defined at F:\WorkSpace\py-case\main.py:18> result=None> | |
now2 | |
""" |
# 多任务协程
import asyncio | |
import requests | |
async def get_response(): | |
url='https://www.baidu.com' | |
res=requests.get(url) | |
return res | |
tasks=list(asyncio.ensure_future(get_response()) for _ in range(3)) | |
print(f'task:{tasks}') | |
loop=asyncio.get_event_loop() | |
loop.run_until_complete(asyncio.wait(tasks)) | |
for task in tasks: | |
print(f'result:{task.result()}') |
此处我们创建 3 个请求并组成列表,然后通过 wait 方法将其放到事件循环中,即可发起 3 次请求
但是此时仍然是没有异步处理的,因为 requests 库并不支持异步,需要换成 aiohttp,况且上文还说了,实现异步就得有挂起操作,实现如下:
import aiohttp | |
import asyncio | |
import time | |
start_time=time.time() | |
async def get_url(url): | |
session=aiohttp.ClientSession() | |
res=await session.get(url) | |
await res.text() | |
await session.close() | |
return res | |
async def req(): | |
url='https://www.httpbin.org/delay/5' | |
print('waiting',url) | |
res=await get_url(url) | |
print(f'{res} - {url}') | |
tasks=list(asyncio.ensure_future(req()) for _ in range(6)) | |
event_loop=asyncio.get_event_loop() | |
event_loop.run_until_complete(asyncio.wait(tasks)) | |
end_time=time.time() | |
time_consuming=end_time-start_time | |
print(time_consuming) |
此代码即可实现异步操作,其中 await 关键字的作用就是将协程挂起,可看到,原本需要至少 30 秒的请求时间只用了 10 秒,大大提升了爬取速度,(耗时与网络状况有关)