快速回答
如何在 Node.js 里使用旋转代理?
使用 fetch 时创建 undici ProxyAgent 用于 http://gw.crawlrelay.com:7777 并作为 dispatcher 传入。axios 或 got 传入代理代理如 https-proxy-agent 或 hpagent,并在 axios 里设置 proxy: false。CrawlRelay 住宅代理通过用户名读取国家和会话,来自 US$0.50/GB。支持加密货币支付,无需 KYC。
Node.js 配置速览
| fetch / undici | new ProxyAgent({ uri, token }),然后 fetch(url, { dispatcher }) |
|---|---|
| axios | httpsAgent: new HttpsProxyAgent(url) 和 proxy: false |
| got | 从 hpagent 获取 agent: { https: new HttpsProxyAgent({ proxy }) } |
| SOCKS5 | 使用端口 7778 的 socks5h:// URL 获取 socks-proxy-agent |
| 每次请求使用新 IP | 每个请求使用新的代理或新的会话 ID;池化连接保持其出口 |
| 模块格式 | 示例为 ES 模块(.mjs 或 "type": "module"),支持顶层 await |
如何在 Node 的 fetch 中使用代理?
Node 的全局 fetch 基于 undici,但默认不支持读取 HTTP_PROXY 或 HTTPS_PROXY。安装 undici 并传入 ProxyAgent 作为调度器。token 字段通过 Proxy-Authorization 头部发送凭据,因此密码不会出现在可能被记录的 URL 中。
import { ProxyAgent, fetch } from "undici";
const credentials = Buffer.from("USERNAME-country-us:PASSWORD").toString("base64");
const dispatcher = new ProxyAgent({
uri: "http://gw.crawlrelay.com:7777",
token: `Basic ${credentials}`,
});
const res = await fetch("https://api.ipify.org?format=json", {
dispatcher,
signal: AbortSignal.timeout(30000),
});
console.log(res.status, await res.json());
await dispatcher.close();如何在 axios 中使用代理?
不要依赖 axios 自带的 proxy 选项用于 HTTPS 站点:它会直接发送请求到代理而不建立 CONNECT 隧道,很多代理会拒绝。需禁用该选项(proxy: false),并传入一个可以打开正确隧道的代理。
import axios from "axios";
import { HttpsProxyAgent } from "https-proxy-agent";
const agent = new HttpsProxyAgent("http://USERNAME-country-de:[email protected]:7777");
const client = axios.create({ httpsAgent: agent, httpAgent: agent, proxy: false, timeout: 30000 });
const { data } = await client.get("https://api.ipify.org?format=json");
console.log(data.ip);如何在 got 中使用代理?
got 每种协议只接受一个代理。hpagent 遵循 Node 的 http.Agent:默认关闭 keep-alive,除非传入 keepAlive: true。想让每个请求使用新连接,也就是通常用新出口时,关闭它或按下面设置的 keepAlive: false。
import got from "got";
import { HttpsProxyAgent } from "hpagent";
const proxy = "http://USERNAME-country-fr:[email protected]:7777";
const body = await got("https://api.ipify.org", {
agent: { https: new HttpsProxyAgent({ proxy, keepAlive: false }) },
timeout: { request: 30000 },
retry: { limit: 2, statusCodes: [429, 502, 503, 504] },
}).text();
console.log(body);如何在 Node.js 中使用 SOCKS5?
使用带有 socks5h:// 协议的 socks-proxy-agent ,这样主机名会在出口解析。它兼容 Node 的 http 和 https 模块,也支持接受代理的任何库。
import https from "node:https";
import { SocksProxyAgent } from "socks-proxy-agent";
const agent = new SocksProxyAgent("socks5h://USERNAME-country-gb:[email protected]:7778");
const req = https.get("https://api.ipify.org", { agent, timeout: 30000 }, (res) => {
let body = "";
res.on("data", (chunk) => (body += chunk));
res.on("end", () => console.log(res.statusCode, body));
});
req.on("timeout", () => req.destroy(new Error("timeout after 30 s")));
req.on("error", (err) => console.error(err.code, err.message));如何在不借助库的情况下限制每个主机的并发数?
Promise.all 在 10,000 个 URL 上会同时打开 10,000 个请求。对每个主机设置小并发限制可以让每个网站保持合理负载,同时整个爬取任务仍是并行的。以下版本无依赖,兼容任意客户端。
import { ProxyAgent, fetch } from "undici";
const PER_HOST = 4;
const active = new Map();
const waiting = new Map();
const dispatcher = new ProxyAgent("http://USERNAME-country-us:[email protected]:7777");
async function withHostSlot(url, task) {
const host = new URL(url).hostname;
while ((active.get(host) ?? 0) >= PER_HOST) {
await new Promise((resolve) => {
if (!waiting.has(host)) waiting.set(host, []);
waiting.get(host).push(resolve);
});
}
active.set(host, (active.get(host) ?? 0) + 1);
try {
return await task();
} finally {
active.set(host, active.get(host) - 1);
waiting.get(host)?.shift()?.();
}
}
const urls = ["https://example.com/", "https://example.org/", "https://example.net/"];
const results = await Promise.all(
urls.map((url) =>
withHostSlot(url, async () => {
const res = await fetch(url, { dispatcher, signal: AbortSignal.timeout(30000) });
return [url, res.status, (await res.arrayBuffer()).byteLength];
}).catch((err) => [url, err.name, 0])
)
);
console.table(results);
await dispatcher.close();为什么我会遇到重复 IP 或 ECONNRESET?
代理池化连接,连接保持其打开时的出口。要实现每次请求新 IP,需要每次请求创建新代理并在用后关闭,或者在代理中关闭 keep-alive,或者在每个请求的用户名中放入唯一-session-<id>。
ECONNRESET 和 socket hang up 通常来自重用已被远端关闭的池化连接,或出口掉线。GET 请求遇到此类错误时,重试一次新连接。连接重置页面 有详细介绍。
Node.js 与代理:常见问题
Node.js 的 fetch 支持 HTTP_PROXY 吗?
当前大多数项目常用版本默认不支持:全局 fetch 会忽略代理环境变量,除非传入调度器。使用上述的 undici ProxyAgent,或者如果希望进程内每个请求都通过代理,则用 undici 的 setGlobalDispatcher() 设置全局调度器。
为什么 axios 代理 HTTPS 站点时会失败?
axios 内建的 proxy 选项把 HTTPS 请求以许多代理拒绝的方式发送给代理,未建立 CONNECT 隧道。需设置 proxy: false 并传入 HttpsProxyAgent 作为 httpsAgent。请求才会通过正确隧道,TLS 保持在你的代码和目标站点之间。
如何用一个 Node.js 进程发送多个国家的请求?
为每个国家创建一个代理,每个代理使用不同用户名如 USERNAME-country-de 或 USERNAME-country-jp,每个请求选用不同代理。代理成本低,分开存储连接池,从而分开出口。
在 Node.js 中我该设置哪个超时?
务必设置超时:fetch 用 AbortSignal.timeout(30000),axios 用 timeout: 30000,got 用 timeout: { request: 30000 }。HTML 页面用 30 秒足够;慢目标和移动出口可达 60 秒,且建议遇超时在新连接上重试一次,而非等待更久。
连接你的 Node.js 爬虫
US$5.94 适用于 10 GB,US$0.50/GB 来自 50 GB。