实操教程2026年9月29日阅读约 8 分钟

代理超时怎么排查:找出时间花在哪

代理超时和代理连接失败,可能发生在连上代理、代理连网站、等网站回复这三段中的任何一段。用一条 curl 命令分清是哪段,再设好超时。

代理超时,说明三段路程中有一段花的时间太长:你的机器连到代理、代理连到网站、网站把答复发回来。每一段失败时报的错不一样,修法也不一样。错误信息通常已经告诉你是哪一段,curl -w 则能显示每一段各花了多久。

这篇是一条排查路线:从你手上的报错出发,用一条计时命令缩小范围,再按你用的产品线设置合适的超时。所有代码都用 HOST、PORT、USERNAME 和 PASSWORD 作占位符,对应控制台服务页面上的值。

代理超时可能发生在哪里?

经过代理的请求会建立两条连接。客户端先连到位于 HOST:PORT 的代理。访问 https 网站时,客户端再请代理建立一条到网站的 CONNECT 隧道,然后通过隧道和网站进行 TLS 通信。所以时间可能花在三个地方:

  1. 你到代理。 一条到 HOST:PORT 的 TCP 连接,正常应该在几毫秒到几百毫秒之间。
  2. 代理到网站。 代理解析网站域名,通过出口 IP 连出去,再告诉你隧道有没有建好。
  3. 网站的答复。 网站处理请求,再把页面经出口发回来。

第 1 段超时,跟你的网络、你的设置或代理地址有关。第 2 段跟目标网站或出口有关。第 3 段通常是目标网站慢,偶尔是出口慢。

第一步:读懂你手上的报错

动手改之前,先把报错对应到某一段。下面是 curl 8、requests 和 httpx 的真实报错,不同版本措辞略有差异。

第 1 段:你的机器连不上代理

  • curl: (7) Failed to connect to HOST port PORT ... Couldn't connect to server、Node 里的 ECONNREFUSED,或者 requests 的 ProxyError 里出现 NewConnectionError ... Connection refused。对方有回应,但拒绝了连接。主机或端口填错了,或者那里没有为你服务的程序。从服务页面重新复制这两个值,并确认服务仍显示为有效。
  • curl: (28) Failed to connect to HOST port PORT after 5001 ms: Timeout was reached、ProxyError 里的 ConnectTimeoutError,或者 httpx.ConnectTimeout。完全没有回应。常见原因是你所在网络(公司、学校、云服务器安全组)的防火墙丢弃了到该端口的出站连接,或者主机填错到了一个不存在的地方。
  • curl: (5) Could not resolve proxy: HOST,或者 requests 里的 NameResolutionError。代理主机名有拼写错误,或者你本地的 DNS 解析出了问题。

第 2 段:代理连不上网站

  • curl: (56) CONNECT tunnel failed, response 502(或 503)、requests 里的 Tunnel connection failed: 502 Bad Gateway,或者 httpx.ProxyError: 502 Bad Gateway。你连上了代理,代理也接受了你,但它打不开到网站的连接。可能是网站宕机、网站拒绝了这个出口,或者域名写错了。用同一组凭据访问 https://api.ipify.org 试试:能通的话,代理没问题,问题在出口和目标网站之间。
  • CONNECT tunnel failed, response 407 不是超时,而是代理拒绝了你的登录,排查方法见407 代理认证错误怎么解决。

第 3 段:连上了,但迟迟没有答复

  • curl: (28) Operation timed out after 30001 milliseconds with 0 bytes received、requests 里的 ReadTimeout、httpx.ReadTimeout。连接已经建立,但没人说话。可能是网站回复慢、出口慢,或者代理卡在了隧道上。

注意 curl 的错误 28 在两段里都会出现。“Failed to connect ... Timeout was reached” 属于第 1 段,“Operation timed out ... with 0 bytes received” 属于第 3 段。看数字后面那句话就能分清。

requests 还有一个坑:设置了代理后,连不上代理会报 ProxyError,而不是 ConnectTimeout,即使真正的原因是超时。请读报错里 “Caused by” 后面的部分,那里才是真正的原因。

第二步:用 curl -w 拆分时间

curl 可以打印请求每个阶段的时间点。经代理运行一次:

curl -s -o /dev/null -x "http://USERNAME:PASSWORD@HOST:PORT" \
  -w 'dns        %{time_namelookup}s\nconnect    %{time_connect}s\ntunnel+tls %{time_appconnect}s\nfirst byte %{time_starttransfer}s\ntotal      %{time_total}s\nstatus     %{http_code}\n' \
  https://httpbin.org/ip

正常的一次大致是这样:

dns        0.000017s
connect    0.000109s
tunnel+tls 0.342913s
first byte 0.455784s
total      0.455814s
status     200

每个数字都是从请求开始算起的累计时间,所以要看的是它们之间的差:

  • connect 是与代理的 TCP 连接完成的时间点。这里偏高,说明是第 1 段:你的网络到代理。
  • tunnel+tls 减 connect 包括代理连接网站,以及通过隧道完成 TLS 握手。这里偏高,说明是第 2 段。
  • first byte 减 tunnel+tls 是网站处理请求、第一批数据经出口返回的时间。这里偏高,说明是第 3 段。
  • total 减 first byte 是下载时间。小页面在这里偏高,说明出口慢;大页面偏高,就是页面本身大。

设置代理后,dns 只是解析 HOST 的时间。网站的域名由代理自己解析,你本地的 DNS 根本不会去查目标网站。

然后把网址换成你的目标网站再跑一次。如果 httpbin 很快,而目标网站的 first byte 差值很长,说明这个网站无论走哪条路都回复得慢,或者只对这个出口慢。去掉 -x 直连跑一次对比一下。

