Node.js 代理设置:Axios、Fetch、Undici 和轮换
axios 的代理选项在 HTTPS 目标上是个陷阱,而原生 fetch 完全忽略你的代理环境变量。以下是适用于 axios、fetch 和 undici 的设置——代理、认证、SOCKS5、轮换和流式传输。
通过代理路由 Node.js 请求看似简单,却可能导致一下午的调试。原因在于大多数项目使用的三个 HTTP 客户端——axios、Node 18 中作为全局提供的原生 fetch,以及底层的 undici 库——对代理的处理方式各不相同,且都不如预期。Axios 有一个内置的 proxy 配置,文档中有说明,仅适用于 Node,但在 HTTP 代理后的 HTTPS 目标上悄然失效:这是一个自 2020 年以来在 axios GitHub 追踪器上开放的 bug,并引发了一个高票的 Stack Overflow 讨论。与此同时,原生 fetch 忽略 HTTP_PROXY 环境变量,且没有任何代理选项。本指南为每个客户端提供了实际可行的设置,以及认证、SOCKS5、轮换和流式传输。
Axios 代理配置与代理代理的对比
Axios 附带一个 proxy 对象——{ host, port, auth },对于普通 HTTP 目标工作良好。陷阱在于 HTTPS:当目标是 https:// 且代理使用 HTTP 时,axios 无法打开 CONNECT 隧道,你的请求要么挂起,要么返回真实 IP。社区达成的解决方案是完全绕过内置配置,给 axios 提供一个代理代理,然后设置 proxy: false 以避免两种机制冲突:
import axios from "axios";
import { HttpsProxyAgent } from "https-proxy-agent";
// Note the destructured import — a default import is the classic gotcha.
const agent = new HttpsProxyAgent("http://USER:PASS@gate.quantumproxies.io:PORT");
const client = axios.create({
httpAgent: agent, // for http:// targets
httpsAgent: agent, // for https:// targets
proxy: false, // disable axios' own broken proxy handling
timeout: 15000,
});
const r = await client.get("https://httpbin.org/ip");
console.log(r.data); // { origin: "<proxy exit IP>" }
两个细节可以节省数小时。首先,使用解构的 { HttpsProxyAgent } 导入——最近的版本将其作为命名符号导出,而默认导入会给你一个在构造时抛出异常的对象。其次,即使代理传输 HTTPS 流量,代理 URL 方案仍保持 http://:方案描述如何到达代理,而到目标的 TLS 在隧道内运行。同样的故障类别也出现在 Python 中;如果你也使用 requests,模式与我们在 ProxyError 和 SSLError 修复指南 中的相似。
认证和环境变量
认证代理使用 HTTP 基本凭据。使用代理时,将它们嵌入 URL 中,如 http://user:pass@host:port;如果密码包含 @、: 或 /,先用 encodeURIComponent() 进行 URL 编码,否则 URL 会在错误位置拆分。407 Proxy Authentication Required 表示凭据被拒绝或你的源 IP 未被列入白名单。Axios 还会从环境中读取 HTTP_PROXY、HTTPS_PROXY 和 NO_PROXY——对于不想修改代码的第三方库代理非常方便——通过设置 proxy: false 来禁用它。重要警告:原生 fetch 不读取这些变量,因此依赖环境变量代理的抓取器在从 axios 切换到 fetch 时会悄然直接连接。

