Python 中的 HTTP 客户端:Requests、HTTPX 与 AIOHTTP 对比

任何使用 Python 超过几分钟的人都一定接触过 Requests 库。它如此普及,以至于有些人甚至误以为它是 Python 标准库的一部分。Requests 的设计极其直观,以至于 r = requests.get(...) 这样的写法几乎成了肌肉记忆。相比之下,如果用 Python 内置的 urllib 编写脚本,往往第一步就是打开官方文档查阅用法。

但 Python 在不断演进,如今再一味默认使用 Requests 已经不再是最优选择。虽然对于简短的同步脚本,Requests 依然是可靠之选,但在现代 Python 开发中------尤其是涉及异步编程的场景下------像 HTTPX 和 AIOHTTP 这样的新库往往更加合适。

本文将对 Python 中三个主流的 HTTP 客户端库------Requests、HTTPX 和 AIOHTTP------进行详细对比,分析它们各自的优缺点和适用场景,帮助你在下一个项目中做出更明智的技术选型。

同步请求 vs 异步请求:核心概念

同步请求:在单进程、单线程的代码中,发起一个请求后,必须等待其返回结果,才能执行下一个请求。

异步请求:同样在单进程、单线程中,发起请求后,在等待响应的"空闲时间"里,可以继续发起其他请求,从而实现并发。

✅ 说明:异步并非多线程,而是通过事件循环(event loop)在 I/O 等待期间切换任务,提升 CPU 利用率。

一切始于 urllib:Guido 的"原初"设计

在深入探讨现代 HTTP 库之前,我们不妨先回溯一下历史:Python 内置的 urllib 模块。

自 Python 早期版本起,urllib 就作为标准库的一部分存在。它的初衷是提供一套完整的 URL 处理与网络操作工具集。然而,其 API 以复杂难用著称,即便是执行一个简单的 HTTP 请求,也常常需要多步操作。

下面是一个使用 urllib 发起 GET 请求的基本示例:

python 复制代码
# urllib_basic.py
from urllib.request import urlopen

with urlopen('https://api.github.com') as response:
    body = response.read()
    print(body)

对于简单 GET 请求,这段代码看起来尚可接受。但一旦涉及设置请求头、发送 POST 请求或进行身份认证,代码就会迅速变得冗长繁琐。例如,下面是使用 urllib 实现带基本认证(Basic Auth)的请求:

python 复制代码
# urllib_example.py
import urllib.request
import json

url = 'http://httpbin.org/basic-auth/user/passwd'
username = 'user'
password = 'passwd'

# 创建带认证处理器的 opener
password_mgr = urllib.request.HTTPPasswordMgrWithDefaultRealm()
password_mgr.add_password(None, url, username, password)
auth_handler = urllib.request.HTTPBasicAuthHandler(password_mgr)
opener = urllib.request.build_opener(auth_handler)

# 发起请求
with opener.open(url) as response:
    raw_data = response.read()
    encoding = response.info().get_content_charset('utf-8')
    data = json.loads(raw_data.decode(encoding))

print(data)

在这个例子中,我们需要手动构建认证处理器、创建 opener、读取响应、解码内容,再解析 JSON。这种冗余和复杂性正是推动第三方 HTTP 库诞生的主要原因。

Requests:为人类设计的 HTTP 库™️

2011 年情人节(没错,就是那天),Kenneth Reitz 发布了 Requests 库,目标是让 HTTP 请求变得对开发者极度友好。仅两年后(2013 年 7 月),Requests 的下载量就突破了 330 万次;截至 2024 年 8 月,它每天的下载量已高达 1200 万次。

事实证明,开发者体验(Developer Experience, DevEx)真的很重要!

安装 Requests 非常简单:

python 复制代码
pip install requests

让我们把前面 urllib 的例子用 Requests 重写一遍:

python 复制代码
# requests_example.py
import requests

# GET 请求
response = requests.get('https://api.github.com')
print(response.text)

# 带认证的请求
url = 'http://httpbin.org/basic-auth/user/passwd'
username = 'user'
password = 'passwd'

response = requests.get(url, auth=(username, password))
data = response.json()

print(data)

对比之下,Requests 的简洁性和可读性一目了然。它自动处理了认证头、JSON 解析等繁琐细节,大大降低了使用门槛。

Requests 成为事实标准的关键特性包括:

  • 自动内容解码:根据 Content-Type 头自动解码响应体。
  • 会话持久化:通过 Session 对象在多个请求间复用连接和参数(如 cookies、headers)。
  • 优雅的错误处理:对网络错误和 HTTP 状态码异常提供清晰的异常类型。
  • 自动解压缩:能自动解压 gzip 等编码的响应内容。

然而,随着 Python 生态的发展,特别是 异步编程(asynchronous programming) 的兴起(Python 3.4 引入了 asyncio),Requests 的局限性逐渐显现------它完全不支持异步操作。

AIOHTTP:专为 asyncio 而生

AIOHTTP 最早发布于 2014 年 10 月,是最早全面拥抱 Python asyncio 框架的 HTTP 库之一。它从底层设计就围绕异步操作展开,非常适合需要高并发、高性能的网络应用。截至 2024 年 5 月,AIOHTTP 日均下载量约为 600 万次。