是代理慢,还是目标网站慢?

多测几次。用一个循环,规律一眼就能看出来:

for i in 1 2 3 4 5; do
  curl -s -o /dev/null -x "http://USERNAME:PASSWORD@HOST:PORT" \
    -w 'connect %{time_connect}  first byte %{time_starttransfer}  total %{time_total}  (%{http_code})\n' \
    https://httpbin.org/ip
done

住宅代理设成“随机 IP”时,每次运行都从不同的家庭网络出去。一堆快的里面夹着一行慢的,是那个出口慢,重试就会换到别处。如果访问目标网站每一行都慢,访问 httpbin 却很快,那就是目标网站的问题。ISP 和数据中心 IP 是静态的,每次都是同一个地址:只在某一个网站上一直慢、别的网站都正常,可能是这个网站在对这个地址限速。

超时该设多少?

设两个数,而不是一个。连接超时设短一些,能快速发现第 1 段的问题,因为正常的代理接受连接很快。读取超时设长一些,给慢出口和慢网站留出余地。术语表里的超时词条一句话概括:住宅代理多留些时间,认定失败之前先重试一次。

requests 如果不传超时,根本没有超时,一次卡住的读取可以让一个工作进程永远挂着。请传一个 (连接, 读取) 元组:

import time

import requests

PROXY = "http://USERNAME:PASSWORD@HOST:PORT"
PROXIES = {"http": PROXY, "https": PROXY}


def probe(url):
    start = time.monotonic()
    try:
        r = requests.get(url, proxies=PROXIES, timeout=(5, 30))
        return f"{r.status_code} after {time.monotonic() - start:.1f}s"
    except requests.exceptions.ProxyError as error:
        return f"proxy leg failed: {error}"
    except requests.exceptions.ReadTimeout:
        return "connected, then no reply in time"
    except requests.exceptions.ConnectionError as error:
        return f"connection failed: {error}"


print(probe("https://api.ipify.org"))
print(probe("TARGET_URL"))

读取超时指的是两次收到数据之间的最长间隔,而不是整个下载的上限,所以一个慢慢往外挤的页面,总耗时可能超过 30 秒。把 IP 回显服务和你的目标网站放在一起测,就能看出是哪一边慢。

httpx 正好相反:它有默认超时,而且每个阶段默认只有 5 秒。对要抓取大页面的住宅出口来说偏紧。把读取调长,连接保持较短:

import httpx

PROXY = "http://USERNAME:PASSWORD@HOST:PORT"
timeout = httpx.Timeout(30.0, connect=5.0)

with httpx.Client(proxy=PROXY, timeout=timeout) as client:
    r = client.get("https://api.ipify.org")
    print(r.text)

并发和你自己的机器

只有任务全速运行时才超时、手动测一个请求又好好的,问题通常在你这边。同时进行的请求太多(并发),会耗尽连接池、文件描述符或上行带宽。在 httpx 里,这表现为 httpx.PoolTimeout,也就是在等空闲连接。把工作进程数减半,看看超时率会不会下降。如果下降了,瓶颈从来就不是代理。

高并发也会给目标网站更大压力,而压力下的网站回复变慢甚至不再回复,从你这边看就像第 3 段的问题。

重试,但要有上限

超时是最适合带退避重试的情况。住宅代理设成“随机 IP”时,重试会换一个新出口,往往这样就够了。但要限制次数:一个已经不回复的网站,不会因为你多问五次就回得更快。退避的代码见处理封锁和重试,别白白烧掉代理流量。

在我们的住宅代理用量表上,连接失败(超时、连接重置,以及我们自己网关返回的错误,比如 407 或 502)按零计费,并且仍然显示在日志里,标记为免费。网站发回来的任何东西都算流量,429 和 5xx 页面也不例外,因为在 HTTPS 隧道里,我们的代理看不到状态码,只看得到字节。规则见计量部分。

去 Discord 求助时带上什么

如果按上面的步骤走完,看起来仍然是代理的问题,请到 Discord 提问,并附上:

  • 发生的时间,带时区。 用 UTC 最省事。
  • 出口 IP,如果请求走到了有出口的那一步。用同一组凭据访问 https://api.ipify.org 就能打印出来。
  • curl -v 的输出,删掉 Proxy-Authorization 那一行和 -x 后面的网址。那一行就是 Base64 编码的密码。
  • 上面 curl -w 的计时,经代理和直连各一份。
  • 用的是哪项服务(住宅、ISP 还是数据中心)、目标网站的域名,以及同时运行多少个请求。

有了这些,别人不用来回追问,就能在日志里找到对应的记录。

快速问答

requests 报 “ProxyError: Max retries exceeded” 是什么意思? urllib3 经代理连接失败,放弃了。“Caused by” 后面写着原因:Connection refused 和 ConnectTimeoutError 属于第 1 段,Tunnel connection failed: 502 属于第 2 段,407 是登录问题。

为什么网站在浏览器里能打开,经代理却超时? 浏览器是直连的,完全跳过了第 1 段和第 2 段。带 -x 和不带 -x 各跑一次 curl -w 命令,看看多出来的时间在哪一段。

代理连接失败(connection refused)算超时吗? 不算。被拒绝说明对方有回应,而且很快就说了不。几乎都是主机或端口填错了。超时则是完全没有回应。

超时从多少开始设? 住宅代理用 5 秒连接超时、30 秒读取超时是合理的起点。数据中心和 ISP 的出口通常更快,拿到自己目标网站的计时数据后可以再收紧。

还有后续问题?

去 Discord 问。答案还能帮到下一个读到这个帖子的人。

加入 Discorddiscord.gg/proxypanda
$5 起步去 Discord 问