Si llegaste aquí buscando cómo usar proxies en Deno y Bun, la respuesta corta es que fetch() no lee configuración de proxy por defecto: en Deno necesitas Deno.createHttpClient({ proxy: { url, basicAuth } }) y pasarlo como { client }, mientras que en Bun basta con fetch(url, { proxy: 'http://user:pass@gate.proxyhat.com:8080' }). A continuación verás por qué esto ocurre, cómo codificar geo-segmentación y sesiones sticky, cuándo usar variables de entorno, y cómo montar un cliente robusto con reintentos, timeouts y certificados CA personalizados.
Por qué fetch() ignora los proxies en Deno y Bun
La API fetch() está estandarizada por WHATWG y no define ningún parámetro de proxy. El estándar asume que el transporte lo gestionan capas inferiores, por lo que cada runtime decide cómo exponerlo. Node.js lo delegó históricamente a librerías como undici y al agente HTTP, y Deno y Bun han seguido caminos distintos pero igualmente explícitos.
Deno implementa fetch() sobre su propio cliente HTTP basado en Rust, y exige crear un HttpClient configurado con proxy antes de la llamada. Bun, en cambio, expone una opción proxy directamente en el segundo argumento de fetch(), lo que reduce el boilerplate a una sola línea. Ambos enfoques son válidos; el de Deno ofrece más control (certificados CA, pools de conexión), el de Bun es más ergonómico para scripts rápidos.
Variables de entorno: el camino de HTTP_PROXY
Si solo necesitas un proxy global para todo el proceso, las variables HTTP_PROXY y HTTPS_PROXY son la opción más simple. Deno las respeta a partir de la versión 1.10+ cuando usas fetch() sin cliente personalizado, y Bun las lee automáticamente. Esto es útil para CLI y scripts de un solo uso, pero pierdes control granular: no puedes mezclar proxies residenciales y datacenter en el mismo proceso, ni rotar sesiones sticky por petición. En producción, casi siempre conviene la configuración por cliente.
Configuración básica en Deno con Deno.createHttpClient
El patrón idiomático en Deno es crear un cliente reutilizable y pasarlo a fetch() mediante la opción client. Esto te permite mantener un pool de conexiones y reutilizar TLS entre llamadas, lo que reduce la latencia unas decenas de milisegundos por petición en cargas medias.
// deno run --allow-net --allow-env proxy_deno.ts
const proxyUrl = 'http://user-country-US:PASSWORD@gate.proxyhat.com:8080';
const client = Deno.createHttpClient({
proxy: {
url: proxyUrl,
// basicAuth es opcional si ya va en la URL, pero refuerza el header:
basicAuth: { username: 'user-country-US', password: 'PASSWORD' },
},
// Para entornos con CA corporativa, añade tus certificados PEM:
caCerts: [Deno.readTextFileSync('./corp-ca.pem')],
});
try {
const res = await fetch('https://httpbin.org/ip', { client });
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const json = await res.json();
console.log('IP de salida:', json.origin);
} catch (err) {
console.error('Petición fallida:', err);
} finally {
client.close(); // libera el pool de conexiones
}
El método client.close() es importante en procesos largos: sin cerrarlo, las conexiones TLS se acumulan y pueden agotar el límite de file descriptors del sistema operativo, típicamente 1024 por proceso en Linux.
Configuración básica en Bun con la opción proxy
Bun expone la opción proxy directamente en fetch(), siguiendo el mismo formato de URL que HTTP_PROXY. No necesitas crear un cliente explícito, aunque Bun reutiliza conexiones internamente.
// bun run proxy_bun.ts
const proxy = 'http://user-country-DE-city-berlin:PASSWORD@gate.proxyhat.com:8080';
try {
const res = await fetch('https://httpbin.org/ip', { proxy });
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const json = await res.json();
console.log('IP de salida:', json.origin);
} catch (err) {
console.error('Petición fallida:', err);
}
Para SOCKS5, cambia el esquema y el puerto: socks5://user-country-DE:PASSWORD@gate.proxyhat.com:1080. Bun soporta SOCKS5 de forma nativa, mientras que en Deno necesitarás una librería de terceros o un túnel local.
Codificar geo-segmentación y sesiones sticky en el nombre de usuario
ProxyHat codifica las opciones de enrutamiento en el nombre de usuario, no en la URL de destino. Esto permite mantener la misma URL objetivo y cambiar el país, la ciudad o la sesión simplemente modificando el usuario. El formato es:
user-country-US— IP de salida en Estados Unidos.user-country-DE-city-berlin— IP en Berlín, Alemania.user-session-abc123— sesión sticky: la misma IP durante toda la vida de la sesión.user-country-US-session-abc123— combina país y sesión sticky.
Las sesiones sticky son esenciales para flujos de login o carritos de compra donde el servidor de destino asocia la IP a una cookie o token. Si la IP cambia a mitad de sesión, el sitio puede invalidar la autenticación o disparar un CAPTCHA. En ProxyHat, una sesión sticky mantiene la misma IP durante aproximadamente 10 minutos de inactividad, suficiente para la mayoría de flujos de scraping.
// Generador de usuarios ProxyHat para Deno y Bun
function buildProxyUser(opts: {
country?: string;
city?: string;
session?: string;
}): string {
const parts = ['user'];
if (opts.country) parts.push(`country-${opts.country}`);
if (opts.city) parts.push(`city-${opts.city.toLowerCase()}`);
if (opts.session) parts.push(`session-${opts.session}`);
return parts.join('-');
}
// Ejemplo: 5 sesiones sticky en EE.UU. para scraping paralelo
const sessions = Array.from({ length: 5 }, (_, i) => `s${i}-${Date.now()}`);
const users = sessions.map((s) =>
buildProxyUser({ country: 'US', session: s })
);
console.log(users[0]); // user-country-US-session-s0-1717986918
Rotar un pool de sesiones sticky con Promise.all y AbortController
Para objetivos con defensa anti-bot agresiva (SERPs, e-commerce, redes sociales), lo ideal no es una IP nueva por petición —eso genera patrones detectables— sino un pool de sesiones sticky que rota de forma controlada. Cada sesión mantiene una IP residencial estable, lo que imita el comportamiento de un usuario real. Lanza varias sesiones en paralelo con Promise.all y protege cada llamada con un AbortController para evitar que una petición colgada bloquee todo el lote.
// Bun o Deno: pool de sesiones sticky con timeout y reintentos
const PASSWORD = Deno?.env?.get('PROXYHAT_PASS') ?? Bun?.env?.PROXYHAT_PASS ?? '';
const TARGET = 'https://api.target.com/search?q=producto';
const SESSIONS = 8;
const TIMEOUT_MS = 15_000;
const MAX_RETRIES = 3;
function buildUser(session: string) {
return `user-country-US-session-${session}`;
}
async function fetchWithProxy(session: string, attempt = 1): Promise<number> {
const ctrl = new AbortController();
const timer = setTimeout(() => ctrl.abort(), TIMEOUT_MS);
const proxy = `http://${buildUser(session)}:${PASSWORD}@gate.proxyhat.com:8080`;
try {
// En Deno, sustituye por fetch(TARGET, { client, signal })
const res = await fetch(TARGET, { proxy, signal: ctrl.signal });
if (res.status === 429 || res.status >= 500) {
throw new Error(`HTTP ${res.status}`);
}
return res.status;
} catch (err) {
if (attempt < MAX_RETRIES) {
const backoff = Math.min(1000 * 2 ** attempt, 8000);
await new Promise((r) => setTimeout(r, backoff));
return fetchWithProxy(session, attempt + 1);
}
throw err;
} finally {
clearTimeout(timer);
}
}
const sessions = Array.from({ length: SESSIONS }, (_, i) => `pool-${i}-${Date.now()}`);
const results = await Promise.allSettled(
sessions.map((s) => fetchWithProxy(s))
);
const ok = results.filter((r) => r.status === 'fulfilled').length;
console.log(`Peticiones OK: ${ok}/${SESSIONS}`);
Usar Promise.allSettled en lugar de Promise.all garantiza que una sesión fallida no cancele todo el lote. El backoff exponencial con techo de 8 segundos evita martillear el destino cuando hay errores 5xx o 429. Con 8 sesiones concurrentes y un timeout de 15 segundos, el lote completo debería completarse en menos de 20 segundos en condiciones normales.
Reintentos, circuit breakers y reutilización de conexiones
En producción necesitas más que reintentos simples. Un patrón robusto combina tres elementos: backoff exponencial con jitter, un circuit breaker que pare de enviar tráfico si la tasa de error supera un umbral, y reutilización del cliente HTTP para mantener vivas las conexiones TLS. En Deno, el HttpClient reutiliza conexiones automáticamente; en Bun, el pool interno hace lo mismo, pero solo si no recreas el proceso.
// Circuit breaker simple para scraping con proxies en Deno
interface BreakerState {
failures: number;
openUntil: number;
}
const breaker: BreakerState = { failures: 0, openUntil: 0 };
const THRESHOLD = 5;
const COOLDOWN_MS = 30_000;
function isOpen(): boolean {
return Date.now() < breaker.openUntil;
}
function recordFailure() {
breaker.failures++;
if (breaker.failures >= THRESHOLD) {
breaker.openUntil = Date.now() + COOLDOWN_MS;
breaker.failures = 0;
console.warn('Circuit breaker abierto por 30s');
}
}
function recordSuccess() {
breaker.failures = 0;
}
async function safeFetch(url: string, client: Deno.HttpClient) {
if (isOpen()) throw new Error('Circuit breaker abierto');
const ctrl = new AbortController();
const t = setTimeout(() => ctrl.abort(), 12_000);
try {
const res = await fetch(url, { client, signal: ctrl.signal });
if (!res.ok) recordFailure();
else recordSuccess();
return res;
} catch {
recordFailure();
throw new Error('fetch abortado o fallido');
} finally {
clearTimeout(t);
}
}
Comparación: cliente nativo vs ProxyHat SDK
El SDK de ProxyHat para Node funciona también bajo Deno y Bun gracias a la compatibilidad con el ecosistema npm. La principal ventaja del SDK es que gestiona rotación, reintentos y telemetría por ti, pero para casos donde necesitas control total del TLS o del pool, el cliente nativo es más flexible.
| Aspecto | fetch + proxy nativo | ProxyHat SDK |
|---|---|---|
| Boilerplate | Más código manual | Mínimo, una llamada |
| Rotación automática | Manual (gestionas sesiones) | Integrada |
| Control de TLS / CA | Completo en Deno (caCerts) | Limitado al SDK |
| Compatibilidad | Deno, Bun, Node | Deno, Bun, Node |
| Telemetría | Manual (logs propios) | Integrada |
// ProxyHat SDK bajo Bun o Deno (npm: @proxyhat/node)
import { ProxyHat } from '@proxyhat/node';
const ph = new ProxyHat({
username: 'user',
password: PASSWORD,
// rotate: 'per-request' o 'sticky'
rotate: 'sticky',
country: 'US',
});
const result = await ph.fetch('https://api.target.com/data');
console.log('Status:', result.status);
console.log('IP usada:', result.proxyIp);
Por qué proxies residenciales para objetivos con bloqueo alto
Los proxies datacenter son más rápidos y baratos, pero sus rangos de IP están publicados en bases de datos como las de ASN de IANA y son trivialmente detectables. Los proxies residenciales usan IPs asignadas a ISPs reales, por lo que el destino no puede distinguirlos de un usuario doméstico. Para SERP scraping, monitorización de precios en e-commerce y verificación de anuncios, la diferencia entre un 10% y un 95% de éxito suele ser residencial vs datacenter.
Combina residenciales con sesiones sticky para maximizar la fiabilidad: una IP estable reduce la probabilidad de CAPTCHA, y si aparece uno, puedes pausar esa sesión y reanudarla más tarde sin perder el contexto. Consulta las ubicaciones disponibles y la página de precios para dimensionar tu pool según el volumen.
Consideraciones éticas y legales
Scrapear datos públicos no es ilegal per se, pero hay matices importantes. En EE.UU., el CFAA ha sido interpretado de forma restrictiva tras el caso hiQ Labs v. LinkedIn, pero saltarse medidas de acceso sigue siendo arriesgado. En la UE, el GDPR aplica a datos personales: si tu scraping recopila datos de usuarios identificables, necesitas base legal. Reglas prácticas:
- Usa la API oficial si existe: es más estable y legalmente más seguro.
- Respeta
robots.txty los términos de servicio del sitio. - No recopiles datos personales sin base legal (consentimiento o interés legítimo).
- Limita la concurrencia para no degradar el servicio del destino.
Para casos de uso concretos, revisa nuestras guías de web scraping y SERP tracking.
Errores comunes y cómo evitarlos
- Olvidar
client.close()en Deno: en procesos largos, agotas file descriptors. Cierra el cliente al final o reutilízalo. - Mezclar HTTP y HTTPS sin
HTTPS_PROXY: si usas variables de entorno, define ambas; si no, las peticiones HTTPS no se enrutarán. - Timeouts demasiado largos: 60 segundos sin
AbortControllerbloquea el lote. Usa 10–15 segundos por petición. - Reintentos sin backoff: martillar un 429 sin esperar empeora el bloqueo. Usa backoff exponencial con jitter.
- Ignorar el certificado CA corporativo: en redes con TLS inspection, necesitas
caCertsen Deno oNODE_EXTRA_CA_CERTSen Bun.
Puntos clave
Usar proxies en Deno y Bun requiere configuración explícita:
Deno.createHttpClienten Deno y la opciónproxyen Bun. Codifica geo-segmentación y sesiones en el nombre de usuario, rota un pool de sesiones sticky conPromise.allSettled, protege conAbortControllery aplica backoff exponencial. Los proxies residenciales son la opción correcta para objetivos con bloqueo alto; los datacenter quedan para casos de baja fricción.
FAQ
¿Qué es usar proxies en Deno y Bun? Es configurar el cliente HTTP de estos runtimes para enrutar fetch() a través de un servidor proxy, ya sea con Deno.createHttpClient({ proxy }) en Deno o con la opción proxy directa en Bun.
¿Por qué importa para usuarios de proxies? Porque fetch() no soporta proxies por defecto: sin configuración explícita, tu IP real queda expuesta y el scraping falla en sitios con geo-restricciones o anti-bot.
¿Qué tipo de proxy funciona mejor? Los proxies residenciales son los más fiables para objetivos con bloqueo alto, porque usan IPs de ISPs reales. Los datacenter son útiles para tareas de baja fricción donde la velocidad importa más que la evasión.
¿Cómo evitas bloqueos al implementar proxies en Deno y Bun? Combina sesiones sticky, rotación de un pool de IPs, timeouts con AbortController, backoff exponencial y respeto a robots.txt y límites de concurrencia.