使用 undici 代理原生 fetch
Node 的全局 fetch 基于 undici 构建,代理也在 undici 中。你创建一个 ProxyAgent 并将其作为非标准 dispatcher 选项传递——这是代理 fetch 的现代、轻依赖方式,开箱即用地正确处理 HTTPS CONNECT:
import { ProxyAgent } from "undici";
const dispatcher = new ProxyAgent({
uri: "http://gate.quantumproxies.io:PORT",
token: "Basic " + Buffer.from("USER:PASS").toString("base64"),
});
const res = await fetch("https://httpbin.org/ip", { dispatcher });
console.log(await res.json());
如果你直接使用 undici 而不是全局 fetch,相同的 ProxyAgent 可以插入到 request() 或 setGlobalDispatcher() 中,一次性代理进程中的每个 fetch。这种单次调用的全局切换是通过代理路由整个代码库的最简洁方式,无需在每个函数中传递调度器。
Node.js 中的 SOCKS5 代理
axios 和 undici 都不原生支持 SOCKS——将 socks5:// 字符串传递给 axios 的 proxy 配置会导致 protocol mismatch 断言。安装 socks-proxy-agent 并以与 HTTPS 代理相同的方式使用它,连接到 httpAgent 和 httpsAgent。优先使用 socks5h:// 而不是 socks5://:结尾的 h 在代理端解析 DNS,防止 DNS 泄漏并从出口位置解析地理限制的主机名。每个 SOCKS5 代理 计划通过 HTTP 和 SOCKS5 暴露相同的网关,因此这是方案的切换,而不是新的购买:
import axios from "axios";
import { SocksProxyAgent } from "socks-proxy-agent";
const agent = new SocksProxyAgent("socks5h://USER:PASS@gate.quantumproxies.io:PORT");
const client = axios.create({ httpAgent: agent, httpsAgent: agent });
const r = await client.get("https://httpbin.org/ip");
console.log(r.data);
没有代理列表的轮换
旧的方案——一个 IP 数组,每次请求使用 Math.random(),删除无效的——是你不再维护的代码。一个旋转网关在每次请求时服务器端分配一个新的出口 IP,因此一个端点表现得像一个完整的池。通过 旋转住宅代理,该池跨越 90M+ IP,覆盖 200+ 国家,下面的循环在每次迭代中打印不同的来源,无需轮换逻辑:
import axios from "axios";
import { HttpsProxyAgent } from "https-proxy-agent";
const agent = new HttpsProxyAgent("http://USER:PASS@gate.quantumproxies.io:PORT");
const client = axios.create({ httpAgent: agent, httpsAgent: agent, proxy: false });
for (let i = 0; i < 3; i++) {
const r = await client.get("https://httpbin.org/ip");
console.log(r.data.origin); // a different exit IP each time
}
当一个流程跨越多个请求——登录、加入购物车、结账——每次请求的轮换会破坏会话。代理用户名中的粘性会话参数在设定窗口内固定一个出口 IP,然后轮换;相同的端点,只需更改一个字符串。如果你将此扩展到数千个并发请求,请将工作从 axios 循环中移出,并阅读我们的 大规模抓取架构指南 以获取队列和并发预算。
通过代理流式传输响应
一旦代理连接,下载文件或大型 JSON 负载与任何请求相同——设置 responseType: "stream" 并将主体写入磁盘。代理透明地处理传输,因此 200MB 的导出从不在内存中缓冲:
import fs from "node:fs";
const r = await client.get("https://example.com/large.json", {
responseType: "stream",
});
r.data.pipe(fs.createWriteStream("out.json"));

常见问题解答
为什么我的 axios 代理无法工作?
最常见的原因是 HTTP 代理后的 HTTPS 目标:axios 的内置 proxy 配置无法打开 CONNECT 隧道,返回你的真实 IP 或挂起。切换到代理代理——将 HttpsProxyAgent 附加到 httpAgent 和 httpsAgent,并设置 proxy: false 以停止 axios 自行处理代理。
axios 支持 SOCKS5 代理吗?
不原生支持——将 socks5:// URL 传递给 proxy 配置会抛出协议不匹配错误。安装 socks-proxy-agent,构建一个 SocksProxyAgent,并将其连接到 httpAgent 和 httpsAgent。使用 socks5h:// 方案,以便 DNS 在代理端解析,而不是从你的机器泄漏。
如何在 Node.js 中使用原生 fetch 代理?
原生 fetch 没有代理选项,并忽略 HTTP_PROXY 环境变量。创建一个 undici ProxyAgent 并将其作为 fetch 调用中的 dispatcher 选项传递,或调用 setGlobalDispatcher() 一次性代理进程中的每个 fetch。Undici 正确处理 HTTPS CONNECT,无需额外配置。
如何使用环境变量设置 axios 代理?
导出 HTTP_PROXY、HTTPS_PROXY 和可选的 NO_PROXY,带完整的代理 URL;axios 会自动读取它们。在请求或实例上设置 proxy: false 以使 axios 忽略环境。记住这只影响 axios——undici 和原生 fetch 不会读取这些变量。
整个故事可以写在卡片上:axios 需要代理和 proxy: false,原生 fetch 需要 undici 调度器,SOCKS 需要自己的代理,轮换应在网关而不是你的循环中进行。管道正确,最后一个变量是 IP 质量——干净的住宅出口通过,而标记的数据中心 IP 在相同代码上得到 403。