AIOHTTP 的核心优势包括:

  • 纯异步架构:所有操作均为 async/await 风格,可高效处理成百上千个并发连接。
  • 客户端 + 服务器双支持:不仅能发起 HTTP 请求,还能构建异步 Web 服务。
  • 原生 WebSocket 支持:完整支持 WebSocket 协议。

安装方式:

pip 复制代码
pip install aiohttp

下面是一个基本用法示例:

python 复制代码
# aiohttp_basic.py
import aiohttp
import asyncio

async def fetch(session, url):
    async with session.get(url) as response:
        return await response.text()

async def main():
    async with aiohttp.ClientSession() as session:
        html = await fetch(session, 'https://api.github.com')
        print(html)

asyncio.run(main())

要真正体现 AIOHTTP 的并发优势,我们需要同时发起多个请求。以下示例并发获取多个带延迟的 URL:

python 复制代码
# aiohttp_multiple.py
import asyncio
import aiohttp
import time

urls = [
    "https://httpbin.org/delay/1",
    "https://httpbin.org/delay/2",
    "https://httpbin.org/delay/3",
    "https://httpbin.org/delay/1",
    "https://httpbin.org/delay/2",
]

async def fetch(session, url, i):
    try:
        start_time = time.perf_counter()
        async with session.get(url) as response:
            await response.text()
            elapsed = time.perf_counter() - start_time
            print(f"请求 {i} 耗时 {elapsed:.2f} 秒")
    except asyncio.TimeoutError:
        print(f"请求 {i} 超时")

async def async_requests():
    start_time = time.perf_counter()
    async with aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=10)) as session:
        tasks = [fetch(session, url, i) for i, url in enumerate(urls, 1)]
        await asyncio.gather(*tasks)

    total_time = time.perf_counter() - start_time
    print(f"\n总耗时:{total_time:.2f} 秒")

if __name__ == "__main__":
    asyncio.run(async_requests())

输出结果类似如下(注意:由于并发执行,总时间接近最慢请求的延迟):

复制代码
请求 1 耗时 2.22 秒
请求 4 耗时 2.22 秒
请求 5 耗时 3.20 秒
请求 2 耗时 3.20 秒
请求 3 耗时 4.30 秒

总耗时:4.31 秒

作为对比,用 Requests 实现相同功能(同步顺序执行):

python 复制代码
# requests_multiple.py
import requests
import time

urls = [
    "https://httpbin.org/delay/1",
    "https://httpbin.org/delay/2",
    "https://httpbin.org/delay/3",
    "https://httpbin.org/delay/1",
    "https://httpbin.org/delay/2",
]

def sync_requests():
    start_time = time.time()
    with requests.Session() as session:
        # 注意:requests.Session 没有 timeout 属性,需在 get 中指定
        for i, url in enumerate(urls, 1):
            try:
                response = session.get(url, timeout=10)
                print(f"请求 {i} 耗时 {response.elapsed.total_seconds():.2f} 秒")
            except requests.Timeout:
                print(f"请求 {i} 超时")

    total_time = time.time() - start_time
    print(f"\n总耗时:{total_time:.2f} 秒")

if __name__ == "__main__":
    sync_requests()

其输出总耗时显著更长(约 12 秒以上),因为每个请求必须等前一个完成后才能开始。

HTTPX:融合两者之长

HTTPX 由 Django REST Framework 的作者 Tom Christie 于 2019 年 8 月发布,旨在融合 Requests 的易用性和 AIOHTTP 的异步能力。它既提供类似 Requests 的同步 API,也原生支持异步操作。

HTTPX 的主要特性包括:

  • 熟悉的 Requests 风格 API:迁移成本极低。
  • 同步与异步双模支持:同一库内无缝切换。
  • 原生 HTTP/2 支持:与现代 Web 服务器通信更高效。
  • 完整的类型注解(Type Annotations):提升 IDE 智能提示和静态检查能力。

安装命令:

pip 复制代码
pip install httpx

同步用法示例(几乎与 Requests 一致):

python 复制代码
# httpx_sync.py
import httpx

response = httpx.get('https://api.github.com')
print(response.status_code)
print(response.json())

异步用法示例:

python 复制代码
# httpx_async.py
import asyncio
import httpx
import time

urls = [
    "https://httpbin.org/delay/1",
    "https://httpbin.org/delay/2",
    "https://httpbin.org/delay/3",
    "https://httpbin.org/delay/1",
    "https://httpbin.org/delay/2",
]

async def fetch(client, url, i):
    response = await client.get(url)
    print(f"请求 {i} 耗时 {response.elapsed.total_seconds():.2f} 秒")

async def async_requests():
    start_time = time.time()
    async with httpx.AsyncClient(timeout=10.0) as client:
        tasks = [fetch(client, url, i) for i, url in enumerate(urls, 1)]
        await asyncio.gather(*tasks)

    total_time = time.time() - start_time
    print(f"\n总耗时:{total_time:.2f} 秒")

