在 Python 网络数据采集领域,“用 asyncio 改写后速度提升几十倍” 是经常被提到的结论。很多开发者从同步的 requests 切换到 aiohttp 后,采集耗时会从数分钟压缩到几十秒。asyncio 之所以能带来如此显著的速度提升,本质上并不是让 CPU 运行得更快,而是彻底盘活了网络 IO 场景下被大量浪费的等待时间,用极低的成本实现了高并发任务调度。
一、先看清本质:采集任务的瓶颈从来不是 CPU
网络采集是典型的IO 密集型任务,这是理解一切问题的前提。
当我们发起一个 HTTP 请求去抓取网页时,整个流程里真正消耗 CPU 计算的环节极少 —— 构造请求、解析响应只占不到 1% 的时间,剩下 99% 以上的时间都在等待:
- 等待 TCP 连接建立
- 等待服务器处理请求
- 等待网络传输响应数据
在传统同步代码中,程序执行到requests.get()时会完全阻塞,直到响应返回才能继续下一行代码。如果有 100 个待采集的页面,每个页面平均响应耗时 1 秒,同步执行就至少需要 100 秒。在这 100 秒里,CPU 绝大多数时间都处于空闲等待状态,计算资源被大量浪费。
asyncio 要解决的核心问题,就是把这些 “死等” 的时间利用起来。
二、核心机制:单线程内的协程并发调度
asyncio 实现高并发的基石是事件循环 + 协程,它在单线程内部完成了多任务的切换与调度,完全避开了传统多线程方案的高昂成本。
1. 事件循环:异步任务的 “调度中枢”
asyncio 的核心是一个运行在单线程里的事件循环(Event Loop)。它会持续监听所有任务的状态:
- 当某个协程遇到 IO 操作(比如发起网络请求)时,会主动挂起自己,把控制权交还给事件循环;
- 事件循环随即切换到另一个已经就绪的协程继续执行;
- 当之前挂起的 IO 操作完成(比如响应回来了),事件循环会再把它唤醒,从中断的地方继续执行。
整个过程中,线程始终在运行,没有任何空闲等待,CPU 利用率被拉满。
2. 协程:比线程轻得多的执行单元
很多人会把协程和线程混淆,二者的调度成本有数量级的差距:
- 线程是操作系统内核级的调度单元,创建和切换都需要陷入内核态,每个线程还会占用数 MB 的栈内存。当并发数达到上千时,线程切换本身就会成为性能瓶颈。
- 协程是用户态的执行单元,由 Python 程序自己控制调度,切换完全在用户态完成,不需要操作系统介入。切换一个协程的开销几乎可以忽略,内存占用也只有 KB 级别。
这意味着 asyncio 可以轻松同时运行上万个采集任务,而不会像多线程那样出现内存暴涨、调度卡顿的问题。
三、对比多线程:没有 GIL 拖累,没有切换开销
Python 由于全局解释器锁(GIL)的存在,多线程在 CPU 密集型场景下无法利用多核优势。即便在 IO 密集型的采集场景中,多线程的性能也往往不如 asyncio。
原因主要有两点:
- 线程切换成本高:操作系统调度线程需要保存和恢复大量上下文,并发越高,切换开销占比越大。而协程切换只需要保存少量寄存器状态,成本极低。
- 资源占用低:开启几百个线程就可能让进程内存占用翻倍,而同样数量的协程几乎不会带来明显的内存增长。对于需要批量发起上千请求的采集任务,asyncio 的并发承载力远高于多线程。
简单来说,多线程是 “用多个工人轮流干活”,而 asyncio 是 “一个工人在多个工位之间无缝切换,一刻也不闲着”。在 IO 等待远多于计算的场景下,后者效率更高。
四、语法层面:用同步的写法写异步逻辑
在 asyncio 普及之前,Python 也有基于回调的异步方案(如 Twisted),但代码嵌套层级深、可读性差,很容易写出 “回调地狱”。
async/await语法的出现解决了这个问题:
async def fetch(url): response = await session.get(url) return await response.text()这段异步代码的结构和同步代码几乎一致,开发者可以用接近同步的思维写出异步逻辑,同时底层依然享受非阻塞 IO 带来的并发收益。这也让 asyncio 成为了网络采集中最主流的异步方案。
五、澄清误区:asyncio 不是万能加速
需要明确的是,asyncio 的加速只适用于IO 密集型场景。如果你的采集任务包含大量 CPU 计算(比如复杂的图片处理、大规模文本解析),asyncio 不仅不会提速,反而可能因为调度开销变慢。
此外,两个常见的错误用法也会让 asyncio 完全失效:
- 在 async 函数中调用同步阻塞库(比如在协程里用 requests),会阻塞整个事件循环,所有任务都会卡住,性能甚至不如同步代码;
- 盲目设置超高并发数,会超过本地网络带宽上限或触发目标服务器限流,反而导致请求大量失败。
总结
asyncio 能提高采集速度,本质上是精准匹配了网络采集的 IO 密集特性:它通过事件循环和协程,把原本浪费在网络等待上的时间全部利用起来,以极低的调度成本实现高并发,让单线程的效率发挥到极致。
对于绝大多数 “发请求、等响应、存数据” 的采集场景,只要正确使用异步 HTTP 客户端(如 aiohttp)配合 asyncio,就能获得非常显著的速度提升。