DenoとBunでのプロキシ利用は、最新のJavaScriptランタイムでスクレイピングや自動化を行う開発者にとって必須のスキルです。標準のfetch() APIはプロキシを無視する仕様のため、各ランタイム独自の方法でプロキシを設定する必要があります。本記事では、DenoのDeno.createHttpClientとBunのfetch(url, { proxy })を用いた実践的な実装例を、ProxyHatのゲートウェイgate.proxyhat.comを使って解説します。
DenoとBunでのプロキシ利用:なぜ標準fetchでは不十分なのか
WHATWG Fetch仕様では、fetch()にプロキシ設定を渡す標準的な方法が定義されていません。これは意図的な設計判断であり、ブラウザ環境ではプロキシ設定がOSやブラウザの責務とされているためです。しかし、サーバーサイドのランタイム(Deno、Bun、Node.js)では、プロキシ経由でリクエストを送る必要が頻繁に生じます。
この仕様の背景については、WHATWG Fetch仕様で確認できます。Node.jsではundiciのProxyAgentを使いますが、DenoとBunはそれぞれ異なるアプローチを採用しています。
Denoのアプローチ:Deno.createHttpClient
Denoでは、Deno.createHttpClient()でプロキシ設定を持つカスタムHTTPクライアントを作成し、それをfetch()の第2引数のclientオプションに渡します。Deno 1.25以降で利用可能です。
// Deno: プロキシ経由でfetchを実行
const client = Deno.createHttpClient({
proxy: {
url: "http://gate.proxyhat.com:8080",
basicAuth: {
username: "user-country-US",
password: "YOUR_PASSWORD",
},
},
});
const res = await fetch("https://httpbin.org/ip", { client });
const data = await res.json();
console.log(data); // { origin: "198.x.x.x" }
この方法の利点は、プロキシ設定をクライアント単位で管理できることです。異なるジオターゲティング設定を持つ複数のクライアントを作成し、用途に応じて使い分けることができます。
Bunのアプローチ:fetchのproxyオプション
Bunはよりシンプルな設計で、fetch()の第2引数にproxy文字列を直接渡せます。Bun 1.0以降でサポートされています。
// Bun: プロキシ経由でfetchを実行
const res = await fetch("https://httpbin.org/ip", {
proxy: "http://user-country-US:YOUR_PASSWORD@gate.proxyhat.com:8080",
});
const data = await res.json();
console.log(data); // { origin: "198.x.x.x" }
BunのproxyオプションはHTTP/HTTPS/SOCKSプロキシをサポートしており、URL文字列として認証情報も含められます。詳細はBun公式ドキュメントを参照してください。
ジオターゲティングとスティッキーセッションのエンコード
ProxyHatでは、ユーザー名にフラグを埋め込むことでジオターゲティングとスティッキーセッションを制御します。これはDenoでもBunでも同じ仕組みです。
| フラグ | 形式例 | 効果 |
|---|---|---|
| 国指定 | user-country-US | 米国のIPを割り当て |
| 都市指定 | user-country-DE-city-berlin | ベルリンのIPを割り当て |
| スティッキーセッション | user-session-abc123 | 同一セッションIDで同一IPを維持 |
| 組み合わせ | user-country-US-session-abc123 | 米国IPでセッション固定 |
SOCKS5プロキシの使用
HTTPプロキシ(ポート8080)に加えて、SOCKS5プロキシ(ポート1080)も利用できます。SOCKS5はTCPレベルで動作するため、より幅広いプロトコルに対応できます。
// Deno: SOCKS5プロキシを使用
const socksClient = Deno.createHttpClient({
proxy: {
url: "socks5://gate.proxyhat.com:1080",
basicAuth: {
username: "user-country-JP-session-tokyo01",
password: "YOUR_PASSWORD",
},
},
});
const res = await fetch("https://httpbin.org/ip", { client: socksClient });
console.log(await res.json());
// Bun: SOCKS5プロキシを使用
const res = await fetch("https://httpbin.org/ip", {
proxy: "socks5://user-country-JP-session-tokyo01:YOUR_PASSWORD@gate.proxyhat.com:1080",
});
console.log(await res.json());
環境変数によるプロキシ設定と使い分け
両ランタイムともHTTP_PROXY / HTTPS_PROXY環境変数をサポートしています。ただし、Denoでは--allow-env権限が必要です。
# 環境変数を設定
export HTTP_PROXY="http://user-country-US:YOUR_PASSWORD@gate.proxyhat.com:8080"
export HTTPS_PROXY="http://user-country-US:YOUR_PASSWORD@gate.proxyhat.com:8080"
# Denoで実行
deno run --allow-net --allow-env script.ts
# Bunで実行
bun run script.ts
// 両ランタイムで動作:環境変数ベースのプロキシ
// HTTP_PROXY / HTTPS_PROXY が設定されていれば自動的に使用される
const res = await fetch("https://httpbin.org/ip");
console.log(await res.json());
環境変数を使うべきケース:CLIツールやスクリプト全体で単一のプロキシを使う場合。デバッグ時のプロキシのオンオフ切り替えにも便利です。
クライアント単位設定を使うべきケース:複数のジオターゲティング設定を同時に使う場合、セッションごとに異なるIPを維持する場合、プロキシ経由と直接接続を混在させる場合。
レジデンシャルプロキシでブロック回避:並行スティッキーセッションの実装
データセンタープロキシは検出されやすく、GoogleやAmazonなどの厳格なターゲットではブロック率が高くなります。レジデンシャルプロキシは実際のISPに割り当てられたIPアドレスを使用するため、ブロックされるリスクが大幅に低下します。詳細はWebスクレイピングのユースケースを参照してください。
以下の例では、複数のスティッキーセッションを並行実行し、AbortControllerでタイムアウトを制御します。これはSERPトラッキングや価格モニタリングでよく使われるパターンです。
// Deno/Bun共通: 並行スティッキーセッションでローテーション
const SESSIONS = [
"user-country-US-session-s01",
"user-country-US-session-s02",
"user-country-US-session-s03",
"user-country-US-session-s04",
"user-country-US-session-s05",
];
const PASSWORD = "YOUR_PASSWORD";
const TARGET_URL = "https://httpbin.org/ip";
const TIMEOUT_MS = 10000;
async function fetchViaProxy(sessionUser: string): Promise<{ session: string; ip: string }> {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), TIMEOUT_MS);
try {
// Denoの場合
const client = Deno.createHttpClient({
proxy: {
url: "http://gate.proxyhat.com:8080",
basicAuth: { username: sessionUser, password: PASSWORD },
},
});
const res = await fetch(TARGET_URL, {
client,
signal: controller.signal,
});
if (!res.ok) {
throw new Error(`HTTP ${res.status}`);
}
const data = await res.json();
return { session: sessionUser, ip: data.origin };
} catch (err) {
console.error(`${sessionUser} failed: ${err.message}`);
return { session: sessionUser, ip: "ERROR" };
} finally {
clearTimeout(timeout);
}
}
// 5セッションを並行実行
const results = await Promise.all(
SESSIONS.map((s) => fetchViaProxy(s))
);
console.table(results);
Bunで同じことを行う場合は、Deno.createHttpClientの代わりにproxyオプションを使います:
// Bun版: 並行スティッキーセッション
async function fetchViaProxyBun(sessionUser: string): Promise<{ session: string; ip: string }> {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 10000);
try {
const res = await fetch("https://httpbin.org/ip", {
proxy: `http://${sessionUser}:YOUR_PASSWORD@gate.proxyhat.com:8080`,
signal: controller.signal,
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();
return { session: sessionUser, ip: data.origin };
} catch (err) {
console.error(`${sessionUser} failed: ${err.message}`);
return { session: sessionUser, ip: "ERROR" };
} finally {
clearTimeout(timeout);
}
}
const results = await Promise.all(SESSIONS.map(fetchViaProxyBun));
console.table(results);
本番運用のベストプラクティス
リトライとバックオフ
プロキシリクエストはネットワークの不安定性やターゲット側の一時的なブロックで失敗することがあります。指数バックオフ付きのリトライを実装することで、成功率を大幅に向上できます。
// 指数バックオフ付きリトライ(Deno/Bun共通)
async function fetchWithRetry(
url: string,
proxyUrl: string,
maxRetries = 3,
baseDelayMs = 500,
): Promise<Response> {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 15000);
// Denoの場合
const client = Deno.createHttpClient({
proxy: {
url: proxyUrl,
basicAuth: {
username: "user-country-US-session-" + crypto.randomUUID(),
password: "YOUR_PASSWORD",
},
},
});
const res = await fetch(url, { client, signal: controller.signal });
clearTimeout(timeout);
if (res.ok) return res;
// 429 or 5xx → リトライ
if (res.status !== 429 && res.status < 500) {
throw new Error(`HTTP ${res.status}: ${await res.text()}`);
}
console.warn(`Attempt ${attempt + 1} got ${res.status}, retrying...`);
} catch (err) {
if (attempt === maxRetries) throw err;
console.warn(`Attempt ${attempt + 1} failed: ${err.message}`);
}
// 指数バックオフ: 500ms, 1000ms, 2000ms
const delay = baseDelayMs * Math.pow(2, attempt);
await new Promise((r) => setTimeout(r, delay));
}
throw new Error("Max retries exceeded");
}
// 使用例
const res = await fetchWithRetry(
"https://httpbin.org/ip",
"http://gate.proxyhat.com:8080",
);
console.log(await res.json());
カスタムCA証明書(Deno)
DenoではcaCertsオプションでカスタムルート証明書を指定できます。企業プロキシ環境や自己署名証明書を使用する場合に有用です。
// Deno: カスタムCA証明書を指定
const caCert = await Deno.readTextFile("./custom-ca.pem");
const client = Deno.createHttpClient({
proxy: {
url: "http://gate.proxyhat.com:8080",
basicAuth: {
username: "user-country-DE-city-berlin",
password: "YOUR_PASSWORD",
},
},
caCerts: [caCert],
});
const res = await fetch("https://httpbin.org/ip", { client });
console.log(await res.json());
接続の再利用
Deno.createHttpClientで作成したクライアントは接続プールを保持するため、同じクライアントインスタンスを再利用することでTCP接続のオーバーヘッドを削減できます。毎回新しいクライアントを作成するのではなく、プロキシ設定ごとに1つのクライアントを作成して使い回すのが推奨されます。
ProxyHat Node SDKとのサイドバイサイド
ProxyHatはNode.js向けSDKを提供しており、これはDenoとBunの両方でNode互換モード経由で実行できます。SDKを使うと、プロキシURLの構築やセッション管理を抽象化できます。詳細はProxyHat公式ドキュメントを参照してください。
// ProxyHat Node SDK(Deno/Bun互換モード)
// インストール: npm install @proxyhat/node
import { ProxyHat } from "@proxyhat/node";
const ph = new ProxyHat({
username: "user",
password: "YOUR_PASSWORD",
defaultCountry: "US",
});
// スティッキーセッション付きでプロキシURLを取得
const proxyUrl = ph.getProxyUrl({
country: "US",
city: "new-york",
session: "order-check-001",
});
console.log(proxyUrl);
// http://user-country-US-city-new_york-session-order-check-001:YOUR_PASSWORD@gate.proxyhat.com:8080
// Denoで使用
const client = Deno.createHttpClient({
proxy: {
url: proxyUrl,
},
});
const res = await fetch("https://httpbin.org/ip", { client });
console.log(await res.json());
SDKを使う利点は、ユーザー名のエンコード規則を気にせずに型安全なAPIでプロキシ設定を構築できることです。ただし、DenoのネイティブAPIだけでも十分にシンプルなため、プロジェクトの要件に応じて選択してください。
ProxyHatのプランと価格については料金ページを、対応ロケーションについてはロケーション一覧を参照してください。SERPトラッキングに興味がある方はSERPトラッキングのユースケースもご覧ください。
倫理的スクレイピングと法的注意点
プロキシを使ったスクレイピングは強力なツールですが、法的・倫理的な境界を守ることが重要です。
- 公開データのみを対象とする:ログイン背後のデータやパスワードで保護されたページのスクレイピングは避けてください。米国ではCFAA(Computer Fraud and Abuse Act)に抵触する可能性があります。
- robots.txtを尊重する:ターゲットサイトのクロール指示に従うことが業界のベストプラクティスです。
- GDPRへの準拠:EUの個人データを収集する場合、GDPRの要件を確認してください。詳細はGDPR公式サイトを参照してください。
- 公式APIを優先する:ターゲットが公式APIを提供している場合は、スクレイピングよりAPIを使用してください。レート制限も明確で、法的リスクも低くなります。
- リクエストレートを抑える:ターゲットサーバーに過度な負荷をかけないよう、適切な遅延と並行数制御を実装してください。
Key Takeaways
- Denoでは
Deno.createHttpClient({ proxy: { url, basicAuth } })を作成し、fetch(url, { client })で渡す。- Bunでは
fetch(url, { proxy: 'http://user:pass@gate.proxyhat.com:8080' })の1行で完結。- ジオターゲティングとスティッキーセッションはユーザー名にエンコード(例:
user-country-US-session-abc123)。- SOCKS5はポート1080を使用し、より幅広いプロトコルに対応。
- 環境変数は全体一律のプロキシ設定に便利、クライアント単位設定は複数プロキシの混在に適する。
- 本番運用ではリトライ・バックオフ、タイムアウト、接続再利用を実装する。
- 倫理的スクレイピングを徹底し、公開データのみを対象とする。
FAQ
DenoとBunでのプロキシ利用とは何ですか?
DenoとBunでのプロキシ利用とは、これらのモダンJavaScriptランタイムでHTTPリクエストをプロキシサーバー経由で送信する仕組みです。標準のfetch()はWHATWG Fetch仕様に従いプロキシを無視するため、DenoではDeno.createHttpClient、Bunではfetchのproxyオプションを使って明示的にプロキシを設定する必要があります。
なぜDenoとBunでのプロキシ利用がプロキシユーザーにとって重要なのですか?
スクレイピング、SERPトラッキング、価格モニタリングなどの用途では、IPローテーションとジオターゲティングが不可欠です。DenoとBunは高速な起動とネイティブTypeScriptサポートで人気を集めており、これらのランタイムでプロキシを正しく設定できないと、IPブロックやレート制限に直面します。プロキシ利用を理解することで、これらのランタイムで本番レベルの自動化パイプラインを構築できます。
DenoとBunでのプロキシ利用に最適なプロキシタイプは何ですか?
ブロックリスクの高いターゲット(Google、Amazon、ソーシャルメディアなど)ではレジデンシャルプロキシが最適です。実際のISP割り当てIPを使用するため、データセンタープロキシよりも検出されにくいです。軽量なAPI呼び出しやテスト用途ではデータセンタープロキシも速度面で有利です。プロトコルの柔軟性が必要な場合はSOCKS5(ポート1080)を選択してください。
DenoとBunでのプロキシ利用でブロックを回避するにはどうすればよいですか?
複数の戦略を組み合わせるのが効果的です。まず、スティッキーセッションでIPを一定期間維持し、不自然なIP変更を防ぎます。次に、リクエスト間に適切な遅延を入れ、人間らしいアクセスパターンを模倣します。さらに、指数バックオフ付きリトライで429エラーから回復し、AbortControllerでタイムアウトを設定してハングアップを防止します。最後に、ジオターゲティングでターゲットに適した地域のIPを使用してください。






