Scraping

Cómo configurar proxies residenciales en Node.js con Axios, Fetch y Got

La configuración del agente son diez líneas. Configurar un proyecto Node para que los proxies sigan siendo configurables, verificables y económicos es la parte que lleva más tiempo. Una guía de configuración.

James Meadow

James Meadow

4 de septiembre de 2026 · 10 min de lectura

Conseguir que una sola petición pase por un proxy en Node es un problema de diez líneas. Todos los tutoriales terminan ahí, y luego el proyecto crece: aparece un segundo cliente HTTP, las credenciales quedan escritas directamente en tres archivos, alguien necesita Alemania en lugar de Estados Unidos, y nadie puede responder si las peticiones están saliendo realmente desde donde deberían.

Esta es una guía de configuración, no una guía de sintaxis. Si lo que necesitas es la configuración exacta del agente que espera cada cliente, incluyendo el motivo por el que Axios necesita proxy: false junto a su agente, eso está cubierto en detalle en using residential proxies in Node.js with Axios and Got. Lo que sigue es cómo organizar un proyecto para que la configuración se mantenga correcta a medida que crece.

Decide qué clientes vas a soportar realmente

Node tiene tres clientes HTTP de uso habitual y cada uno gestiona los proxies mediante un mecanismo distinto. Soportar los tres es normal en una base de código con cierta antigüedad, pero debería ser una decisión y no un accidente.

Axios necesita un agente explícito, porque su propia opción proxy no gestiona el tunneling HTTPS como cabría esperar. Instala https-proxy-agent.

Got recibe el agente en una propiedad agent.https en lugar de como opción de nivel superior.

Fetch nativo pasa por undici, así que necesita ProxyAgent como dispatcher. Undici viene incluido con Node, pero conviene tenerlo como dependencia explícita si vas a construir dispatchers tú mismo.

La consecuencia práctica es que tu lista de dependencias viene determinada por qué clientes mantienes, así que mantener dos clientes significa mantener dos rutas de proxy. La mayoría de las bases de código estarían mejor si estandarizaran en uno solo y convirtieran los rezagados, y las que de verdad necesitan dos deberían al menos saber por qué.

Las credenciales van en el entorno, no en el código fuente

El gateway recibe el targeting en el nombre de usuario, lo que significa que la cadena de credenciales y la configuración de enrutamiento son la misma cadena. Eso es cómodo, y también es cómo las credenciales de proxy acaban en un commit.

Mantén las piezas separadas en la configuración:

PROXY_HOST=p.shifter.io
PROXY_PORT=443
PROXY_USER=customer-USERNAME
PROXY_PASS=your-password
PROXY_COUNTRY=us

Luego ensambla el nombre de usuario en tiempo de ejecución. La regla que te ahorra problemas más adelante es que ningún archivo fuera de tu módulo de proxy debería contener una URL de proxy literal. En cuanto una cadena de credenciales con targeting incrustado aparece de forma directa en un scraper, se copia, y las copias se van desincronizando.

No pongas la contraseña en una URL que registres en logs. Las URL de proxy son la forma más habitual con la que las credenciales acaban en un agregador de logs, porque la línea de depuración natural es la URL completa.

Un solo módulo construye los agentes

La decisión estructural más importante es tener exactamente un único sitio que convierta la configuración en un agente. Es un módulo pequeño y evita toda una categoría de problemas.

proxy.js
import { HttpsProxyAgent } from "https-proxy-agent";
const { PROXY_HOST, PROXY_PORT, PROXY_USER, PROXY_PASS } = process.env;
export function proxyUrl({ country, session, ttl } = {}) {
const parts = [PROXY_USER];
if (country) parts.push("country", country);
if (session) parts.push("sid", session);
if (session && ttl) parts.push("ttl", String(ttl));
const user = parts.join("-");
return `https://${user}:${PROXY_PASS}@${PROXY_HOST}:${PROXY_PORT}`;
}
export function agentFor(opts) {
return new HttpsProxyAgent(proxyUrl(opts));
}

Vale la pena señalar explícitamente dos detalles de ese fragmento. Los indicadores de targeting se construyen a partir de opciones con nombre en lugar de concatenar cadenas en cada punto de llamada, que es lo que evita que un error tipográfico en un indicador se convierta en un 407 que parece un problema de credenciales. Y ttl solo se añade cuando hay una sesión presente, porque un time to live no tiene nada que mantener vivo sin un identificador de sesión.

Los que llaman a la función nunca ven entonces una URL:

const agent = agentFor({ country: "de", session: "job-141", ttl: 600 });
// axios
await axios.get(url, { httpsAgent: agent, proxy: false });
// got
await got(url, { agent: { https: agent } });

Fetch nativo es el que no recibe un agente, ya que undici quiere un dispatcher en su lugar:

import { ProxyAgent, fetch } from "undici";
import { proxyUrl } from "./proxy.js";
const dispatcher = new ProxyAgent(proxyUrl({ country: "de" }));
await fetch(url, { dispatcher });

Mantener proxyUrl exportado por separado es lo que permite que la ruta de fetch comparta configuración con los otros dos sin duplicar la construcción de la cadena.

Reutiliza los agentes, y decide las sesiones de antemano

Un agente mantiene un pool de conexiones. Construir uno por petición desecha ese pool cada vez, lo que se manifiesta como una latencia que acabarás diagnosticando erróneamente como un problema de red.

Construye los agentes una vez por cada configuración que uses y guárdalos en caché. Si estás rotando entre cinco países, eso son cinco agentes mantenidos durante toda la vida del proceso, no uno por petición.

