aiohttp 3.14.0(2026 年 6 月 1 日)弃用了 BasicAuth 和 proxy_auth 参数(更新日志,#12499)。替代做法是用 aiohttp.encode_basic_auth() 生成 Proxy-Authorization 请求头:
import asyncio
import aiohttp
PROXY = "http://proxy.example.com:8080"
PROXY_HEADERS = {"Proxy-Authorization": aiohttp.encode_basic_auth("user", "pass")}
async def main() -> None:
async with aiohttp.ClientSession(proxy=PROXY) as session:
async with session.get("https://example.com/", proxy_headers=PROXY_HEADERS) as response:
print(response.status)
asyncio.run(main())在我们的实验环境里,这种写法让每个 https:// 请求都通过了认证,并且没有 DeprecationWarning。它没有让普通的 http:// 请求通过认证:aiohttp 3.14.4 只在建立 HTTPS 隧道的 CONNECT 请求里发送 proxy_headers,所以对 http:// 的 URL,代理没有收到凭据,返回了 407。
| 凭据的传递方式 | https:// URL | http:// URL | 弃用警告 |
|---|---|---|---|
proxy_headers={"Proxy-Authorization": aiohttp.encode_basic_auth(...)} | 200 | 407 | 无 |
proxy="http://user:pass@host:port" | 200 | 200 | 无 |
proxy_auth=aiohttp.BasicAuth(...) | 200 | 200 | 每次调用两条 |
这些结果记录于 2026 年 10 月 6 日,使用 aiohttp 3.14.4 和 aiohttp-socks 0.12.0,运行在 Python 3.14.4 上,以 python -W always 启动,对着一个要求 Basic 认证的本地代理。aiohttp 3.14.0 和 3.14.3 的结果相同。
从 proxy_auth=aiohttp.BasicAuth 迁移
旧的调用在 3.14.4 中对两种 URL 协议仍然有效:
async with session.get(url, proxy=PROXY, proxy_auth=aiohttp.BasicAuth("user", "pass")) as response:
print(response.status)每次调用打印两条警告:
DeprecationWarning: BasicAuth is deprecated and will be removed in aiohttp 4.0; use aiohttp.encode_basic_auth() with headers={'Authorization': ...} instead
DeprecationWarning: The 'proxy_auth' parameter is deprecated and will be removed in v4; pass proxy_headers={'Proxy-Authorization': aiohttp.encode_basic_auth(login, password)} instead替代写法:
proxy_headers = {"Proxy-Authorization": aiohttp.encode_basic_auth("user", "pass")}
async with session.get(url, proxy=PROXY, proxy_headers=proxy_headers) as response:
print(response.status)上线前检查四件事:
- 普通
http://URL 会丢掉凭据。 文档里的示例把proxy_headers和http://的 URL 一起使用。在实验中,这个请求到达代理时没有Proxy-Authorization,也没有我们加在proxy_headers里的自定义请求头,返回的是 407。Issue #4422 针对 aiohttp 3.6.2 报告过同样的现象。如果你要请求http://的 URL,把凭据写进代理 URL:proxy="http://user:pass@host:port"对两种协议都返回 200,且没有警告。 ClientSession没有proxy_headers参数。 文档里有ClientSession(proxy=..., proxy_headers=...)的写法。在 3.14.4 中它抛出了TypeError: ClientSession.__init__() got an unexpected keyword argument 'proxy_headers'。在会话上设置proxy=,每次请求再传proxy_headers=,就像第一个示例那样。- 不要把这个请求头挪到
headers=。 对https://的 URL,请求头在隧道内部传输。把Proxy-Authorization放进headers=后,实验中的目标服务器收到了代理凭据。 - 默认编码变了。
encode_basic_auth()使用 UTF-8,而BasicAuth使用 latin1。对密码päss,两者生成的请求头值不同,所以如果你的代理需要旧的字节,请传入encoding="latin1"。
encode_basic_auth() 是 3.14 新增的(参考文档)。在 aiohttp 3.13.5 上,同样的代码抛出了 AttributeError: module aiohttp has no attribute encode_basic_auth。
环境变量需要 trust_env=True
默认的 ClientSession() 会忽略代理环境变量。设置了 HTTP_PROXY 时,请求直接发往目标,代理什么也没有记录。下面这个会话使用了代理:
async with aiohttp.ClientSession(trust_env=True) as session:
async with session.get("http://example.com/") as response:
print(response.status)文档说明,aiohttp 通过 urllib.request.getproxies() 读取这些变量,把它们用于 HTTP、HTTPS、WS 和 WSS 协议,让 no_proxy 中的主机绕过代理,并且可以从 ~/.netrc 读取代理凭据。在实验中,使用 trust_env=True 时:
HTTP_PROXY和小写的http_proxy对http://的 URL 生效,HTTPS_PROXY对https://的 URL 生效。只设置HTTP_PROXY时,https://请求是直连的。ALL_PROXY被忽略。- 变量的 URL 里的凭据让两种协议都通过了认证,并且没有打印警告。
NO_PROXY=127.0.0.1让请求直连。它对显式的proxy=参数不起作用,而且显式参数的优先级高于变量。- 值为
https://代理 URL 的变量被跳过。aiohttp 记录了HTTPS proxies https://127.0.0.1:8888 are not supported, ignoring,然后直接连接。
代理环境变量比较了这些规则在其他客户端中的情况。
https:// URL 上的 ClientHttpProxyError: 407
当代理拒绝 CONNECT 请求时,aiohttp 在任何内容到达目标之前就抛出异常。代理 URL 里密码错误时:
aiohttp.client_exceptions.ClientHttpProxyError: 407, message='Proxy Authentication Required', url='http://user:wrong@127.0.0.1:8888'密码就在消息里,所以异常去哪里,它就去哪里:日志、调用栈、错误追踪系统。当凭据放在 proxy_headers 里,或者取自 HTTPS_PROXY 时,同样的失败打印的是 url='http://127.0.0.1:8888'。
对 https:// 流量,不要把凭据放在代理 URL 里。在必须写进 URL 的地方,记录 exc.status 和 exc.message(实验中是 407 和 Proxy Authentication Required),而不是异常文本。当凭据看起来没错时,修复代理错误 407 给出了检查顺序。
http:// URL 上状态码 407 但没有异常
对 http:// 的 URL,代理的 407 是一个普通响应。不会抛出异常,response.status 是 407,Proxy-Authenticate 响应头里是认证质询。只捕获异常的代码会把它当成成功。
检查 response.status,或者用 raise_for_status=True 创建会话。这样抛出了:
aiohttp.client_exceptions.ClientResponseError: 407, message='Proxy Authentication Required', url='http://127.0.0.1:8080/'在请求上使用 raise_for_status=True 和调用 response.raise_for_status() 抛出的是同一个错误。这里的类是 ClientResponseError,URL 是目标的 URL,不是代理的。ClientHttpProxyError 是它的子类,所以 except aiohttp.ClientResponseError 加上对 exc.status == 407 的判断可以覆盖两种协议。
socks5:// 代理 URL 导致的 Bad status line: b'\x05\x00'
aiohttp 不会拒绝 proxy="socks5://..."。它向 SOCKS 端口发送一个 HTTP 请求,然后在读取响应时失败:
aiohttp.client_exceptions.ClientResponseError: 400, message="Bad status line:\n Expected HTTP/, RTSP/ or ICE/:\n\n b'\\x05\\x00'\n ^", url='http://127.0.0.1:8080/'\x05\x00 是 SOCKS5 服务器响应的开头,被当成了 HTTP 状态行来读取。socks5h:// 以同样的方式失败。解决办法是使用 SOCKS 连接器,见下文。
https:// 代理 URL 导致的 ClientConnectorSSLError
https:// 的代理 URL 让 aiohttp 与代理本身建立 TLS。对着实验中只说明文 HTTP 的代理,请求在握手阶段失败:
aiohttp.client_exceptions.ClientConnectorSSLError: Cannot connect to host 127.0.0.1:8888 ssl:default [[SSL: RECORD_LAYER_FAILURE] record layer failure (_ssl.c:1081)]代理看到的是 TLS ClientHello,并用明文的 400 作了回应。代理 URL 的协议描述的是到代理的连接,而不是到目标的连接:http:// 的代理 URL 通过 CONNECT 承载 https:// 请求。这段错误文本来自 OpenSSL 3.5.5,在其他版本上可能不同。
DeprecationWarning: BasicAuth is deprecated
只要构造 aiohttp.BasicAuth(...) 就会触发第一条警告,此时还没有发出任何请求。用于网站凭据的 auth= 参数也被弃用了。它的警告给出的替代写法是 headers={'Authorization': aiohttp.encode_basic_auth(login, password)}。
你可能两条都看不到。默认情况下,只有当触发它的代码位于 __main__ 时,Python 才显示 DeprecationWarning。在实验中,被弃用的调用位于被运行的脚本里时打印了警告,位于被导入的模块里时什么也没打印。python -W always 在两处都打印了警告。python -W error 把第一条变成了异常,这样跑一遍测试就能找出每个调用点。
SOCKS 代理:使用 aiohttp-socks
python -m pip install aiohttp-socksimport aiohttp
from aiohttp_socks import ProxyConnector
connector = ProxyConnector.from_url("socks5://127.0.0.1:1080")
async with aiohttp.ClientSession(connector=connector) as session:
async with session.get("https://example.com/") as response:
print(response.status)使用 aiohttp-socks 0.12.0 时,这段代码对 http:// 和 https:// 的 URL 都返回 200,SOCKS 服务器记录了每个连接。两个细节:
socks5://已经把主机名发给代理。对http://localhost:8080/,服务器记录的是域名。使用rdns=False时,它记录的是127.0.0.1。ProxyConnector.from_url("socks5h://...")抛出了ValueError: Invalid scheme component: socks5h。SOCKS5 与 SOCKS5h 的区别展示了各个客户端如何处理这两种协议。
Requests 和 HTTPX 需要各自的 SOCKS 依赖包,详见解决 Missing dependencies for SOCKS support。
代理出错后的重试
407、SOCKS 的状态行错误和上面的 TLS 错误都是配置错误。同一个请求会以同样的方式再次失败,所以应该修正配置,而不是重试。对超时和断开的连接,只重试可以安全重复的请求。POST 可能在代理连接失败之前就已经到达目标。代理重试:丢失一个响应,创建了两个任务展示了这种情况,以及幂等键能改变什么。
如果你用 HTTPX 做异步请求,请看 HTTPX 异步代理。如果想在编码代理中获得诊断帮助,Proxy Toolkit MCP 接受经过清理的错误描述。不要把凭据放进它的参数里。
测试方法
在 Ubuntu 26.04.1 上,使用 Python 3.14.4 和 OpenSSL 3.5.5,一切都在 127.0.0.1 上:python -m http.server 作为 HTTP 和 HTTPS 目标,一个要求 Basic 认证并记录每个请求及其请求头名称的代理,一个记录每个目的地的 SOCKS5 服务器,以及一个报告自己收到哪些请求头的 HTTPS 目标。67 个用例中的每一个都在新的解释器和干净的环境中运行。没有使用任何代理账号。
实验归档包含运行脚本、用例、代理、SOCKS5 服务器、README 和记录的结果。
未测试:macOS 和 Windows、Digest 和 NTLM 代理认证、需要认证的 SOCKS 代理、WebSocket 请求、~/.netrc 中的凭据,以及需要通过 TLS 连接的代理。
ipvolt 是一项面向开发者的代理服务,目前尚未开放;加入早期访问名单,开放时你会收到一封邮件。
参考来源与延伸阅读
本指南参考的技术资料。请以你所安装版本的文档以及代理服务商支持的配置为准。
- aiohttp changelog: 3.14.0
- aiohttp pull request #12499: Deprecate auth. Add encode_basic_auth
- aiohttp Advanced Client Usage: proxy support and trust_env
- aiohttp Client Reference: encode_basic_auth and proxy_headers
- aiohttp issue #4422: proxy_headers not passed to proxy
- aiohttp-socks on PyPI
- Python warnings: the default warning filter