# 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` 以避免两种机制冲突：

```js
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@residentialpremium.quantumproxies.io:8080");

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 修复指南](https://quantumproxies.io/blog/python-requests-proxyerror-fix) 中的相似。

## 认证和环境变量

认证代理使用 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 时会悄然直接连接。

![同一请求的三段代码：axios 在 httpAgent 和 httpsAgent 上使用 HttpsProxyAgent 并设置 proxy: false，原生 fetch 在每次调用时传入 undici dispatcher，而 undici 的 setGlobalDispatcher 配合 ProxyAgent 可为整个进程设置代理。把它们混用是“proxy not working”的头号原因。](https://res.cloudinary.com/dmqliab0t/image/upload/f_auto,q_auto,w_1200/v1785335145/blog/generated/qp-b9-007-diag1.png)
*相同的请求，三种管道系统。Axios 需要代理，原生 fetch 需要 undici 调度器，混淆它们是“代理无法工作”的首要原因。*

## 使用 undici 代理原生 fetch

Node 的全局 `fetch` 基于 undici 构建，代理也在 undici 中。你创建一个 `ProxyAgent` 并将其作为非标准 `dispatcher` 选项传递——这是代理 fetch 的现代、轻依赖方式，开箱即用地正确处理 HTTPS CONNECT：

```js
import { ProxyAgent } from "undici";

const dispatcher = new ProxyAgent({
  uri: "http://residentialpremium.quantumproxies.io:8080",
  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 代理](https://quantumproxies.io/socks5-proxies) 计划通过 HTTP 和 SOCKS5 暴露相同的网关，因此这是方案的切换，而不是新的购买：

```js
import axios from "axios";
import { SocksProxyAgent } from "socks-proxy-agent";

const agent = new SocksProxyAgent("socks5h://USER:PASS@residentialpremium.quantumproxies.io:1080");

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，因此一个端点表现得像一个完整的池。通过 [旋转住宅代理](https://quantumproxies.io/rotating-proxies)，该池跨越 90M+ IP，覆盖 200+ 国家，下面的循环在每次迭代中打印不同的来源，无需轮换逻辑：

```js
import axios from "axios";
import { HttpsProxyAgent } from "https-proxy-agent";

const agent = new HttpsProxyAgent("http://USER:PASS@residentialpremium.quantumproxies.io:8080");
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 循环中移出，并阅读我们的 [大规模抓取架构指南](https://quantumproxies.io/blog/large-scale-web-scraping-architecture) 以获取队列和并发预算。

## 通过代理流式传输响应

一旦代理连接，下载文件或大型 JSON 负载与任何请求相同——设置 `responseType: "stream"` 并将主体写入磁盘。代理透明地处理传输，因此 200MB 的导出从不在内存中缓冲：

```js
import fs from "node:fs";

const r = await client.get("https://example.com/large.json", {
  responseType: "stream",
});
r.data.pipe(fs.createWriteStream("out.json"));
```

![一个 httpbin.org/ip 页面展示了 Node.js 中的四种代理错误：axios 的 proxy 配置返回你的真实 IP 或卡住、默认导入 agent 时抛出异常、在 axios 中使用 socks5:// 导致协议不匹配，以及 fetch 忽略 HTTP_PROXY。卡片给出了每个问题的修复方法，并有一条提示警告不要同时配置 agent 和 proxy。](https://res.cloudinary.com/dmqliab0t/image/upload/f_auto,q_auto,w_1200/v1785335146/blog/generated/qp-b9-007-diag2.png)
*大多数 Node.js 代理失败归结为五个错误：axios 的 HTTPS bug、默认代理导入、重复的代理配置、错误的 SOCKS 方案，或期望 fetch 读取环境变量。*

## 常见问题解答

### 为什么我的 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。

[获取首次请求即通过的住宅代理](https://quantumproxies.io/residential-proxies)
