Node.jsでプロキシを使用するには何が必要ですか?
Node.jsに内蔵されているfetchはundiciというライブラリで動作し、デフォルトではプロキシ設定を読みません。そのため、HTTPプロキシはundiciのProxyAgentをdispatcherとして渡し、SOCKS5プロキシはsocks-proxy-agentをNodeのhttpsモジュールにエージェントとして渡して使用します。
例ではIP 198.51.100.23、HTTPポート8000、SOCKS5ポート1080、ユーザー名spx_user、パスワードspx_passを使用します。すべてのコードはESモジュール形式(.mjs)です。
npm install undici socks-proxy-agentfetchにHTTPプロキシを接続するにはどうすればよいですか?
ProxyAgentにプロキシアドレスを設定し、ユーザー名とパスワードはtokenオプションにBasic認証値として設定します。undiciからインポートしたfetchにdispatcherとして渡すと、そのリクエストがプロキシを経由します。HTTPSアドレスはCONNECT方式で暗号化されて転送されます。
同じProxyAgentを複数のリクエストで再利用すると、接続が再利用されて速くなります。プロキシごとにエージェントを1つ作成し、順番に使えば済みます。
import { fetch, ProxyAgent } from "undici";
const agent = new ProxyAgent({
uri: "http://198.51.100.23:8000",
token: "Basic " + Buffer.from("spx_user:spx_pass").toString("base64"),
});
const res = await fetch("https://api.ipify.org?format=json", { dispatcher: agent });
console.log(res.status, await res.json()); // "ip" should be the proxy IPSOCKS5プロキシはどのように接続しますか?
socks-proxy-agentはNodeのhttp、httpsモジュールと一緒に使用するエージェントです。アドレスをsocks5hにすると、ドメイン名の解決をプロキシ側で行います。以下の例はhttps.getにエージェントを渡し、応答本文を収集して出力します。
axiosのようにhttpモジュール上で動作するライブラリもhttpAgentとhttpsAgentに同じエージェントを設定するとSOCKS5でリクエストが送信されます。
import https from "node:https";
import { SocksProxyAgent } from "socks-proxy-agent";
const agent = new SocksProxyAgent("socks5h://spx_user:spx_pass@198.51.100.23:1080");
https.get("https://api.ipify.org?format=json", { agent }, (res) => {
let body = "";
res.on("data", (chunk) => (body += chunk));
res.on("end", () => console.log(res.statusCode, body));
}).on("error", (err) => console.error("request failed:", err.message));APIでリストを受け取って循環するにはどうすればいいですか?
開始時にAPIでリストを取得し、プロキシごとにエージェントを作成し、リクエストごとに次のエージェントを使用する方式です。以下のコードはHTTPリストをip:port:user:pass形式で取得し、ProxyAgent配列を作成し、失敗した場合は次のプロキシで再試行します。
リクエストにはAbortSignal.timeoutで時間制限を必ず設定してください。応答のないリクエストが溜まると全体の作業が遅くなります。
import { fetch, ProxyAgent } from "undici";
const API = "https://socks5proxy.io/api/v1";
const KEY = "YOUR_API_KEY";
const text = await (await fetch(API + "/proxies/download?format=ip:port:user:pass&protocol=http", {
headers: { Authorization: "Bearer " + KEY },
})).text();
const agents = text.trim().split("\n").map((line) => {
const [ip, port, user, pass] = line.trim().split(":");
return new ProxyAgent({
uri: "http://" + ip + ":" + port,
token: "Basic " + Buffer.from(user + ":" + pass).toString("base64"),
});
});
let i = 0;
async function get(url, tries = 3) {
for (let t = 0; t < tries; t++) {
const dispatcher = agents[i++ % agents.length];
try {
const res = await fetch(url, { dispatcher, signal: AbortSignal.timeout(20000) });
if (res.ok) return await res.text();
} catch {
// try the next proxy
}
}
return null;
}
console.log((await get("https://example.com/"))?.length);IP認証を使用するとコードはどのように変わりますか?
ダッシュボードにNode.jsが実行されるサーバーのIPv4を登録すると、ProxyAgentでtokenオプションを省きアドレスのみ入力できます。SOCKS5もsocks5h://198.51.100.23:1080のようにIDとパスワードなしで使用可能です。パスワードを環境変数やコードに置かなくて良いため、複数サーバーへの展開が容易です。
axiosを使用している場合、どのように接続しますか?
axiosはNodeのhttp、httpsモジュール上で動作するためエージェントを渡してプロキシを使用できます。SOCKS5はsocks-proxy-agentをhttpAgentとhttpsAgentに設定し、axios自体のproxyオプションはfalseに設定して二重設定を防ぎます。
HTTPプロキシはaxiosのproxyオプションにhost、port、authを入力する方法もありますが、HTTPSアドレスではエージェントを使用する方法がより安定しています。https-proxy-agentパッケージを使用すると、HTTPプロキシも同じ構造で接続できます。
import axios from "axios";
import { SocksProxyAgent } from "socks-proxy-agent";
const agent = new SocksProxyAgent("socks5h://spx_user:spx_pass@198.51.100.23:1080");
const client = axios.create({ httpAgent: agent, httpsAgent: agent, proxy: false, timeout: 20000 });
const { data } = await client.get("https://api.ipify.org?format=json");
console.log(data);複数のリクエストを同時に送信する際の注意点は何ですか?
Promise.allで数千のリクエストを一度に開始すると、接続が多すぎてプロキシと対象サイトの両方に負担がかかります。同時に処理する数を決め、その数だけ実行するジョブキューを設定してください。小さな関数で自作しても良いし、p-limitのようなパッケージを使用しても構いません。
プロキシごとに同時接続数を均等に分散すると、1つのIPにリクエストが集中しません。共有プランには同時接続500が含まれているので、全体の同時リクエスト数をその範囲内に収めてください。
環境変数で全リクエストにプロキシを適用できますか?
undiciのEnvHttpProxyAgentはHTTP_PROXY、HTTPS_PROXY、NO_PROXY環境変数を読み取り、プロキシを設定します。setGlobalDispatcherで登録すると、そのプロセスのすべてのfetchがプロキシを経由します。複数のライブラリが内部でfetchを使用する場合、一度に適用できるので便利です。
グローバル設定はすべてのリクエストに同じIPを使用するため、IPを分けて使用する必要がある収集作業よりも、1つの国からアクセスする必要があるツールや試験に適しています。ローテーションが必要な場合は、前述のようにリクエストごとにdispatcherを渡してください。
// HTTPS_PROXY=http://spx_user:spx_pass@198.51.100.23:8000 node app.mjs
import { EnvHttpProxyAgent, setGlobalDispatcher, fetch } from "undici";
setGlobalDispatcher(new EnvHttpProxyAgent());
const res = await fetch("https://api.ipify.org");
console.log(await res.text());よくある問題はどう解決しますか?
Node.jsの内蔵fetchにdispatcherを渡したのに無視されているように見える場合は、undiciパッケージからfetchを一緒にインポートしたか確認してください。内蔵fetchとインストールしたundiciのバージョンが異なるとエージェントが合わないことがあるので、同じパッケージからfetchとProxyAgentをインポートする方法が最も確実です。
407レスポンスは認証情報が間違っていることを意味します。token値のBasicの後にユーザー名:パスワードをbase64で変換した値が入っているか確認してください。ECONNREFUSEDはポートが間違っている場合が多いです。HTTPエージェントにはHTTPポートを、SOCKSエージェントにはSOCKS5ポートを入力してください。
- fetchとProxyAgentは同じundiciパッケージから
- 407: token値と登録したIPを確認
- ECONNREFUSED: HTTPポートとSOCKS5ポートの区別
- 全リクエストに時間制限