if __name__ == "__main__":
    asyncio.run(async_requests())

输出结果与 AIOHTTP 类似,总耗时约 4--5 秒。

HTTPX 的最大优势在于:你可以在同一个项目中自由混合同步与异步代码,而无需引入多个 HTTP 库。

如何为你的项目选择合适的 HTTP 客户端?

下表总结了三者的功能对比:

特性 / 能力 Requests AIOHTTP HTTPX
同步操作
异步操作
原生 HTTP/2 支持
WebSocket 支持 通过插件支持
类型注解(Type Hints) 部分支持
带退避策略的自动重试 通过插件
SOCKS 代理支持 通过插件 通过插件
事件钩子(Event Hooks)
Brotli 压缩支持 通过插件
异步 DNS 查询

推荐使用场景

  • Requests:适合简单脚本、教学示例或不需要异步的中小型项目。其简洁性和社区支持无可替代。
  • AIOHTTP:适用于高并发异步服务(如 Web 爬虫、实时数据处理、微服务网关),尤其当你需要 WebSocket 或自建异步服务器时。
  • HTTPX:如果你希望兼顾同步与异步,或想为未来升级到 HTTP/2 做准备,HTTPX 是最佳选择。它也是从 Requests 平滑过渡到异步世界的理想桥梁。

FQA

  1. 异步有哪些使用场景
  • I/O 密集型任务:比如同时请求 100 个 API、下载多个文件。
  • 高并发服务:如 FastAPI、Quart 等异步 Web 框架处理成千上万的并发连接。
  1. 所有请求都用aiohttp,可以吗?
    技术上完全可以,但仍然不建议。
    有很多场景并不是高并发,如果简单的请求都用aiohttp,就会带来几个问题:
  • 简单请求代码也很复杂
python 复制代码
import asyncio
import aiohttp

async def fetch_once():
    async with aiohttp.ClientSession() as session:
        async with session.get("https://api.example.com/data") as resp:
            return await resp.json()

# 调用
result = asyncio.run(fetch_once())
  • 所有调用点都必须变成 async/await
    一旦底层用了 aiohttp,上层函数也必须是 async,否则无法获取结果。
python 复制代码
# 如果你有一个同步函数想调用它?
def old_function():
    data = fetch_once()  # ❌ 报错!不能在同步函数中直接调用协程

你只能:

改成 async def old_function() → 连锁反应,整个调用链变异步

或用 asyncio.run(fetch_once()) → 但不能在已有事件循环中使用(比如在 Jupyter、FastAPI 里会报错)

  • 调试和测试更复杂

    (1)异步代码在 IDE 中调试体验较差(断点、堆栈)

    (2)单元测试需用 pytest-asyncio 或手动管理事件循环

    (3)初学者容易写出"看似并发实则串行"的 bug(比如忘记 await)

  • 在某些环境无法运行

    (1)Jupyter Notebook:默认已有事件循环,asyncio.run() 会报错

    (2)Django/Flask 同步视图:在同步 Web 框架中强行跑异步代码很别扭

    (3)简单脚本/REPL:每次都要写 asyncio.run(),破坏交互流畅性

  • 性能未必更好(甚至更差)

    如果你只发 1~5 个请求,aiohttp 的事件循环开销可能超过收益

    (1)100 并发:aiohttp 快

    (2)1 次请求:requests 更快(无协程调度开销)

参考文献

  1. https://dev.to/leapcell/comparing-requests-aiohttp-and-httpx-which-http-client-should-you-use-3784
  2. https://www.speakeasy.com/blog/python-http-clients-requests-vs-httpx-vs-aiohttp
相关推荐
u0109272712 小时前
持续集成/持续部署(CI/CD) for Python
jvm·数据库·python
lixin5565562 小时前
基于迁移学习的图像风格增强器
java·人工智能·pytorch·python·深度学习·语言模型
阡陌..2 小时前
浅谈SAR图像处理---形态学滤波
图像处理·人工智能·python
qq_229058013 小时前
python-Dgango项目收集静态文件、构建前端、安装依赖
开发语言·python
测试人社区—66793 小时前
2025区块链分层防御指南:AI驱动的安全测试实战策略
开发语言·驱动开发·python·appium·pytest
喵手3 小时前
Python爬虫零基础入门【第九章:实战项目教学·第10节】下载型资源采集:PDF/附件下载 + 去重校验!
爬虫·python·爬虫实战·python爬虫工程化实战·零基础python爬虫教学·下载型资源采集·pdf下载
玄同7653 小时前
深入理解 SQLAlchemy 的 relationship:让 ORM 关联像 Python 对象一样简单
人工智能·python·sql·conda·fastapi·pip·sqlalchemy
Yorlen_Zhang3 小时前
Python @property 装饰器详解:优雅控制属性访问的魔法
开发语言·python
喵手4 小时前
Python爬虫零基础入门【第九章:实战项目教学·第13节】)动态站点“回到接口“:识别接口并用 Requests 重写(更稳)!
爬虫·python·python爬虫实战·python爬虫工程化实战·python爬虫零基础入门·动态站点·识别接口并requests重写