起点
proxies 字典、Session 级默认值、凭据编码、SOCKS5、环境变量行为,以及代理步骤失败时 Requests 抛出的异常。
用 proxies 字典为请求设置代理
Requests 从一个以目标协议为键的字典中选择代理。https 键的值用于 https:// 目标,http 键的值用于 http:// 目标;两者通常指向同一个网关。all 键适用于任何协议,而 https://example.com 这样的键则把某一个主机固定到特定网关。
值是代理 URL,带有它自己的协议。http://host:port 是普通 HTTP 代理;对于 https:// 目标,Requests 会请求它打开一条 CONNECT 隧道。只有当提供商在代理上终止 TLS 时,才把 https://host:port 用作代理 URL。始终传入 timeout;否则一个停滞的代理会无限期阻塞调用。
requests.get 与 proxies · 单个请求
import requests
PROXIES = {
"http": "http://proxy.example.invalid:8080",
"https": "http://proxy.example.invalid:8080",
}
response = requests.get(
"https://example.com/",
proxies=PROXIES,
timeout=(10, 20),
)
print(response.status_code)使用 Session 让每个请求共享代理
Session 把连接池、Cookie 和代理设置放在一起。设置一次 session.proxies,通过该 Session 发出的每个请求都会使用这个网关,而单次调用上的 proxies 参数仍然可以覆盖它。也是在这里决定进程环境是否可以参与配置:trust_env=False 会阻止 Requests 读取 HTTP_PROXY、HTTPS_PROXY、NO_PROXY、netrc 凭据和 REQUESTS_CA_BUNDLE 变量,从而只由代码决定路由。
requests.Session · 共享的代理设置
import requests
with requests.Session() as session:
session.trust_env = False
session.proxies.update({
"http": "http://proxy.example.invalid:8080",
"https": "http://proxy.example.invalid:8080",
})
for url in ("https://example.com/", "https://example.com/robots.txt"):
response = session.get(url, timeout=(10, 20))
print(url, response.status_code)代理认证:把凭据编码进 URL
Requests 从代理 URL 本身发送代理凭据,形如 http://username:password@host:port;它会替你添加 Proxy-Authorization 请求头。auth 参数属于目标站点认证,不会到达代理。
分别对用户名和密码进行编码,使 @、: 或 / 这类字符无法改变 URL 结构,并让原始 PROXY_URL 不包含凭据。把下面的脚本保存为 proxy_check.py,在虚拟环境中用 python -m pip install requests 安装 Requests 之后,运行 python proxy_check.py。https://proxy.example.invalid:8443 只是一个无法工作的示例。
proxy_check.py · Python 3 + Requests
import os
from urllib.parse import quote, urlsplit, urlunsplit
import requests
gateway = urlsplit(os.environ["PROXY_URL"])
if gateway.scheme not in {"http", "https"} or not gateway.hostname:
raise ValueError("Use your provider's HTTP(S) proxy URL")
if gateway.username is not None or gateway.path not in {"", "/"} or gateway.query or gateway.fragment:
raise ValueError("Keep credentials and paths out of PROXY_URL")
username = quote(os.environ["PROXY_USERNAME"], safe="")
password = quote(os.environ["PROXY_PASSWORD"], safe="")
proxy = urlunsplit((gateway.scheme, f"{username}:{password}@{gateway.netloc}", "", "", ""))
with requests.Session() as session:
session.trust_env = False
with session.get(
"https://example.com/",
proxies={"http": proxy, "https": proxy},
timeout=(10, 20),
allow_redirects=False,
stream=True,
) as response:
response.raise_for_status()
print({"status": response.status_code})环境变量与 trust_env
当 trust_env 保持默认值 True 时,Requests 会通过 urllib.request.getproxies() 从环境中补齐你未设置的协议。它接受大写或小写的 HTTP_PROXY、HTTPS_PROXY、ALL_PROXY 和 NO_PROXY,在 macOS 上还会读取系统代理配置。这在工作站上很方便,但在容器中却是常见的意外来源:从镜像继承的 HTTPS_PROXY 会把你以为是直连的流量转走。
NO_PROXY 是以逗号分隔的主机名、域名后缀或 IP 地址列表,其中的条目绕过代理。对于路由必须可复现的任务,设置 trust_env=False 并显式传入 proxies;对于工作站脚本,设置变量并在调用 Requests 时不带 proxies 参数就足够了。
HTTPS_PROXY · trust_env 为 True 时自动读取
export HTTPS_PROXY='http://proxy.example.invalid:8080'
export NO_PROXY='localhost,127.0.0.1,.internal.example'
python -c 'import requests; print(requests.get("https://example.com/", timeout=(10, 20)).status_code)'SOCKS5 代理:requests[socks],socks5 与 socks5h 的区别
SOCKS 支持来自 PySocks 附加依赖:用 python -m pip install "requests[socks]" 安装,然后在同一个字典中使用 socks5:// 或 socks5h:// 代理 URL。socks5 在本地解析目标主机名并向代理发送 IP 地址;socks5h 发送主机名由代理解析,这通常是远程网关所期望的。用户名和密码放在 URL 中,方式与 HTTP 代理完全相同。
socks5h 代理 · requests[socks]
import requests
PROXIES = {
"http": "socks5h://proxy.example.invalid:1080",
"https": "socks5h://proxy.example.invalid:1080",
}
response = requests.get("https://example.com/", proxies=PROXIES, timeout=(10, 20))
print(response.status_code)在多个代理之间轮换
当任务需要把请求分散到多个网关时,保留一个 Session 用于连接池,并按请求选择 proxies 参数。示例循环遍历一个列表;真实任务会从配置中取值,记录每个请求由哪个网关服务,并在网关反复失败后将其下线而不是立即重试。连续请求是否应保持同一个出口地址是会话模型层面的决定,许多提供商通过用户名或专用端口来表达它。请遵循提供商的文档,而不是从一次成功的请求中推断。
itertools.cycle · 一个 Session,多个网关
from itertools import cycle
import requests
GATEWAYS = cycle([
"http://proxy-a.example.invalid:8080",
"http://proxy-b.example.invalid:8080",
])
with requests.Session() as session:
session.trust_env = False
for url in ("https://example.com/", "https://example.com/robots.txt"):
gateway = next(GATEWAYS)
response = session.get(url, proxies={"http": gateway, "https": gateway}, timeout=(10, 20))
print(gateway, url, response.status_code)弄清 timeout 度量的是什么
元组分别提供连接超时和读取超时。读取超时限制的是等待套接字数据的时间;它不是整个下载的总期限。当整个任务必须在固定时间内完成时,在任务运行器中另设一个墙钟预算。上面的诊断以流式方式打开并关闭响应,而不下载完整的响应体。
把 trust_env 设为 False 也会禁用 Requests 从环境派生的 CA 信任包设置。如果你的组织使用自定义信任包,用 verify 显式传入其批准的文件。不要用 verify=False 来让失败的测试通过。
解读错误:ProxyError、407 与超时
代理故障如何呈现取决于目标协议。对于 https:// 目标,故障发生在 CONNECT 隧道阶段,Requests 会抛出 requests.exceptions.ProxyError,其消息包含代理的回复,例如 Tunnel connection failed: 407 Proxy Authentication Required。对于普通 http:// 目标则没有隧道,因此 407 会作为一个 status_code 为 407 的普通 Response 到达,完全不会抛出异常;需要显式检查它。
在添加重试逻辑之前,先在 curl 中运行相同的网关和目标站点。记录异常类、尝试次数和耗时,并去掉凭据。可复现的认证错误需要修改配置;在更高并发下重复它只会增加噪音。
- 带 407 的 ProxyError:代理拒绝了 URL 中的凭据。重新检查编码和提供商的用户名格式。
- 带 Cannot connect to proxy 或名称解析消息的 ProxyError:网关主机或端口错误,或本网络无法访问它。
- ConnectTimeout:超时元组中的连接部分在代理或目标站点应答之前到期。ReadTimeout:连接成功,但在读取限制内没有数据到达。
- 隧道成功后的 SSLError:目标站点的证书验证失败,或代理正在拦截 TLS。保持验证开启并向提供商确认。
- 仅凭一次成功的 HTTP 请求无法验证出口的位置。
上线之前
- 使用项目虚拟环境。
- 将凭据与网关分开编码。
- 明确决定环境是否可以参与代理设置。
- 除网络超时外,再保留一个任务期限。
参考来源与延伸阅读
本指南参考的技术资料。请以你所安装版本的文档以及代理服务商支持的配置为准。