跳到正文

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 源码写明产生该错误的请求可以安全重试
ReadTimeoutGET 可以,POST 要谨慎请求可能已经到达网站
429 Too Many Requests可以,先等一会儿响应带 Retry-After 就按它等,之后放慢速度
502、503、504可以,几次通常是网关或网站一侧的临时问题
403、404不要结果不会变;该改请求或目标

依据 Requests 源码、urllib3 Retry 文档、MDN 和我们 2026 年 11 月 13 日的本地测试。标「可以」的行是指少量重试,不保证重试一定成功。

5 步排查并加固

  1. 第 1 步:读报错信息的结尾

    打印一次 str(exc)。407 看账号密码,Connection refused 或超时看地址、端口、协议,或资产是否已结束。

  2. 第 2 步:用 curl 测同一个代理

    用「我的资产」里的值运行 curl -x http://LOGIN:PASSWORD@HOST:2222 https://ipinfo.io/json。curl 能通而 Python 不通,就检查 proxies 字典和环境变量里的代理,它们可能覆盖 session.proxies。

  3. 第 3 步:每次调用都设超时

    不设超时,请求永远不会放弃。用元组,比如 timeout=(3.05, 20),分别设置连接和读取超时。

  4. 第 4 步:加上带退避的重试

    只处理状态码,挂一个 Retry 适配器(上面前一个代码块)。还要处理 ProxyError 并在 407 时停止,就用第二个代码块里的循环。

  5. 第 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
  1. Requests 源码:requests.exceptions

    官方文档 · 确认日期:

    2026 年 11 月 13 日阅读:ProxyError 继承自 ConnectionError;ConnectTimeout 同时继承 ConnectionError 和 Timeout,文档字符串写明产生该错误的请求可以安全重试;ReadTimeout 只继承 Timeout;RetryError 的说明是「自定义重试逻辑失败」。

  2. Requests 文档:高级用法

    官方文档 · 确认日期:

    2026 年 11 月 13 日阅读:Requests 默认不重试失败的连接;重试通过 HTTPAdapter(max_retries=...) 挂载 urllib3.util.Retry 实现;不设置超时就不会超时,用元组可分别设置连接和读取超时;代理地址可写成 http://user:password@host;session.proxies 可能被环境变量里的代理覆盖;Session 内的 keep-alive 是自动的。

  3. 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 决定状态码重试用尽后是否抛异常。

  4. MDN:429 Too Many Requests

    官方文档 · 确认日期:

    2026 年 11 月 13 日阅读:429 表示客户端在一段时间内发送了过多请求;响应里可能带 Retry-After,说明多久后再请求。

  5. 本页代码的本地测试

    本地示例 · 确认日期:

    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 则按设定次数放弃。

  6. Proxy302 产品信息与价格表

    产品观察 · 确认日期:

    站点产品信息表(价格按美元,1 PTC = 1 美元;协议与端口、租期;2026 年 11 月 4 日协议实测、11 月 10 日动态机房线路实测),2026 年 11 月 13 日核对。

准备好试试了吗?

注册账号、充值一次,就能选动态住宅、静态 ISP 或数据中心 IP,几分钟配进你的浏览器。