每個 Node.js HTTP 客戶端處理 proxy 的方式都不一樣,而其中有一半在沒有輔助套件的情況下根本不處理 proxy 驗證。axios 在某些版本中會默默忽略自己針對 HTTPS 目標的 proxy 選項,原生 fetch 完全沒有 proxy 選項,而那些錯誤訊息 — ECONNRESET、407,或乾脆就是卡住不動 — 什麼都沒告訴你。
這是一份完整、可用的參考文件,教你如何把 axios、got、node-fetch、原生 fetch(undici)以及 superagent 透過需驗證的住宅 proxy 來路由。它是我們 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, // for http:// targets
httpsAgent: agent, // for https:// targets
proxy: false, // IMPORTANT: disable axios's own proxy handling
});
console.log(res.data); // -> the proxy's exit IP
大家常漏掉的兩件事:你必須同時設定 httpAgent 與 httpsAgent(axios 會依目標的 scheme 來挑選),而且你必須設定 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,會忽略 proxy 環境變數,也沒有 agent 選項。undici 的做法是用 dispatcher:
const { ProxyAgent } = require("undici");
// undici's ProxyAgent speaks HTTP CONNECT (use your HTTP proxy port)
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 proxy。若要在原生 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 清單 — 閘道會在每次連線時給你一個新的出口,或為每個工作階段 id 固定一個出口。在 Node 中,這乾淨地對應到每個身分一個 agent:
// Sticky: same session id -> same exit IP across requests
function identityAgent(sessionId) {
return new SocksProxyAgent(
`socks5h://USERNAME-session-${sessionId}:[email protected]:913`
);
}
// Account A keeps IP A, account B keeps IP B - cookies and IP move together
const agentA = identityAgent("acct_a");
const agentB = identityAgent("acct_b");
在同一個身分內重用 agent 以做連線池化;千萬別在不同身分之間共用同一個 agent。何時該輪換、何時該固定本身就是另一個主題 — 參見黏性 vs 輪換工作階段。
| 症狀 | 原因 | 修法 |
|---|---|---|
407 Proxy Authentication Required | 憑證沒有送達 proxy | 把 user:pass 放進 agent URL,而不是客戶端設定 |
| axios 在 http:// 可用,在 https:// 失敗 | 只設了 httpAgent | 也要設 httpsAgent,並加上 proxy: false |
立刻出現 ECONNRESET | 連接埠錯誤/協定錯誤(用 HTTP 連接埠搭配 SOCKS agent) | 讓 agent 類型與連接埠相符 |
| DNS 洩漏/內部主機名稱失敗 | socks5:// 會在本機解析 DNS | 使用 socks5h:// — 那個 h 會把主機名稱透過 proxy 送出 |
| 原生 fetch 忽略 HTTP_PROXY | undici 不會讀取環境變數 | 明確傳入一個 dispatcher |
| 本機可用,但目標站台回 403 | 不是 proxy 的問題 — 是 TLS 指紋 | 用 got-scraping 或真正的瀏覽器;參見 TLS 指南 |
socks-proxy-agent / https-proxy-agent);別相信內建的 proxy 選項。proxy: false。got:agent: {http, https}。原生 fetch:undici 的 ProxyAgent dispatcher。socks5h:// 讓 DNS 透過 proxy 解析 — 不會洩漏。新用戶註冊即送 500M免費流量,首次儲值另有贈金。優惠限時供應。