requests ProxyError:重试与退避写法
根据报错文字分清是账号错了还是连接断了,再把请求包起来,只重试值得重试的失败。
ProxyError 是什么意思,3 个常见原因
ProxyError 继承自 ConnectionError,也就是目标网站还没回应就已经出错。报错信息结尾的文字能告诉你是哪种情况:我们在本地测试中,账号被拒时结尾是 Tunnel connection failed: 407 Proxy Authentication Required,端口没开时结尾是 Connection refused。
原因 1:host、端口或协议写错。Proxy302 网关的 HTTP 端口是 2222,SOCKS5 端口是 3333;在 requests 里用 SOCKS5 地址还需要 pip install "requests[socks]"。原因 2:账号密码被拒(407),可能是敲错、漏了特殊字符,或者密码里的 @ 没在 URL 里做百分号编码。原因 3:资产已结束,比如租用的 IP 过了到期日,或流量余额用完。
不是所有失败都是 ProxyError。状态码 429 或 503 是网站或网关的正常回复,超时也是单独的异常,处理方式不同,见下表。
每种失败怎么处理
| 项目 | 要重试吗 | 怎么做 |
|---|---|---|
| ProxyError,结尾是 407 | 不要 | 改对账号密码后停止;每次重试都会再被拒 |
| ProxyError,Connection refused | 不要 | 检查 host、端口、协议,以及资产是否已结束 |
| ConnectTimeout | 可以,几次 | Requests 源码写明产生该错误的请求可以安全重试 |
| ReadTimeout | GET 可以,POST 要谨慎 | 请求可能已经到达网站 |
| 429 Too Many Requests | 可以,先等一会儿 | 响应带 Retry-After 就按它等,之后放慢速度 |
| 502、503、504 | 可以,几次 | 通常是网关或网站一侧的临时问题 |
| 403、404 | 不要 | 结果不会变;该改请求或目标 |
依据 Requests 源码、urllib3 Retry 文档、MDN 和我们 2026 年 11 月 13 日的本地测试。标「可以」的行是指少量重试,不保证重试一定成功。
5 步排查并加固
第 1 步:读报错信息的结尾
打印一次 str(exc)。407 看账号密码,Connection refused 或超时看地址、端口、协议,或资产是否已结束。
第 2 步:用 curl 测同一个代理
用「我的资产」里的值运行 curl -x http://LOGIN:PASSWORD@HOST:2222 https://ipinfo.io/json。curl 能通而 Python 不通,就检查 proxies 字典和环境变量里的代理,它们可能覆盖 session.proxies。
第 3 步:每次调用都设超时
不设超时,请求永远不会放弃。用元组,比如 timeout=(3.05, 20),分别设置连接和读取超时。
第 4 步:加上带退避的重试
只处理状态码,挂一个 Retry 适配器(上面前一个代码块)。还要处理 ProxyError 并在 407 时停止,就用第二个代码块里的循环。
第 5 步:限定次数并记录最后一次错误
临时故障 3 到 4 次尝试就够了。之后停下来,把最后一次的错误文字记下来,才看得到真正原因。
给状态码用的 Retry 适配器
import requests
from requests.adapters import HTTPAdapter
from urllib3.util import Retry
PROXY = "http://LOGIN:PASSWORD@HOST:2222"
PROXIES = {"http": PROXY, "https": PROXY}
retries = Retry(
total=3,
backoff_factor=1,
status_forcelist=[429, 502, 503, 504],
allowed_methods={"GET"},
)
session = requests.Session()
session.mount("https://", HTTPAdapter(max_retries=retries))
session.mount("http://", HTTPAdapter(max_retries=retries))
response = session.get("https://ipinfo.io/json", proxies=PROXIES, timeout=(3.05, 20))
print(response.status_code, response.text)仅为示例:把 LOGIN、PASSWORD、HOST 换成「我的资产」里的值;登录里的 @ 等特殊字符要做百分号编码。重试间隔为 backoff_factor * 2 ** 已重试次数。
遇到账号错误就停的重试循环
import random
import time
import requests
PROXY = "http://LOGIN:PASSWORD@HOST:2222"
PROXIES = {"http": PROXY, "https": PROXY}
RETRY_STATUS = {429, 502, 503, 504}
MAX_ATTEMPTS = 4
def fetch(url):
last_error = None
for attempt in range(1, MAX_ATTEMPTS + 1):
delay = min(2 ** attempt, 30) # 2, 4, 8, 16 seconds
try:
# a new Session opens a new connection, so a rotating line can give a new IP
with requests.Session() as session:
response = session.get(url, proxies=PROXIES, timeout=(3.05, 20))
if response.status_code not in RETRY_STATUS:
return response
retry_after = response.headers.get("Retry-After", "")
if retry_after.isdigit():
delay = min(int(retry_after), 60) # the server said how long to wait
last_error = RuntimeError(f"HTTP {response.status_code}")
except requests.exceptions.ProxyError as exc:
if "407" in str(exc):
raise # wrong login: retrying will not help
last_error = exc
except (requests.exceptions.ConnectTimeout, requests.exceptions.ReadTimeout) as exc:
last_error = exc
if attempt < MAX_ATTEMPTS:
time.sleep(delay + random.uniform(0, 1))
raise RuntimeError(f"gave up after {MAX_ATTEMPTS} attempts: {last_error}")
if __name__ == "__main__":
print(fetch("https://ipinfo.io/json").text)仅为示例,登录信息为占位符。每次尝试新建一个 Session,也就是新连接;轮换线路上,新连接会换新 IP。407 判断读取的是错误文字,已在 requests 2.32.5 的本地测试中确认。
在 Proxy302 上的重试与轮换
在轮换住宅线路上,每个新连接换一个 IP,但 requests.Session 会保持连接,同一个 Session 里的多次调用通常是同一个 IP。所以上面的循环每次尝试都新建 Session。静态 ISP 和机房 IP 在租期内不轮换,重试用的还是同一个 IP;1 天起价为机房 US$0.25、静态 ISP US$0.50。
尝试次数要少。按流量计费的线路上,每次到达网站的尝试都会消耗流量。没有免费试用,最小的购买是 1 GB 轮换住宅流量,或一个 IP 一天。
常见问题
requests.exceptions.ProxyError 是什么意思?
代理一侧的连接问题,是 ConnectionError 的子类。报错信息的结尾会说明是账号被拒(407)还是连接没能建立。
requests 默认会重试吗?
不会。文档写明 Requests 默认不重试失败的连接,要通过 HTTPAdapter 挂载 urllib3.util.Retry 来加。
Retry 会重试 POST 吗?
默认不会。allowed_methods 默认只含幂等方法:DELETE、GET、HEAD、OPTIONS、PUT、TRACE。只有重复发送是安全的,才加上 POST。
退避会等多久?
等待时间每次重试翻倍,上限为 backoff_max(默认 120 秒)。urllib3 文档里 factor 为 0.1 时的例子是 0.0、0.2、0.4、0.8 秒,也就是首次重试不等待。对 413、429、503 默认遵守 Retry-After。
为什么 ProxyError 只是偶尔出现?
偶发的连接失败很常见,重试两三次即可;如果每次都出现,就要找固定原因:账号密码、端口、资产已结束,或环境变量里的代理。
参考资料
6
- Requests 源码:requests.exceptions
官方文档 · 确认日期:
2026 年 11 月 13 日阅读:ProxyError 继承自 ConnectionError;ConnectTimeout 同时继承 ConnectionError 和 Timeout,文档字符串写明产生该错误的请求可以安全重试;ReadTimeout 只继承 Timeout;RetryError 的说明是「自定义重试逻辑失败」。
- Requests 文档:高级用法
官方文档 · 确认日期:
2026 年 11 月 13 日阅读:Requests 默认不重试失败的连接;重试通过 HTTPAdapter(max_retries=...) 挂载 urllib3.util.Retry 实现;不设置超时就不会超时,用元组可分别设置连接和读取超时;代理地址可写成 http://user:password@host;session.proxies 可能被环境变量里的代理覆盖;Session 内的 keep-alive 是自动的。
- urllib3 文档:urllib3.util.Retry
官方文档 · 确认日期:
2026 年 11 月 13 日阅读:backoff_factor 默认关闭(0);等待时间为 backoff_factor * (2 ** 已重试次数),上限 backoff_max(默认 120 秒);status_forcelist 默认关闭;allowed_methods 默认只含幂等方法 DELETE、GET、HEAD、OPTIONS、PUT、TRACE;对 413、429、503 默认遵守 Retry-After;raise_on_status 决定状态码重试用尽后是否抛异常。
- MDN:429 Too Many Requests
官方文档 · 确认日期:
2026 年 11 月 13 日阅读:429 表示客户端在一段时间内发送了过多请求;响应里可能带 Retry-After,说明多久后再请求。
- 本页代码的本地测试
本地示例 · 确认日期:
2026 年 11 月 13 日用 requests 2.32.5、urllib3 2.6.3 对本地模拟代理的测试服务器运行:对 CONNECT 回 407 时抛出 ProxyError,结尾是 Tunnel connection failed: 407 Proxy Authentication Required;端口关闭时抛出 ProxyError,结尾是 Connection refused;代理一直回 503 时 Retry 抛出 RetryError;本页的 fetch() 遇到 407 立即抛出,遇到 503 则按设定次数放弃。
- Proxy302 产品信息与价格表
产品观察 · 确认日期:
站点产品信息表(价格按美元,1 PTC = 1 美元;协议与端口、租期;2026 年 11 月 4 日协议实测、11 月 10 日动态机房线路实测),2026 年 11 月 13 日核对。
准备好试试了吗?
注册账号、充值一次,就能选动态住宅、静态 ISP 或数据中心 IP,几分钟配进你的浏览器。