Si alguna vez has enviado miles de peticiones con got o axios y de repente recibes 403 Forbidden en el 80 % de tus responses, no estás solo. Los sistemas anti-bot modernos no solo miran la IP: inspeccionan el orden de las cabeceras, los valores de sec-ch-ua, el fingerprint TLS y la coherencia entre el user-agent declarado y el resto del handshake HTTP/2. Esta guía explica cómo got-scraping en Node.js resuelve la capa de cabeceras y cómo combinarlo con proxies residenciales de ProxyHat para alcanzar tasas de éxito superiores al 95 % en scraping a escala.
Por qué las peticiones crudas de got o axios son detectadas
El problema empieza en algo tan básico como el orden de las cabeceras. Los navegadores reales envían cabeceras en un orden predecible y específico por motor (Chromium, Firefox, WebKit). Una petición generada con got o axios envía las cabeceras en el orden en que las añades al objeto options, lo cual casi nunca coincide con el de un navegador real. Sistemas como Akamai Bot Manager, Datadome y Cloudflare Bot Management comparan ese orden contra perfiles conocidos y bloquean lo que no encaja.
Además, los clientes HTTP de Node.js por defecto:
- No envían
sec-ch-ua,sec-ch-ua-mobilenisec-ch-ua-platform, que son obligatorias en Chromium desde 2020. - Declaran un
Accept-Languagegenérico o inexistente. - Negocian HTTP/2 con un fingerprint ALPS/SETTINGS distinto al de Chrome.
- No incluyen cabeceras como
sec-fetch-site,sec-fetch-modeysec-fetch-destque los navegadores añaden automáticamente.
El resultado: aunque tu IP sea limpia, el servidor te identifica como bot en menos de 200 ms. Según la documentación de MDN sobre client hints, las cabeceras Sec-CH-UA son enviadas por el navegador para describir su marca y versión, y su ausencia es una señal inequívoca de cliente no-navegador.
La superficie idiomática de got-scraping
got-scraping es un wrapper sobre got desarrollado por Apify que integra un generador de cabeceras coherente y soporte nativo de proxies HTTP/2. La idea no es parchear got con hacks, sino usar sus puntos de extensión oficiales: got.extend, hooks y opciones de instancia.
got.extend y useHeaderGenerator
El patrón idiomático es crear una instancia extendida de got con el plugin useHeaderGenerator y un objeto headerGeneratorOptions que define el perfil del navegador simulado:
const { gotScraping } = require('got-scraping');
const client = gotScraping.extend({
headerGeneratorOptions: {
browsers: [{ name: 'chrome', minVersion: 120 }],
devices: ['desktop'],
operatingSystems: ['windows', 'macos'],
locales: ['es-ES', 'en-US'],
},
http2: true,
timeout: { request: 30000 },
retry: { limit: 3 },
});
module.exports = client;
Aquí headerGeneratorOptions acepta arrays de browsers, devices y operatingSystems. Cada petición recibe un conjunto de cabeceras coherente: si el user-agent dice Chrome 121 en Windows, las sec-ch-ua coinciden, el sec-ch-ua-platform dice "Windows", y el orden de cabeceras sigue el patrón exacto de Chromium. Esto es lo que got scraping header generator hace internamente: construye un perfil válido y lo serializa en el orden correcto.
La opción proxyUrl para HTTP/1 y HTTP/2
got-scraping soporta proxyUrl tanto para proxies HTTP/1 como HTTP/2. Para proxies HTTP/2, usa el módulo http2-wrapper y tunela a través de CONNECT. La firma es simple:
const response = await client.get('https://example.com/api/data', {
proxyUrl: 'http://user-country-US:pass@gate.proxyhat.com:8080',
});
El proxy puede ser estático por instancia o dinámico por petición, lo que permite rotar IPs sin reconfigurar el cliente entero.
Por qué los proxies residenciales importan una vez las cabeceras y el TLS son reales
Cuando got-scraping ya genera cabeceras perfectas y el fingerprint HTTP/2 coincide con Chrome, el siguiente vector de detección es la IP. Un datacenter IP en ASN de AWS o DigitalOcean es trivialmente identificable: servicios como MaxMind GeoIP2 clasifican el tipo de ASN (ISP residencial vs hosting) con alta precisión. Si tu user-agent dice ser un Chrome en casa pero la IP pertenece a un rango de OVH, el anti-bot marca la inconsistencia.
Los proxies residenciales de ProxyHat usan IPs asignadas por ISPs reales a hogares reales, por lo que el ASN coincide con el de un usuario doméstico. Combinado con cabeceras coherentes de got-scraping, el resultado es un perfil que pasa los filtros más estrictos. Para casos donde necesitas evasión de CAPTCHA activa o acceso a contenido móvil, los proxies móviles (4G/5G) ofrecen rotación natural de IP por torre celular.
| Tipo de proxy | ASN | Latencia típica | Detección anti-bot | Caso ideal |
|---|---|---|---|---|
| Datacenter | Hosting (AWS, Hetzner…) | 50–150 ms | Alta | APIs públicas, scraping sin WAF |
| Residencial | ISP doméstico | 200–800 ms | Baja | SERP, e-commerce, sitios con WAF |
| Móvil | Operador 4G/5G | 300–1200 ms | Muy baja | Redes sociales, apps móviles |
ProxyHat ofrece los tres tipos. Para empezar, consulta la página de precios y las ubicaciones disponibles. Para casos de uso específicos como web scraping o SERP tracking, los residenciales suelen ser el punto de partida correcto.
Enrutando a través de ProxyHat: HTTP y SOCKS5
ProxyHat expone un gateway único en gate.proxyhat.com. El puerto 8080 maneja HTTP/HTTPS y el puerto 1080 maneja SOCKS5. El geo-targeting y el control de sesión se pasan en el username, no en la URL path:
user-country-US:pass— IP residencial en EE. UU.user-country-DE-city-berlin:pass— IP en Berlín, Alemania.user-session-abc123:pass— sesión sticky que mantiene la misma IP entre peticiones.
Para SOCKS5, el formato es socks5://user-country-US:pass@gate.proxyhat.com:1080. got-scraping soporta SOCKS5 vía el paquete socks-proxy-agent, que se puede pasar como agent en las opciones.
Ejemplo completo: rotación de IPs residenciales con hooks de reintento
El siguiente ejemplo muestra un cliente got-scraping que rota sesiones residenciales por petición, reintentando con una nueva IP en cada fallo. Usa beforeRequest para inyectar el proxy dinámico y afterResponse para detectar bloques:
const { gotScraping } = require('got-scraping');
const crypto = require('crypto');
const PROXYHAT_USER = process.env.PROXYHAT_USER;
const PROXYHAT_PASS = process.env.PROXYHAT_PASS;
const GATEWAY = 'gate.proxyhat.com:8080';
function buildProxyUrl(country = 'US') {
const session = crypto.randomBytes(6).toString('hex');
const username = `${PROXYHAT_USER}-country-${country}-session-${session}`;
return `http://${username}:${PROXYHAT_PASS}@${GATEWAY}`;
}
const scraper = gotScraping.extend({
headerGeneratorOptions: {
browsers: [{ name: 'chrome', minVersion: 120 }],
devices: ['desktop'],
operatingSystems: ['windows', 'macos'],
},
http2: true,
timeout: { request: 30000 },
retry: { limit: 4, statusCodes: [403, 429, 500, 502, 503] },
hooks: {
beforeRequest: [
(options) => {
// Rota el proxy en cada intento
options.proxyUrl = buildProxyUrl('US');
return options;
},
],
afterResponse: [
(response, retryWithMergedOptions) => {
if (response.statusCode === 403 || response.statusCode === 429) {
// Fuerza reintento con nueva IP
return retryWithMergedOptions({
proxyUrl: buildProxyUrl('US'),
});
}
return response;
},
],
},
});
(async () => {
for (let i = 0; i < 50; i++) {
const res = await scraper.get('https://httpbin.org/headers');
console.log(i, res.statusCode, JSON.parse(res.body).headers['User-Agent']);
}
})();
Cada iteración usa una sesión distinta, por lo que ProxyHat asigna una IP residencial nueva. El hook afterResponse detecta 403/429 y reintenta con otra IP sin que el código de llamada se entere. Esto es got-scraping proxy usado de forma idiomática: no hay hacks, solo los puntos de extensión que el framework expone.
Para generar usernames de forma más robusta, puedes consultar la documentación oficial de ProxyHat o construir un pequeño SDK interno que centralice la lógica de geo-targeting.
Patrones de producción: concurrencia, cookies y Crawlee
Concurrencia acotada con p-limit
Si lanzas 500 peticiones en paralelo sin control, agotas los sockets de Node.js y disparas rate limits. Usa p-limit para acotar la concurrencia a un nivel razonable, por ejemplo 20 conexiones simultáneas:
const pLimit = require('p-limit');
const limit = pLimit(20);
const urls = [/* ... 500 URLs ... */];
const results = await Promise.all(
urls.map((url) => limit(() => scraper.get(url)))
);
Con 20 conexiones concurrentes y proxies residenciales, puedes procesar unas 80–120 peticiones por segundo con latencia media de 400 ms. Ajusta el límite según la capacidad de tu pool de IPs y la tolerancia del sitio objetivo.
Cookie jars para sesiones con estado
Algunos sitios requieren mantener cookies entre peticiones (tokens CSRF, sesiones de login). got-scraping hereda el soporte de tough-cookie de got. Pasa cookieJar en las opciones:
const { CookieJar } = require('tough-cookie');
const jar = new CookieJar();
const sessionClient = scraper.extend({ cookieJar: jar });
await sessionClient.post('https://example.com/login', { json: { user: 'x', pass: 'y' } });
await sessionClient.get('https://example.com/dashboard');
Si usas sesiones sticky de ProxyHat (-session-abc123) junto con un cookie jar, mantienes la misma IP y las mismas cookies durante toda la sesión lógica.
Escalando a Crawlee y CheerioCrawler
Cuando el scraping crece más allá de unos cientos de URLs, lo idiomático es subir a Crawlee, el framework de Apify que envuelve got-scraping. CheerioCrawler usa got-scraping internamente y añade cola de URLs, deduplicación, rotación automática de proxies y gestión de errores. La integración con ProxyHat es directa vía proxyConfiguration:
const { CheerioCrawler, ProxyConfiguration } = require('crawlee');
const proxyConfiguration = new ProxyConfiguration({
proxyUrls: [
'http://user-country-US-session-s1:pass@gate.proxyhat.com:8080',
'http://user-country-US-session-s2:pass@gate.proxyhat.com:8080',
'http://user-country-US-session-s3:pass@gate.proxyhat.com:8080',
],
});
const crawler = new CheerioCrawler({
proxyConfiguration,
requestHandler: async ({ $, request }) => {
const title = $('title').text();
console.log(request.url, title);
},
maxConcurrency: 20,
});
await crawler.run(['https://example.com/page1', 'https://example.com/page2']);
Crawlee rota entre las URLs de proxy del array y reintenta automáticamente con otra IP cuando una petición falla. Para rotación dinámica por país o ciudad, puedes implementar una ProxyConfiguration custom que genere usernames al vuelo.
Cuándo un navegador headless es inevitable
got-scraping resuelve la capa HTTP, pero no ejecuta JavaScript. Si el sitio renderiza contenido dinámicamente (React, Vue, SPAs) o usa desafíos de Cloudflare Turnstile / reCAPTCHA que requieren ejecución de JS, necesitas un navegador headless. En ese caso, usa PlaywrightCrawler de Crawlee con los mismos proxies residenciales de ProxyHat. La regla práctica: si el HTML que ves en view-source: contiene los datos, usa got-scraping; si necesitas esperar a que JS pueble el DOM, usa headless.
Un navegador headless consume unos 150–300 MB de RAM por instancia, frente a los ~20 MB de un proceso got-scraping. Para un fleet de 100 instancias concurrentes en contenedores Docker, eso significa 15–30 GB de RAM solo en navegadores. Por eso, prefiere got-scraping siempre que sea posible y reserva headless para los casos donde no haya alternativa.
Ética y consideraciones legales
Antes de cualquier scraping, verifica que el dato que buscas es público, revisa el ToS del sitio y considera si existe una API oficial. En EE. UU., el CFAA puede aplicar a accesos no autorizados; en la UE, el GDPR protege datos personales. No escrapees datos personales sin base legal.
Prácticas recomendadas:
- Honra
robots.txt: si una ruta está enDisallow, no la escrapees salvo justificación documentada. - Respeta rate limits: si el sitio devuelve
429, reduce la concurrencia, no la fuerces. - Prefiere APIs oficiales cuando existan. Son más estables, más rápidas y legalmente más seguras.
- No almacenes datos personales sin consentimiento. El anonimizado o agregación reduce riesgo bajo GDPR.
Puntos clave
- got-scraping genera cabeceras coherentes de navegador (orden, sec-ch-ua, accept-language) y soporta HTTP/2, cerrando la brecha que deja
gotyaxioscrudos. - Los puntos de extensión idiomáticos son
got.extend,headerGeneratorOptions,proxyUrly los hooksbeforeRequest/afterResponse. - Los proxies residenciales de ProxyHat (
gate.proxyhat.com:8080HTTP,:1080SOCKS5) eliminan el vector de detección por ASN una vez las cabeceras y el TLS son correctos. - La rotación de sesiones se controla vía username (
-country-US-session-abc123), sin reconfigurar el cliente. - Para escala, sube a Crawlee con
CheerioCrawleryProxyConfiguration; reserva headless para contenido que requiere ejecución de JS. - Ética primero: datos públicos, robots.txt, rate limits y preferencia por APIs oficiales.
¿Listo para empezar? Revisa los planes de ProxyHat y configura tu primer cliente got-scraping con proxies residenciales en menos de 10 minutos.






