Python 爬取数据选 Requests 还是 aiohttp 对比区别

文章导读
选 Requests 还是 aiohttp,主要看你的爬虫是跑几次还是长期高并发运行。简单脚本选 Requests 省事,大规模采集选 aiohttp 省时间。
📋 目录
  1. A 核心区别与适用场景
  2. B 代码实战对比
  3. C 性能基准测试脚本
  4. D 异步异常处理最佳实践
  5. E 选型建议总结
  6. F 常见坑
  7. G 参考资料
A A

选 Requests 还是 aiohttp,主要看你的爬虫是跑几次还是长期高并发运行。简单脚本选 Requests 省事,大规模采集选 aiohttp 省时间。

先说结论:Requests 适合同步简单任务,aiohttp 适合异步高并发场景,两者底层模型不同,不可混用。

  • 适合:Requests 用于单次请求、简单脚本;aiohttp 用于高并发、大量 IO 操作。
  • 重点看:Requests 是同步阻塞,aiohttp 是异步非阻塞,等待响应时的 CPU 利用率完全不同。
  • 别忽略:aiohttp 基于 asyncio,需要 Python 3 环境且代码必须使用 async/await 写法,单请求场景下可能因开销略慢于 Requests。

核心区别与适用场景

这两个库的根本区别不在于“快慢”,而在于“等待时的行为”。

Requests 是同步库,发起请求后,整个线程会停下来等待服务器响应,这段时间 CPU 处于空闲状态,无法处理其他任务。这在请求量少时没问题,但如果要爬取上千个页面,大量时间都浪费在网络 IO 等待上。

aiohttp 是异步库,基于 asyncio 事件循环。发起请求后,程序不会死等,而是切换去执行其他任务,直到响应返回。这意味着在单个线程内,它可以并发处理数百个请求,将网络 IO 等待时间压缩到极致。在高并发场景下性能差异显著,但单请求场景下异步开销可能导致其略慢于同步方案。

代码实战对比

如果你还在纠结,可以直接参考下面的代码形态,看哪种更符合你的项目习惯。

Requests 同步写法(推荐复用 Session):

import requests

# 实例化 Session 复用连接,避免频繁握手
session = requests.Session()

urls = ['https://example.com/1', 'https://example.com/2']
for url in urls:
    response = session.get(url)
    print(response.text)

session.close()

aiohttp 异步写法(展示并发能力):

import aiohttp
import asyncio

async def fetch(session, url):
    try:
        async with session.get(url) as resp:
            return await resp.text()
    except Exception as e:
        print(f"Request failed {url}: {e}")
        return None

async def main():
    urls = ['https://example.com/1', 'https://example.com/2']
    # 创建 ClientSession 复用连接
    async with aiohttp.ClientSession() as session:
        # 使用 gather 并发执行所有任务
        tasks = [fetch(session, url) for url in urls]
        results = await asyncio.gather(*tasks)
        print(results)

asyncio.run(main())

如果你习惯一行一行代码顺序执行,选 Requests;如果你能接受 async/await 语法并希望同时发起多个请求,选 aiohttp。

性能基准测试脚本

选型后,可以通过以下脚本验证效果。注意测试时需确保网络环境稳定。

import time
import requests
import aiohttp
import asyncio

TARGET_URL = 'https://httpbin.org/delay/1'  # 模拟延迟 1 秒
COUNT = 10

def test_requests():
    start = time.time()
    session = requests.Session()
    for _ in range(COUNT):
        session.get(TARGET_URL)
    session.close()
    return time.time() - start

async def fetch_aiohttp(session, url):
    async with session.get(url) as resp:
        await resp.text()

async def test_aiohttp():
    start = time.time()
    async with aiohttp.ClientSession() as session:
        tasks = [fetch_aiohttp(session, TARGET_URL) for _ in range(COUNT)]
        await asyncio.gather(*tasks)
    return time.time() - start

if __name__ == '__main__':
    print(f"Requests 耗时:{test_requests():.2f} 秒")
    print(f"aiohttp 耗时:{asyncio.run(test_aiohttp()):.2f} 秒")

预期结果:Requests 耗时约为 COUNT * 延迟秒数(串行),aiohttp 耗时接近单次延迟秒数(并发)。

异步异常处理最佳实践

异步爬虫并发高,一旦某个请求异常未捕获,可能导致整个任务中断。务必在 async 函数内部做好 try/except 处理。

async def safe_fetch(session, url):
    try:
        async with session.get(url, timeout=10) as resp:
            resp.raise_for_status()
            return await resp.text()
    except aiohttp.ClientError as e:
        # 记录失败 URL 以便重试
        print(f"Network error for {url}: {e}")
    except asyncio.TimeoutError:
        print(f"Timeout for {url}")
    except Exception as e:
        print(f"Unexpected error for {url}: {e}")
    return None

选型建议总结

选型时不要盲目跟风,按以下步骤评估:

Python 爬取数据选 Requests 还是 aiohttp 对比区别

1. 评估任务规模

如果只是偶尔爬几十个页面,或者脚本只运行几分钟,Requests 足够好用,开发效率最高。如果需要长期运行、采集成千上万个 URL,aiohttp 的并发优势才能体现。

2. 检查团队技能

aiohttp 需要理解异步编程概念(如事件循环、协程)。如果团队成员只熟悉同步代码,强行上异步可能导致代码难以维护,甚至出现事件循环关闭错误。

3. 确认运行环境

aiohttp 仅支持 Python 3 环境,且依赖 asyncio 机制。Requests 则兼容性更好。此外,如果项目需要 HTTP/2 支持或同时需要同步/异步模式,httpx 可能是更灵活的选择。

常见坑

1. Requests 未复用连接

使用 Requests 循环请求时,务必实例化 requests.Session() 对象,否则每次请求都会重新建立 TCP 连接,速度会非常慢。

2. aiohttp 事件循环错误

在异步代码中,常见错误是在事件循环关闭后尝试创建新连接,或者在不同线程间混用事件循环。确保所有异步操作都在同一个事件循环上下文中运行,推荐使用 asyncio.run() 入口。

参考资料

建议查阅官方文档获取最新 API 用法:

  • Requests 官方文档:https://docs.python-requests.org
  • aiohttp 官方文档:https://docs.aiohttp.org
  • Python asyncio 文档:https://docs.python.org/3/library/asyncio.html