每个 Node.js HTTP 客户端处理代理的方式都不一样,其中一半在不借助辅助包的情况下根本不处理代理认证。axios 在某些版本里对 HTTPS 目标会悄悄忽略它自己的 proxy 选项,原生 fetch 压根没有代理选项,而那些错误信息——ECONNRESET、407,或者干脆卡住不动——什么有用的都告诉不了你。
这是一份让 axios、got、node-fetch、原生 fetch(undici)和 superagent 走带认证住宅代理的完整可用参考。它是我们 Python 系列指南(requests/httpx/aiohttp、Scrapy)的 Node 配套篇。所有示例都用标准占位格式——换成你自己的真实凭证即可:
socks5h://USERNAME:[email protected]:913
别用内置的 proxy 选项。用 proxy agent。Node 的 HTTP 客户端把连接处理委托给一个 "agent" 对象,而 agent 包(https-proxy-agent、socks-proxy-agent)能正确实现 CONNECT 隧道和认证。内置选项往往做不到——axios 的 proxy 配置就是最臭名昭著的例子。
npm install socks-proxy-agent https-proxy-agent
const axios = require("axios");
const { SocksProxyAgent } = require("socks-proxy-agent");
const agent = new SocksProxyAgent(
"socks5h://USERNAME:[email protected]:913"
);
const res = await axios.get("https://api.ipify.org?format=json", {
httpAgent: agent, // 用于 http:// 目标
httpsAgent: agent, // 用于 https:// 目标
proxy: false, // 重要:关掉 axios 自己的代理处理
});
console.log(res.data); // -> 代理的出口 IP
大家常漏掉两点:你必须同时设置 httpAgent 和 httpsAgent(axios 按目标协议来选),并且必须设 proxy: false,免得 axios 在你的 agent 之上又去套用 HTTP_PROXY 环境变量。
const got = require("got");
const { SocksProxyAgent } = require("socks-proxy-agent");
const agent = new SocksProxyAgent(
"socks5h://USERNAME:[email protected]:913"
);
const body = await got("https://api.ipify.org?format=json", {
agent: { http: agent, https: agent },
}).json();
如果你要用 got 抓受保护的目标,去看 got-scraping 吧——API 一样,还附带类浏览器的请求头生成和 HTTP/2 指纹模拟(为什么这很重要:JA3/JA4 解析)。
Node 内置的 fetch 来自 undici,会忽略代理环境变量,也没有 agent 选项。undici 的做法是用 dispatcher:
const { ProxyAgent } = require("undici");
// undici 的 ProxyAgent 走 HTTP CONNECT(用你的 HTTP 代理端口)
const dispatcher = new ProxyAgent({
uri: "http://us.jibaoproxy.com:1000",
token: "Basic " + Buffer.from("USERNAME:PASSWORD").toString("base64"),
});
const res = await fetch("https://api.ipify.org?format=json", { dispatcher });
console.log(await res.json());
注意:undici 的 ProxyAgent 只支持 HTTP 代理。原生 fetch 要走 SOCKS5,要么在前面架一个本地转发器,要么改用接受 socks agent 的客户端(上面的 axios/got)。
const fetch = require("node-fetch");
const { SocksProxyAgent } = require("socks-proxy-agent");
const agent = new SocksProxyAgent(
"socks5h://USERNAME:[email protected]:913"
);
const res = await fetch("https://api.ipify.org?format=json", { agent });
用轮换住宅网关,你不用管理 IP 列表——网关每条连接给你一个全新出口,或者按 session id 锁定一个出口。在 Node 里这干净地对应到每个身份一个 agent:
// Sticky:相同 session id -> 跨请求保持同一出口 IP
function identityAgent(sessionId) {
return new SocksProxyAgent(
`socks5h://USERNAME-session-${sessionId}:[email protected]:913`
);
}
// 账号 A 保持 IP A,账号 B 保持 IP B —— cookie 和 IP 一起移动
const agentA = identityAgent("acct_a");
const agentB = identityAgent("acct_b");
在一个身份内复用 agent 以利用连接池;千万别跨身份共享同一个 agent。什么时候该轮换、什么时候该锁定是另一个话题——见 sticky vs 轮换会话。
| 症状 | 原因 | 修法 |
|---|---|---|
407 Proxy Authentication Required | 凭证没传到代理 | 把 user:pass 放进 agent 的 URL 里,别放在客户端配置里 |
| axios 在 http:// 上能用,在 https:// 上失败 | 只设了 httpAgent | 也要设 httpsAgent,并加 proxy: false |
立刻 ECONNRESET | 端口错 / 协议错(HTTP 端口配 SOCKS agent) | 让 agent 类型和端口匹配 |
| DNS 泄漏 / 内网主机名解析失败 | socks5:// 在本地解析 DNS | 用 socks5h://——那个 h 会把主机名交给代理解析 |
| 原生 fetch 忽略 HTTP_PROXY | undici 不读环境变量 | 显式传入 dispatcher |
| 本地能跑,目标站点返回 403 | 不是代理的锅——是 TLS 指纹 | 用 got-scraping 或真实浏览器;见 TLS 指南 |
socks-proxy-agent / https-proxy-agent);绝不信内置的代理选项。proxy: false。got:agent: {http, https}。原生 fetch:undici 的 ProxyAgent dispatcher。socks5h:// 让 DNS 走代理解析——不泄漏。新用户注册即送500M免费流量,首次充值额外加赠,活动期间限时开放。