Las sesiones son la decisión relacionada. Sin un identificador de sesión el gateway rota, que es lo que quieres para peticiones independientes. Con sid obtienes una IP fija, mantenida durante el ttl que especifiques, que es lo que quieres para cualquier cosa con continuidad: un conjunto de resultados paginado, un flujo de varios pasos, cualquier cosa donde la segunda petición necesite venir del mismo sitio que la primera. El tiempo fijo por defecto es de 120 segundos si no configuras uno.

Elegir esto en el momento de la configuración en lugar de en cada punto de llamada es la diferencia entre una política de rotación coherente y una base de código donde la mitad de las peticiones resultan ser fijas por casualidad. Las ventajas y desventajas están explicadas en sticky vs rotating residential proxies.

Verifica antes de construir sobre ello

Escribe el script de verificación antes que el scraper. Lleva cinco minutos y convierte toda una categoría de confusión futura en una respuesta inmediata.

import { agentFor } from "./proxy.js";
import axios from "axios";
const agent = agentFor({ country: "de" });
const { data } = await axios.get("https://api.ipify.org?format=json", {
httpsAgent: agent,
proxy: false,
});
console.log(data);

Si eso imprime tu propia dirección, el agente no está adjunto y todas las peticiones del proyecto están saliendo directas. Este es un estado genuinamente habitual en el que estar durante días, porque nada falla: el código funciona, simplemente no está usando el proxy. Comprobar que el país coincide realmente con lo que pediste es la segunda parte de la verificación, y el método para hacerlo correctamente está en testing proxy speed, success rate and location accuracy.

Conviértelo en un script dentro de package.json para que cualquiera pueda ejecutarlo cuando algo no cuadre.

Mapea los errores una sola vez

Los errores de proxy son lo bastante específicos como para diagnosticarse automáticamente, y hacerlo en un solo sitio es mejor que interpretarlos repetidamente a las tres de la madrugada:

  • 407 significa que las credenciales son incorrectas o que un indicador de targeting está mal formado. Un indicador mal escrito acaba aquí, por lo que vale la pena revisar los nombres de tus indicadores antes que tu contraseña.
  • 502 significa que ningún exit coincide con tu filtro. El filtro es demasiado restrictivo, no que la red esté caída.
  • 509 significa que la asignación de ancho de banda se ha agotado.
  • Connection refused normalmente significa un host o puerto heredado de una configuración anterior.

Solo los fallos transitorios merecen un reintento. Reintentar un 407 en bucle consume tu límite de tasa contra un problema que nunca se resolverá por sí solo, y reintentar un 509 no sirve absolutamente de nada. Envuelve los reintentos en un backoff real en lugar de un retraso fijo, tal como se explica en rate limiting and request throttling.

Limita la concurrencia de forma deliberada

Node arrancará con gusto diez mil peticiones, y ninguno de los tres clientes te lo impedirá. Bajo un proxy esto es peor de lo habitual, porque cada una de esas peticiones es un túnel que hay que establecer.

Usa un limitador de concurrencia desde el principio en lugar de añadirlo después del primer incidente. Un límite moderado con un rendimiento constante supera a una ráfaga sin límite que activa defensas y luego pasa su tiempo reintentando.

Desarrollo, CI y producción son diferentes

Tres notas prácticas que surgen en todos los proyectos de Node en cuanto los proxies entran en juego.

El tráfico residencial se factura por ancho de banda, así que una suite de pruebas que golpea objetivos reales a través del proxy es una factura recurrente sin ningún beneficio. Simula la capa HTTP en las pruebas unitarias y mantén un número reducido de peticiones reales en una comprobación separada, activada manualmente.

El desarrollo local debería usar el mismo módulo y las mismas variables de entorno que producción, con valores distintos. Una configuración que solo existe en producción es una configuración que nadie ha probado.

Y las imágenes de contenedor no deberían incluir credenciales incrustadas. Pásalas en tiempo de ejecución, igual que cualquier otro secreto.

Preguntas frecuentes

¿Necesito https-proxy-agent si solo uso fetch?

No. ProxyAgent de undici cubre esa ruta. Necesitas el paquete de agente separado para Axios y Got.

¿Por qué Axios necesita proxy: false cuando ya le he dado un agente?

Porque de lo contrario Axios intentará aplicar su propia gestión de proxy encima del agente, y las dos cosas no son compatibles entre sí. Ponerlo en false deja el trabajo enteramente en manos del agente.

¿Puedo configurar el targeting por petición en lugar de por agente?

Puedes, pero cada configuración distinta es un agente distinto, así que construir uno por petición es lo que te cuesta el pool de conexiones. Guárdalos en caché por clave de configuración.

¿El país debería ser un valor de configuración o un argumento por llamada?

Ambas cosas, en la práctica. Un valor por defecto en la configuración, que se pueda sobrescribir por llamada para los trabajos que necesiten una región específica. Lo que quieres evitar es que el país aparezca como literal dentro de scrapers individuales.

La conclusión

La configuración de proxy en Node es pequeña y bien entendida. Lo que determina si un proyecto se mantiene mantenible es la organización a su alrededor: un módulo que construye agentes a partir de la configuración, credenciales que viven en el entorno, agentes reutilizados en lugar de reconstruidos, una política de sesiones elegida deliberadamente, un script de verificación que existe antes que el scraper, y una gestión de errores que sabe distinguir entre un filtro demasiado restrictivo y una contraseña incorrecta.

Configura esto una vez y añadir una biblioteca de cliente o una nueva región se convierte en un cambio de configuración. Sáltatelo y cada nuevo requisito es una búsqueda en la base de código de cadenas escritas directamente en el código. Los detalles del gateway están en la página de proxies residenciales, con las tarifas de ancho de banda en la página de precios.

¿Listo para empezar?

Prueba los proxies residenciales de Shifter, más de 205M IPs, más de 195 países, desde 0,75 $/GB.

Comenzar