Socks5Proxy

Node.jsプロキシ設定、HTTPとSOCKS5プロキシをfetchとhttpsに接続する方法

このガイドではNode.js 18以上でSocks5ProxyのHTTPプロキシをundici ProxyAgentで、SOCKS5プロキシをsocks-proxy-agentで接続する方法と、APIでリストを取得しローテーションするコードを説明します。

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-agent

fetchに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 IP

SOCKS5プロキシはどのように接続しますか?

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ポートの区別
  • 全リクエストに時間制限
Node.jsプロキシ設定、undici・SOCKS5エージェント接続 | Socks5Proxy