Los sitios web modernos han aprendido a reconocer navegadores automatizados (Selenium, Puppeteer, Playwright) y bloquean sus solicitudes. Los navegadores sin cabeza dejan decenas de huellas digitales, que los sistemas anti-bots utilizan para detectar la automatización en milisegundos. En esta guía, analizaremos todos los métodos de ocultación de navegadores sin cabeza con ejemplos de código en Python y JavaScript, para que tus scrapers funcionen de manera estable sin bloqueos.
Este artículo está destinado a desarrolladores que se dedican al web scraping, automatización de pruebas o recopilación de datos de sitios protegidos. Examinaremos los detalles técnicos de la detección y soluciones prácticas para eludir la protección.
Cómo los sitios detectan navegadores sin cabeza: métodos principales
Los sistemas anti-bots utilizan una verificación de navegador en múltiples niveles, analizando cientos de parámetros. Los navegadores sin cabeza se diferencian de los normales por múltiples signos que no se pueden ocultar simplemente reemplazando el User-Agent. Comprender los métodos de detección es el primer paso hacia una ocultación efectiva.
Marcadores de automatización de JavaScript
El método más común es la verificación de propiedades de JavaScript que solo aparecen en navegadores automatizados:
navigator.webdriver— devuelvetrueen Selenium y Puppeteerwindow.chrome— ausente en Chrome sin cabezanavigator.plugins.length— igual a 0 en modo sin cabezanavigator.languages— a menudo un array vacío o solo contiene "en-US"navigator.permissions— la API funciona de manera diferente en modo sin cabeza
Análisis del Protocolo de Chrome DevTools
Puppeteer y Playwright controlan el navegador a través del Protocolo de Chrome DevTools (CDP). La presencia de una conexión CDP se puede detectar a través de verificaciones especiales de JavaScript que analizan objetos window.cdc_ o verifican anomalías en el comportamiento de eventos del mouse y teclado.
Canvas y WebGL Fingerprinting
Los navegadores sin cabeza generan huellas digitales idénticas de Canvas y WebGL, ya que utilizan renderizado por software en lugar de hardware. Los sistemas anti-bots crean un elemento Canvas invisible, dibujan texto o figuras en él y calculan el hash de la imagen. Si miles de usuarios tienen un hash idéntico, es una señal de bots.
Análisis de comportamiento
Los sistemas modernos (DataDome, PerimeterX, Cloudflare Bot Management) analizan los movimientos del mouse, la velocidad de desplazamiento, los patrones de clics. Los navegadores sin cabeza realizan acciones instantáneamente y sin retrasos naturales, lo que revela la automatización. También se analizan eventos: en un navegador normal, siempre ocurre un evento mousemove antes de un clic, mientras que los bots a menudo hacen clic sin movimiento previo del mouse.
Importante: Los sistemas anti-bots modernos utilizan aprendizaje automático para analizar cientos de parámetros simultáneamente. Ocultar solo un parámetro (por ejemplo, User-Agent) no protegerá contra bloqueos: se necesita un enfoque integral.
Eliminación de navigator.webdriver y otros marcadores de JavaScript
La propiedad navigator.webdriver es la forma más sencilla de detectar Selenium y otras herramientas de WebDriver. En un navegador normal, esta propiedad devuelve undefined, mientras que en uno automatizado devuelve true. Se puede eliminar ejecutando código JavaScript antes de cargar la página.
Selenium (Python): eliminación de webdriver a través de CDP
Para Selenium, se debe utilizar el Protocolo de Chrome DevTools para ejecutar JavaScript antes de cargar cualquier página:
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
options = Options()
options.add_argument('--disable-blink-features=AutomationControlled')
driver = webdriver.Chrome(options=options)
# Eliminación de navigator.webdriver a través de CDP
driver.execute_cdp_cmd('Page.addScriptToEvaluateOnNewDocument', {
'source': '''
Object.defineProperty(navigator, 'webdriver', {
get: () => undefined
});
'''
})
driver.get('https://example.com')
La opción --disable-blink-features=AutomationControlled desactiva la bandera que Chrome agrega en modo de automatización. Esta es una protección básica que debe combinarse con otros métodos.
Puppeteer (Node.js): ocultación a través de Page.evaluateOnNewDocument
En Puppeteer, se utiliza el método page.evaluateOnNewDocument() para ejecutar código antes de cargar la página:
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch({
headless: true,
args: ['--disable-blink-features=AutomationControlled']
});
const page = await browser.newPage();
// Eliminación de webdriver y otros marcadores
await page.evaluateOnNewDocument(() => {
Object.defineProperty(navigator, 'webdriver', {
get: () => undefined
});
// Adición del objeto chrome
window.chrome = {
runtime: {}
};
// Emulación de plugins
Object.defineProperty(navigator, 'plugins', {
get: () => [1, 2, 3, 4, 5]
});
});
await page.goto('https://example.com');
})();
Playwright: opciones de ocultación integradas
Playwright tiene una ocultación más avanzada de serie, pero aún se requiere configuración adicional:
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch({
headless: true,
args: ['--disable-blink-features=AutomationControlled']
});
const context = await browser.newContext({
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
});
const page = await context.newPage();
await page.addInitScript(() => {
Object.defineProperty(navigator, 'webdriver', {
get: () => undefined
});
});
await page.goto('https://example.com');
})();
Ocultación del Protocolo de Chrome DevTools
Puppeteer y Playwright dejan huellas de la conexión CDP que se pueden detectar a través del análisis de objetos window. Los sistemas anti-bots buscan variables con el prefijo cdc_, $cdc_ o __webdriver_, que son creadas por Chrome al conectarse a través del Protocolo DevTools.
Eliminación de variables CDP
El siguiente script elimina todas las variables relacionadas con la automatización del objeto window:
await page.evaluateOnNewDocument(() => {
// Eliminación de todas las variables con el prefijo cdc_
const cdcProps = Object.keys(window).filter(prop =>
prop.includes('cdc_') ||
prop.includes('$cdc_') ||
prop.includes('__webdriver_')
);
cdcProps.forEach(prop => {
delete window[prop];
});
// Redefinición de document.__proto__ para ocultar CDP
const originalQuery = document.querySelector;
document.querySelector = function(selector) {
if (selector.includes('cdc_')) return null;
return originalQuery.call(this, selector);
};
});
Uso de versiones parcheadas de Chromium
Existen compilaciones modificadas de Chromium que no dejan huellas CDP. Por ejemplo, la biblioteca puppeteer-extra con el plugin puppeteer-extra-plugin-stealth aplica automáticamente decenas de parches para ocultar CDP.
Configuración de User-Agent y encabezados HTTP
Los navegadores sin cabeza utilizan cadenas de User-Agent obsoletas o poco realistas. Por ejemplo, Puppeteer agrega por defecto la palabra "HeadlessChrome" en el User-Agent. Además, es necesario configurar encabezados adicionales que están presentes en las solicitudes de navegadores normales.
User-Agent actualizados para ocultación
Utiliza User-Agent recientes de navegadores reales. Aquí hay ejemplos para 2024:
# Chrome 120 en Windows 10
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36
# Chrome 120 en macOS
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36
# Firefox 121 en Windows
Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0
Configuración de encabezados en Selenium
from selenium.webdriver.chrome.options import Options
options = Options()
options.add_argument('user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36')
# Encabezados adicionales a través de CDP
driver.execute_cdp_cmd('Network.setUserAgentOverride', {
"userAgent": 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
"platform": 'Win32',
"acceptLanguage": 'en-US,en;q=0.9'
})
Configuración de encabezados en Puppeteer
await page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36');
await page.setExtraHTTPHeaders({
'Accept-Language': 'en-US,en;q=0.9',
'Accept-Encoding': 'gzip, deflate, br',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
'Sec-Fetch-Site': 'none',
'Sec-Fetch-Mode': 'navigate',
'Sec-Fetch-User': '?1',
'Sec-Fetch-Dest': 'document'
});
Los encabezados Sec-Fetch-* son críticos: aparecieron en Chrome 76+ y su ausencia revela versiones antiguas de navegadores o bots.
Emulación de Canvas y WebGL Fingerprint
La huella digital de Canvas y WebGL es un método poderoso de detección, ya que los navegadores sin cabeza generan huellas digitales idénticas. Los sistemas anti-bots crean un Canvas invisible, dibujan texto en él y calculan el hash de los píxeles. Si miles de solicitudes tienen el mismo hash, son bots.
Adición de ruido en Canvas
El siguiente script añade ruido aleatorio a la huella digital de Canvas, haciendo que cada solicitud sea única:
await page.evaluateOnNewDocument(() => {
const originalToDataURL = HTMLCanvasElement.prototype.toDataURL;
const originalToBlob = HTMLCanvasElement.prototype.toBlob;
const originalGetImageData = CanvasRenderingContext2D.prototype.getImageData;
// Función para añadir ruido
const addNoise = (canvas, context) => {
const imageData = originalGetImageData.call(context, 0, 0, canvas.width, canvas.height);
for (let i = 0; i < imageData.data.length; i += 4) {
imageData.data[i] += Math.floor(Math.random() * 10) - 5;
imageData.data[i + 1] += Math.floor(Math.random() * 10) - 5;
imageData.data[i + 2] += Math.floor(Math.random() * 10) - 5;
}
context.putImageData(imageData, 0, 0);
};
HTMLCanvasElement.prototype.toDataURL = function() {
addNoise(this, this.getContext('2d'));
return originalToDataURL.apply(this, arguments);
};
});
Emulación de parámetros de WebGL
WebGL revela información sobre la tarjeta gráfica y los controladores. En modo sin cabeza, estos parámetros revelan renderizado por software:
await page.evaluateOnNewDocument(() => {
const getParameter = WebGLRenderingContext.prototype.getParameter;
WebGLRenderingContext.prototype.getParameter = function(parameter) {
// Emulación de una tarjeta gráfica real
if (parameter === 37445) {
return 'Intel Inc.';
}
if (parameter === 37446) {
return 'Intel Iris OpenGL Engine';
}
return getParameter.call(this, parameter);
};
});
El parámetro 37445 es UNMASKED_VENDOR_WEBGL, y 37446 es UNMASKED_RENDERER_WEBGL. En modo sin cabeza, devuelven "Google SwiftShader", lo que revela la automatización.
Selenium Stealth: soluciones listas para Python
La biblioteca selenium-stealth aplica automáticamente decenas de parches para ocultar Selenium. Esta es la solución más sencilla para desarrolladores de Python, que no requiere configuración manual de cada parámetro.
Instalación y configuración básica
pip install selenium-stealth
from selenium import webdriver
from selenium_stealth import stealth
from selenium.webdriver.chrome.options import Options
options = Options()
options.add_argument("--headless")
options.add_argument("--no-sandbox")
options.add_argument("--disable-dev-shm-usage")
driver = webdriver.Chrome(options=options)
# Aplicación de parches de stealth
stealth(driver,
languages=["en-US", "en"],
vendor="Google Inc.",
platform="Win32",
webgl_vendor="Intel Inc.",
renderer="Intel Iris OpenGL Engine",
fix_hairline=True,
)
driver.get("https://bot.sannysoft.com")
driver.save_screenshot("test.png")
La biblioteca elimina automáticamente navigator.webdriver, añade window.chrome, emula plugins, oculta WebGL y aplica más de 20 parches. Esto cubre el 80% de los casos de detección.
Configuración avanzada con proxies
Para una ocultación completa, combina selenium-stealth con proxies residenciales: proporcionan direcciones IP reales de usuarios domésticos, lo que es crítico para eludir sistemas anti-bots avanzados:
from selenium.webdriver.common.proxy import Proxy, ProxyType
proxy = Proxy()
proxy.proxy_type = ProxyType.MANUAL
proxy.http_proxy = "ip:port"
proxy.ssl_proxy = "ip:port"
capabilities = webdriver.DesiredCapabilities.CHROME
proxy.add_to_capabilities(capabilities)
driver = webdriver.Chrome(desired_capabilities=capabilities, options=options)
stealth(driver, languages=["en-US", "en"], vendor="Google Inc.", platform="Win32")
Puppeteer Extra Stealth Plugin para Node.js
Para Puppeteer, existe un plugin puppeteer-extra-plugin-stealth, que es la solución más avanzada para ocultar navegadores sin cabeza en el ecosistema de JavaScript. Contiene 23 módulos independientes, cada uno de los cuales oculta un aspecto específico de la automatización.
Instalación y uso básico
npm install puppeteer-extra puppeteer-extra-plugin-stealth
const puppeteer = require('puppeteer-extra');
const StealthPlugin = require('puppeteer-extra-plugin-stealth');
puppeteer.use(StealthPlugin());
(async () => {
const browser = await puppeteer.launch({
headless: true,
args: ['--no-sandbox', '--disable-setuid-sandbox']
});
const page = await browser.newPage();
await page.goto('https://bot.sannysoft.com');
await page.screenshot({ path: 'test.png' });
await browser.close();
})();
Qué oculta el Stealth Plugin
El plugin aplica automáticamente los siguientes parches:
- Eliminación de
navigator.webdriver - Adición del objeto
window.chrome - Emulación de la API
navigator.permissions - Ocultación de
navigator.pluginsynavigator.mimeTypes - Emulación de Canvas y WebGL fingerprint
- Ocultación de User-Agent y lenguajes
- Corrección de anomalías en el contentWindow de iframe
- Emulación de Battery API, Media Devices, WebRTC
Configuración con proxies y parámetros adicionales
const puppeteer = require('puppeteer-extra');
const StealthPlugin = require('puppeteer-extra-plugin-stealth');
const AdblockerPlugin = require('puppeteer-extra-plugin-adblocker');
puppeteer.use(StealthPlugin());
puppeteer.use(AdblockerPlugin({ blockTrackers: true }));
(async () => {
const browser = await puppeteer.launch({
headless: true,
args: [
'--proxy-server=http://your-proxy:port',
'--disable-web-security',
'--disable-features=IsolateOrigins,site-per-process'
]
});
const page = await browser.newPage();
// Autenticación del proxy
await page.authenticate({
username: 'your-username',
password: 'your-password'
});
await page.setViewport({ width: 1920, height: 1080 });
await page.goto('https://example.com', { waitUntil: 'networkidle2' });
})();
Playwright: configuración de anti-detección
Playwright tiene una arquitectura más avanzada en comparación con Puppeteer y deja menos huellas de automatización. Sin embargo, para eludir sistemas anti-bots avanzados, se necesita configuración adicional. Para Playwright existe un puerto del plugin stealth: playwright-extra.
Instalación de playwright-extra
npm install playwright-extra playwright-extra-plugin-stealth playwright
const { chromium } = require('playwright-extra');
const stealth = require('playwright-extra-plugin-stealth');
chromium.use(stealth());
(async () => {
const browser = await chromium.launch({ headless: true });
const context = await browser.newContext({
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
viewport: { width: 1920, height: 1080 },
locale: 'en-US',
timezoneId: 'America/New_York'
});
const page = await context.newPage();
await page.goto('https://bot.sannysoft.com');
await page.screenshot({ path: 'test.png' });
await browser.close();
})();
Configuración del contexto del navegador para una máxima ocultación
Playwright permite crear contextos de navegador aislados con configuraciones individuales. Esto es útil para multi-cuentas o scraping paralelo:
const context = await browser.newContext({
userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36',
viewport: { width: 1440, height: 900 },
locale: 'en-US',
timezoneId: 'America/Los_Angeles',
permissions: ['geolocation'],
geolocation: { latitude: 37.7749, longitude: -122.4194 },
colorScheme: 'light',
deviceScaleFactor: 2,
isMobile: false,
hasTouch: false,
// Configuración de proxy para el contexto
proxy: {
server: 'http://your-proxy:port',
username: 'user',
password: 'pass'
}
});
Los parámetros geolocation y timezoneId deben coincidir con la dirección IP del proxy, de lo contrario, los sistemas anti-bots detectarán la discrepancia (por ejemplo, IP de California, pero timezone de Nueva York).
Rotación de proxies para reducir el riesgo de bloqueo
Incluso un navegador sin cabeza perfectamente oculto puede ser bloqueado si utiliza una dirección IP para cientos de solicitudes. Los sistemas anti-bots modernos analizan la frecuencia de solicitudes desde una IP y bloquean actividades sospechosas. La rotación de proxies es un elemento obligatorio de protección al hacer scraping.
Tipos de proxies para scraping: comparación
| Tipo de proxy | Velocidad | Trust Score | Mejor para |
|---|---|---|---|
| Datacenter | Muy alta (50-200 ms) | Bajo | Sitios simples, scraping masivo |
| Residenciales | Media (300-1000 ms) | Alto | Sitios protegidos, redes sociales |
| Móviles | Baja (500-2000 ms) | Muy alto | Aplicaciones móviles, Instagram, TikTok |
Para hacer scraping de sitios protegidos (marketplaces, redes sociales, plataformas publicitarias), se recomiendan proxies residenciales, ya que tienen IP reales de usuarios domésticos y no caen en las listas negras de sistemas anti-bots.
Implementación de rotación de proxies en Puppeteer
const puppeteer = require('puppeteer-extra');
const StealthPlugin = require('puppeteer-extra-plugin-stealth');
puppeteer.use(StealthPlugin());
const proxyList = [
'http://user1:pass1@proxy1:port',
'http://user2:pass2@proxy2:port',
'http://user3:pass3@proxy3:port'
];
async function scrapeWithRotation(urls) {
for (let i = 0; i < urls.length; i++) {
const proxy = proxyList[i % proxyList.length];
const browser = await puppeteer.launch({
headless: true,
args: [`--proxy-server=${proxy}`]
});
const page = await browser.newPage();
try {
await page.goto(urls[i], { waitUntil: 'networkidle2' });
const data = await page.evaluate(() => document.body.innerText);
console.log(data);
} catch (error) {
console.error(`Error en ${urls[i]}:`, error);
} finally {
await browser.close();
}
// Retraso entre solicitudes
await new Promise(resolve => setTimeout(resolve, 2000));
}
}
scrapeWithRotation([
'https://example1.com',
'https://example2.com',
'https://example3.com'
]);
Rotación a través de proxies basados en sesión
Algunos proveedores de proxies (incluido ProxyCove) ofrecen rotación basada en sesión: cada solicitud recibe automáticamente una nueva IP sin necesidad de reiniciar el navegador. Esto se implementa a través de un formato especial de URL de proxy:
// Formato: username-session-RANDOM:password@gateway:port
const generateSessionProxy = () => {
const sessionId = Math.random().toString(36).substring(7);
return `http://username-session-${sessionId}:password@gateway.proxycove.com:12321`;
};
const browser = await puppeteer.launch({
args: [`--proxy-server=${generateSessionProxy()}`]
});
Cómo comprobar la calidad de la ocultación: herramientas de prueba
Después de configurar la ocultación, es necesario comprobar qué tan bien tu navegador sin cabeza imita a un usuario normal. Existen varios sitios especializados que analizan decenas de parámetros del navegador y muestran qué huellas de automatización han quedado.
Herramientas de prueba principales
- bot.sannysoft.com — verifica más de 15 parámetros de detección, incluyendo webdriver, objeto Chrome, plugins, Canvas
- arh.antoinevastel.com/bots/areyouheadless — se especializa en la detección de Chrome sin cabeza
- pixelscan.net — análisis avanzado de huellas digitales con visualización de todos los parámetros
- abrahamjuliot.github.io/creepjs — el análisis más detallado (más de 200 parámetros), muestra el nivel de confianza del navegador
- iphey.com — verificación de la dirección IP para determinar si pertenece a un proxy o VPN
Automatización de pruebas
Crea un script para verificar automáticamente la ocultación después de cada cambio en la configuración:
const puppeteer = require('puppeteer-extra');
const StealthPlugin = require('puppeteer-extra-plugin-stealth');
puppeteer.use(StealthPlugin());
async function testStealth() {
const browser = await puppeteer.launch({ headless: true });
const page = await browser.newPage();
// Prueba 1: Sannysoft
await page.goto('https://bot.sannysoft.com');
await page.screenshot({ path: 'test-sannysoft.png', fullPage: true });
// Prueba 2: Are you headless
await page.goto('https://arh.antoinevastel.com/bots/areyouheadless');
const headlessDetected = await page.evaluate(() => {
return document.body.innerText.includes('You are not Chrome headless');
});
console.log('Detectado como headless:', !headlessDetected);
// Prueba 3: Propiedad webdriver
const webdriverPresent = await page.evaluate(() => navigator.webdriver);
console.log('navigator.webdriver:', webdriverPresent);
// Prueba 4: Objeto Chrome
const chromePresent = await page.evaluate(() => !!window.chrome);
console.log('Objeto window.chrome presente:', chromePresent);
await browser.close();
}
testStealth();
Lista de verificación para una ocultación exitosa
Tu navegador sin cabeza está correctamente oculto si:
navigator.webdriverdevuelveundefinedwindow.chromeexiste y contiene el objetoruntimenavigator.plugins.lengthes mayor que 0- El proveedor y el renderer de WebGL muestran una tarjeta gráfica real, no SwiftShader
- La huella digital de Canvas es única para cada sesión
- El User-Agent corresponde a la versión actual de Chrome/Firefox
- La dirección IP del proxy no está en listas negras (verificación a través de iphey.com)
- La zona horaria y el locale coinciden con la geolocalización de la dirección IP
Conclusión
La ocultación de navegadores sin cabeza es una tarea compleja que requiere atención a decenas de parámetros. Los sistemas anti-bots modernos utilizan aprendizaje automático y analizan cientos de características del navegador simultáneamente, por lo que simplemente reemplazar el User-Agent ya no funciona. Para un scraping exitoso, es necesario combinar varios métodos de protección.
Los elementos clave de una ocultación efectiva incluyen la eliminación de marcadores de automatización de JavaScript (navigator.webdriver, variables CDP), la emulación de huellas digitales de Canvas y WebGL, la configuración de encabezados HTTP realistas y el uso de proxies de calidad. Las soluciones listas (selenium-stealth para Python, puppeteer-extra-plugin-stealth para Node.js) cubren el 80% de los casos, pero para eludir protecciones avanzadas se requiere configuración adicional.
Un punto crítico es la elección de proxies. Incluso un navegador perfectamente oculto será bloqueado si utiliza direcciones IP de listas negras o realiza demasiadas solicitudes desde una sola IP. Para hacer scraping de sitios protegidos, recomendamos utilizar proxies residenciales con rotación automática: proporcionan un alto trust score y un riesgo mínimo de bloqueos, ya que utilizan IP reales de usuarios domésticos en lugar de direcciones de servidores.
Prueba regularmente la calidad de la ocultación a través de servicios especializados (bot.sannysoft.com, pixelscan.net) y adapta la configuración a los cambios en los sistemas anti-bots. El scraping es una carrera armamentista constante entre los desarrolladores de bots y los creadores de protección, por lo que una configuración que funcione hoy puede requerir actualizaciones en unos meses.