クイックアンサー
Node.jsでローテーティングプロキシはどう使う?
fetchならundici ProxyAgent をhttp://gw.crawlrelay.com:7777向けに作成し dispatcher として渡す。axiosやgotなら https-proxy-agent または hpagent のようなプロキシエージェントを渡し、axiosではproxy: falseを設定。CrawlRelayのレジデンシャルプロキシは国とセッションをユーザー名から読み取り、$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;プールされた接続はそのままエグジットを保持 |
| モジュール形式 | 例としてはトップレベルawaitを持つESモジュール(.mjsまたは"type": "module")があります |
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でプロキシを使うには?
HTTPSサイトに対してはaxiosのproxyオプションに頼らないでください:これを使うと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はプロトコルごとにエージェントを1つ使います。hpagentはNodeのhttp.Agentに従います:keepAlive: trueを渡さない限りkeep-aliveはオフです。keep-aliveはオフのままにするか、以下のように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を使うには?
socks-proxy-agentをsocks5h://スキーム付きで使い、ホスト名は出口で解決させます。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が2回出る、またはECONNRESETが起こるのですか?
エージェントは接続をプールし、プールされた接続は開かれた出口を保持します。リクエストごとに新しいIPを使いたい場合は、リクエストごとに新しいエージェントを作成し終了時に閉じるか、エージェントでkeep-aliveをオフにするか、各リクエストのユーザー名に一意の-session-<id>を入れてください。
ECONNRESETとsocket hang upは通常、既に相手側で閉じられたプールされたソケットを再利用したか、出口が切断されたことによります。GETリクエストは新しい接続で一度だけリトライしてください。接続リセットについてのページが詳細を説明しています。
Node.jsとプロキシ:よくある質問
Node.jsのfetchはHTTP_PROXYをサポートしますか?
現在多くのプロジェクトで使われているバージョンではデフォルトでサポートしていません:グローバルfetchはプロキシ環境変数を無視します。ディスパッチャーを渡す必要があります。上記のようにundiciのProxyAgentを使うか、プロセス内の全リクエストでプロキシを使うならundiciのsetGlobalDispatcher()でグローバルディスパッチャーを設定してください。
なぜHTTPSサイトでのaxiosはプロキシ越しに失敗する?
axiosの組み込みproxyオプションはHTTPSリクエストを多くのプロキシが拒否する形式でプロキシに送ります。CONNECTトンネルを開きません。proxy: falseを設定し、httpsAgentとしてHttpsProxyAgentを渡してください。リクエストは正しいトンネル経由で送られ、TLSはコードとサイト間に留まります。
1つのNode.jsプロセスで複数国を送るには?
国ごとにエージェントを1つ作成し、それぞれにUSERNAME-country-deやUSERNAME-country-jpのような異なるユーザー名を付け、リクエストごとにエージェントを選択します。エージェントは安価で、分けておけば接続プールや出口が別々に保持されます。
Node.jsでどのタイムアウトを設定すべき?
常に設定してください:fetchはAbortSignal.timeout(30000)、axiosはtimeout: 30000、gotはtimeout: { request: 30000 }。HTMLページなら30秒が適当です。遅いターゲットや4Gモバイルプロキシ出口なら最大60秒とし、長く待つ代わりに新しい接続で1回リトライしましょう。
Node.jsのクロールツールを接続
10 GB用の$5.94、50 GBからの$0.50/GB。