Использование прокси в Deno и Bun — это задача, с которой сталкивается каждый разработчик, запускающий веб-скрейпинг, автоматизацию или ценовой мониторинг на новых JavaScript-рантаймах. В отличие от Node.js, где прокси-агенты давно стандартизированы через https-proxy-agent и подобные пакеты, Deno и Bun решают вопрос иначе: встроенный fetch() по умолчанию игнорирует системные прокси, и конфигурация задаётся на уровне клиента или вызова.
Почему fetch игнорирует прокси и как это решают Deno и Bun
Стандарт WHATWG Fetch не описывает прокси-параметров — это намеренное решение, оставляющее прокси-конфигурацию на усмотрение среды выполнения. В браузере прокси управляется ОС или расширениями; в серверных рантаймах — разработчиком. Это означает, что fetch('https://example.com') в Deno и Bun всегда идёт напрямую, если вы не указали прокси явно.
Deno решает это через Deno.createHttpClient() — фабрику HTTP-клиентов, принимающую объект с полем proxy. Полученный клиент передаётся в fetch() как опция { client }. Bun идёт проще: прокси указывается прямо в опциях вызова fetch(url, { proxy }), без отдельного клиентского объекта.
Deno: Deno.createHttpClient с прокси
// deno run --allow-net proxy_demo.ts
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 body = await res.text();
console.log(body);
// { "origin": "198.51.100.42" } — IP адрес прокси
Обратите внимание: basicAuth — отдельное поле, а не часть URL. Это удобно, когда учётные данные содержат специальные символы, которые пришлось бы URL-кодировать.
Bun: fetch с опцией proxy
// bun run proxy_demo.ts
const res = await fetch("https://httpbin.org/ip", {
proxy: "http://user-country-US:YOUR_PASSWORD@gate.proxyhat.com:8080",
});
const body = await res.text();
console.log(body);
Bun принимает прокси как строку URL в одной строке — проще для быстрых скриптов, но менее гибко при ротации. Для динамической смены прокси на каждый запрос достаточно собирать строку заново.
Геотаргетинг и sticky-сессии в имени пользователя
ProxyHat кодирует параметры сессии и геолокации прямо в имени пользователя. Это работает с любым рантаймом — Deno, Bun, Node.js, curl — потому что всё сводится к HTTP Basic Auth.
| Параметр | Формат в username | Пример |
|---|---|---|
| Страна | user-country-XX | user-country-DE |
| Город | user-country-XX-city-name | user-country-DE-city-berlin |
| Sticky-сессия | user-session-ID | user-session-abc123 |
| Комбинация | user-country-US-session-abc123 | США + сессия abc123 |
Sticky-сессия сохраняет один выходной IP на протяжении жизни сессии — обычно до 10–30 минут в зависимости от провайдера. Это критично для сайтов, требующих последовательности запросов: вход в аккаунт, навигация по каталогу, добавление в корзину.
SOCKS5 на порту 1080
// Deno — SOCKS5 через createHttpClient
const socksClient = Deno.createHttpClient({
proxy: {
url: "socks5://gate.proxyhat.com:1080",
basicAuth: {
username: "user-country-DE-session-xyz789",
password: "YOUR_PASSWORD",
},
},
});
const res = await fetch("https://httpbin.org/ip", { client: socksClient });
console.log(await res.text());
// Bun — SOCKS5 в строке proxy
const res = await fetch("https://httpbin.org/ip", {
proxy: "socks5://user-country-DE-session-xyz789:YOUR_PASSWORD@gate.proxyhat.com:1080",
});
console.log(await res.text());
SOCKS5 полезен, когда целевой сайт работает по нестандартным протоколам или когда HTTP-прокси блокируется корпоративным файрволом. Latency через SOCKS5 обычно на 20–50 ms выше из-за дополнительного уровня инкапсуляции.
Переменные окружения HTTP_PROXY / HTTPS_PROXY
Оба рантайма поддерживают переменные окружения HTTP_PROXY и HTTPS_PROXY. Это удобно для быстрого тестирования и для скриптов, где прокси нужен глобально:
# Терминал
export HTTPS_PROXY="http://user-country-US:YOUR_PASSWORD@gate.proxyhat.com:8080"
deno run --allow-net proxy_env.ts
# или
bun run proxy_env.ts
// proxy_env.ts — работает в обоих рантаймах
const res = await fetch("https://httpbin.org/ip");
console.log(await res.text());
Однако env-var подход имеет ограничения: нельзя задать разные прокси для разных запросов в одном процессе, нельзя динамически менять страну или сессию, нельзя комбинировать residential и datacenter в одном скрипте. Для production-скрейпинга используйте per-client конфигурацию через Deno.createHttpClient или опцию proxy в Bun.
Ротация sticky-сессий с Promise.all и AbortController
Резидентные прокси нужны для сайтов с агрессивной антибот-защитой: e-commerce, соцсети, SERP. Datacenter-IP блокируются на этапе TLS-fingerprinting или IP-reputation-проверки. Резидентные адреса выглядят как реальные пользователи и имеют значительно более высокий success rate — по нашим наблюдениям, для защищённых сайтов разница достигает 3–5 раз.
Следующий пример создаёт пул из 10 sticky-сессий, каждая со своим выходным IP, и отправляет запросы параллельно через Promise.all с таймаутом через AbortController:
// bun run rotation_pool.ts
const PROXY_GATE = "gate.proxyhat.com:8080";
const PASSWORD = "YOUR_PASSWORD";
const TARGET = "https://httpbin.org/ip";
function buildProxyUrl(sessionId: string, country = "US"): string {
const username = `user-country-${country}-session-${sessionId}`;
return `http://${username}:${PASSWORD}@${PROXY_GATE}`;
}
async function fetchWithTimeout(
url: string,
proxy: string,
timeoutMs = 10000,
): Promise<{ session: string; status: number; body: string }> {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), timeoutMs);
try {
const res = await fetch(url, {
proxy,
signal: controller.signal,
});
const body = await res.text();
return { session: proxy, status: res.status, body };
} finally {
clearTimeout(timer);
}
}
// Генерируем 10 уникальных сессий
const sessions = Array.from({ length: 10 }, (_, i) => `sess-${Date.now()}-${i}`);
const results = await Promise.all(
sessions.map((sid) =>
fetchWithTimeout(TARGET, buildProxyUrl(sid))
.catch((err) => ({ session: sid, status: 0, body: err.message }))
),
);
for (const r of results) {
console.log(`[${r.status}] ${r.session.slice(0, 40)} → ${r.body.slice(0, 60)}`);
}
Каждая сессия получит свой IP-адрес от ProxyHat. Если один запрос упадёт по таймауту, Promise.all не прервёт остальные, потому что ошибка перехватывается в .catch(). Для Deno версия идентична, но proxy заменяется на client: Deno.createHttpClient({ proxy: { url, basicAuth } }).
Production-паттерны: ретраи, CA-сертификаты, переиспользование соединений
Ретраи с экспоненциальной задержкой
// Работает в Deno и Bun
async function fetchWithRetry(
url: string,
proxyFn: (attempt: number) => string,
maxRetries = 5,
baseDelay = 1000,
): Promise<Response> {
let lastError: Error | null = null;
for (let attempt = 0; attempt < maxRetries; attempt++) {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), 15000);
try {
const res = await fetch(url, {
proxy: proxyFn(attempt),
signal: controller.signal,
});
if (res.status === 200) return res;
if (res.status === 429 || res.status >= 500) {
// Retryable
const retryAfter = res.headers.get("retry-after");
const delay = retryAfter
? parseInt(retryAfter, 10) * 1000
: baseDelay * Math.pow(2, attempt);
await new Promise((r) => setTimeout(r, delay));
continue;
}
// Non-retryable
return res;
} catch (err) {
lastError = err as Error;
const delay = baseDelay * Math.pow(2, attempt) + Math.random() * 500;
await new Promise((r) => setTimeout(r, delay));
} finally {
clearTimeout(timer);
}
}
throw lastError ?? new Error("Max retries exceeded");
}
// Ротация прокси на каждой попытке
let counter = 0;
const result = await fetchWithRetry(
"https://httpbin.org/status/200",
() => {
const sid = `retry-${Date.now()}-${counter++}`;
return `http://user-country-US-session-${sid}:YOUR_PASSWORD@gate.proxyhat.com:8080`;
},
);
console.log(result.status);
Кастомные CA-сертификаты в Deno
Deno позволяет передавать CA-сертификаты напрямую в createHttpClient, что полезно при работе через MITM-прокси или в корпоративных сетях:
// 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-US", password: "YOUR_PASSWORD" },
},
caCerts: [caCert],
});
const res = await fetch("https://httpbin.org/ip", { client });
console.log(await res.text());
Bun пока не имеет эквивалента caCerts — для корпоративных сред с MITM-прокси Deno предпочтительнее.
Переисользование соединений
В Deno клиент, созданный через Deno.createHttpClient, переиспользует TCP-соединения автоматически. Создавайте один клиент на сессию и переиспользуйте его для всех запросов в рамках этой сессии — это снижает latency на 30–80 ms за счёт устранения TLS-handshake на каждом запросе.
В Bun fetch также поддерживает connection pooling под капотом, но поскольку прокси указывается строкой, пул привязан к идентичной строке прокси. Меняете страну или сессию — получаете новое соединение. Для sticky-сессий это нормально; для агрессивной ротации учитывайте overhead.
ProxyHat Node SDK: работает в обоих рантаймах
ProxyHat предоставляет Node.js SDK, который совместим с Bun (полная совместимость с npm-пакетами) и частично с Deno через npm: спецификатор. SDK абстрагирует форматирование username, ротацию и обработку ошибок:
// Bun — npm install @proxyhat/node-sdk
// Deno — deno run --allow-net --allow-env sdk_demo.ts
import { ProxyHat } from "npm:@proxyhat/node-sdk";
// или в Bun: import { ProxyHat } from "@proxyhat/node-sdk";
const ph = new ProxyHat({
username: "YOUR_USERNAME",
password: "YOUR_PASSWORD",
gateway: "gate.proxyhat.com",
port: 8080,
});
// Получаем URL прокси с геотаргетингом
const proxyUrl = ph.getProxyUrl({
country: "DE",
city: "berlin",
session: "order-flow-001",
});
console.log(proxyUrl);
// http://user-country-DE-city-berlin-session-order-flow-001:YOUR_PASSWORD@gate.proxyhat.com:8080
// Используем в Bun fetch
const res = await fetch("https://httpbin.org/ip", { proxy: proxyUrl });
console.log(await res.text());
SDK полезен, когда логика ротации сложная: взвешенный выбор страны, ротация по времени, fallback на datacenter при исчерпании residential-лимита. Для простых скриптов достаточно ручного форматирования строки — overhead SDK не оправдан.
Сравнение Deno и Bun для прокси-задач
| Возможность | Deno | Bun |
|---|---|---|
| Указание прокси | Deno.createHttpClient({ proxy }) + { client } | fetch(url, { proxy }) |
| SOCKS5 | Через proxy.url | Через строку proxy |
| Кастомные CA | caCerts в клиенте | Нет прямого API |
| Env-var прокси | Поддерживается | Поддерживается |
| Connection pooling | На уровне клиента | На уровне fetch |
| npm-совместимость | Через npm: спецификатор | Нативная |
Этика и правовые аспекты
Скрейпинг публичных данных в большинстве юрисдикций легален, но границы размыты. В США Computer Fraud and Abuse Act (CFAA) исторически использовался против скрейпинга, хотя прецедент hiQ Labs v. LinkedIn (9-й округ, 2022) подтвердил, что скрейпинг публичных данных не нарушает CFAA. В ЕС GDPR регулирует обработку персональных данных — IP-адреса считаются персональными данными, и их сбор требует законного основания.
Практические принципы:
- Сначала проверяйте официальный API — многие платформы предоставляют данные легально и быстрее.
- Уважайте
robots.txtи Terms of Service, даже если они юридически не обязывают. - Ограничивайте частоту запросов: 1–2 запроса в секунду на сессию — разумный минимум для большинства сайтов.
- Не собирайте персональные данные без правового основания.
- Не обходьте технические меры защиты (CAPTCHA, paywalls) автоматически — это может квалифицироваться как нарушение.
Подробнее о применении прокси для скрейпинга — в нашем материале по использованию прокси для веб-скрейпинга. Для SERP-трекинга см. руководство по отслеживанию поисковой выдачи. Полный список доступных локаций — на странице локаций ProxyHat. Тарифы и лимиты — на странице цен. Документация по API доступна на docs.proxyhat.com.
Ключевые выводы
- fetch игнорирует прокси по умолчанию — это соответствует стандарту WHATWG, а не баг рантайма.
- Deno использует
Deno.createHttpClient({ proxy })+{ client }в fetch — гибко, поддерживаетcaCerts.- Bun принимает прокси строкой в
fetch(url, { proxy })— лаконично, идеально для быстрых скриптов.- Геотаргетинг и sticky-сессии кодируются в username:
user-country-US-session-abc123.- SOCKS5 доступен на порту 1080 — используйте, когда HTTP-прокси нежелателен.
- Env-var удобен для тестирования; per-client — для production.
- Ротация сессий через
Promise.all+AbortControllerдаёт параллелизм и устойчивость к таймаутам.- Ретраи с backoff обязательны для production — 429 и 5xx требуют задержки, а не повтора в лоб.
Использование прокси в Deno и Bun не сложнее, чем в Node.js, если знать, куда смотреть. Deno даёт больше контроля над TLS и сертификатами; Bun — скорость и простоту. Выбирайте по потребностям проекта, а не по хайпу.






