Por Qué tu Navegador de Playwright Recibe Desafíos y tu Chrome Real No
Lanzas Playwright contra una página y aparece una pantalla de “Just a moment…”. Se queda girando. A veces muestra una casilla. A veces se recarga y vuelve al mismo desafío. Te cansas de esperar, abres exactamente la misma URL en el Chrome que usas a diario, en el mismo portátil y con la misma Wi-Fi, y la página carga en un segundo. Ni rastro de la casilla.
Misma IP. Misma máquina. Misma URL. Lo único que ha cambiado es el navegador. Esa es la pista: la mayoría de las veces el sistema anti-bot no reacciona a tu red, sino al navegador que lanzó tu herramienta de automatización.
Cómo Se Manifiesta el Problema
Este problema tiene un patrón reconocible:
- Una página de desafío que nunca se resuelve sola en el navegador automatizado, pero que se resuelve de forma pasiva (o ni siquiera aparece) en tu Chrome normal.
- Un desafío con casilla que, cuando tu script hace clic, se convierte en otro desafío o en una página de “access denied” definitiva.
- Un scraper que funciona en modo visible en tu portátil y falla en modo headless en un servidor.
- Una configuración que funcionó durante meses y empezó a fallar tras actualizar el framework, sin cambios de código por tu parte.
Si tu Chrome normal también queda bloqueado desde la misma red, puedes dejar de leer aquí: tu problema es la reputación de la IP o el ritmo de peticiones, no el navegador. El resto del artículo trata del caso en que la diferencia es el navegador.
Tu Navegador de Automatización No Es el Chrome Que Usas
Cuando ejecutas pip install playwright y playwright install, no obtienes el Google Chrome de tu escritorio. Obtienes un navegador pensado para automatización:
- Playwright 1.57 y posteriores funciona sobre builds de Chrome for Testing en lugar de Chromium de código abierto. Las ejecuciones visibles usan Chrome for Testing y las headless usan
chrome-headless-shellpor defecto. - Puppeteer v20 y posteriores descarga Chrome for Testing, y desde la v21.6 también descarga
chrome-headless-shellpara las ejecuciones headless.
Google describe Chrome for Testing como una variante “creada exclusivamente para automatización del navegador y pruebas”. Está fijada a una versión y no se actualiza sola, justo lo que necesita una batería de tests.
chrome-headless-shell es otra historia. Es la implementación headless antigua, que la documentación de Chrome describe como “un envoltorio ligero alrededor del módulo //content de Chromium”, y que históricamente se identifica con HeadlessChrome en su user agent. Es rápida y perfecta para capturas de pantalla. No es el navegador completo.
Tu Chrome de escritorio es el build oficial de marca que usa una enorme parte de los usuarios reales, y se mantiene actualizado de forma automática. Para un sistema anti-bot que puntúa lo normal que parece un visitante, la distancia entre “el navegador que usa todo el mundo” y “un build hecho para automatización” es una señal que merece la pena leer.
Por Qué los Sistemas Anti-Bot Notan la Diferencia
Los desafíos modernos no son puzles. Cloudflare explica que Turnstile ejecuta “una serie de pequeños desafíos JavaScript no interactivos para recopilar señales sobre el visitante o el entorno del navegador”, incluidos el “sondeo de APIs web” y desafíos “para detectar peculiaridades del navegador y comportamiento humano”. Otros proveedores trabajan de forma parecida. Estos son algunos puntos donde los builds de automatización destacan:
Flags de automatización
Según MDN, Chrome pone navigator.webdriver a true cuando se inicia con --enable-automation, --headless o --remote-debugging-port=0. Los frameworks de automatización lanzan Chrome con un conjunto de switches por defecto, y --enable-automation es uno de los clásicos. También es el que provoca la barra “Chrome está siendo controlado por software de prueba automatizado”.
Diferencias de build y de funciones
La propia documentación de Playwright señala que “Chromium no tiene todos los códecs que incluyen Google Chrome o Microsoft Edge”. A un headless shell le faltan partes aún mayores del navegador real. Cualquier comprobación que pregunte “¿qué puede hacer realmente este navegador?” y lo compare con “¿qué dice ser?” puede encontrar huecos como estos.
Desfase de versión
Un build fijado tiene la misma versión todos los días, mientras que la población real de Chrome avanza cada pocas semanas. Meses después, tu scraper declara una versión de navegador que cada vez menos visitantes reales siguen usando.
El canal de control
Los frameworks hablan con Chrome a través del DevTools Protocol, y algunos de los comandos que usan por defecto tienen efectos secundarios que un script de la página puede detectar. Herramientas como Patchright existen precisamente para evitar los más conocidos, como la fuga de Runtime.enable.
El entorno que rodea al navegador
Un perfil nuevo sin historial, un viewport fijo por defecto, las fuentes de un servidor y el renderizado por software dentro de un contenedor sin GPU se van sumando. Ninguno de estos factores es decisivo por sí solo. Juntos dibujan algo que no se parece a una persona con un portátil.
Por qué hacer clic en la casilla no ayuda
Cuando aparece la casilla, el sistema ya ha decidido que tu entorno es dudoso. Un clic programado es solo un dato más, medido dentro del mismo entorno sospechoso. Por eso hacer clic suele escalar a un desafío más duro o a un bloqueo en lugar de dejarte pasar. Si el desafío no se resuelve de forma pasiva, arregla primero el entorno.
Cómo Diagnosticarlo en Local
No adivines. Ejecuta la misma sonda en cada configuración de navegador y compara. Este script lanza el build incluido y tu Chrome instalado, en modo visible y headless, e imprime lo que puede ver un script de la página:
from playwright.sync_api import sync_playwright
PROBE = """() => {
const gl = document.createElement('canvas').getContext('webgl');
const dbg = gl && gl.getExtension('WEBGL_debug_renderer_info');
return {
userAgent: navigator.userAgent,
webdriver: navigator.webdriver,
brands: navigator.userAgentData
? navigator.userAgentData.brands.map(b => `${b.brand} ${b.version}`)
: null,
h264: window.MediaSource
? MediaSource.isTypeSupported('video/mp4; codecs="avc1.42E01E"')
: null,
webglRenderer: dbg ? gl.getParameter(dbg.UNMASKED_RENDERER_WEBGL) : null,
viewport: `${innerWidth}x${innerHeight}`,
};
}"""
def probe(channel, headless):
with sync_playwright() as p:
browser = p.chromium.launch(channel=channel, headless=headless)
page = browser.new_page()
page.goto("https://example.com")
result = page.evaluate(PROBE)
browser.close()
return result
for channel in (None, "chrome"):
for headless in (True, False):
label = f"{channel or 'bundled'} / {'headless' if headless else 'headed'}"
print(label)
for key, value in probe(channel, headless).items():
print(f" {key}: {value}")
Cada línea que difiera entre el build incluido y tu Chrome real es algo que un sitio también puede leer. Después haz la prueba de verdad: carga la página protegida con cada una de las cuatro configuraciones, desde la misma red. La combinación que pase te dice qué capa debes corregir. Si no pasa ninguna, el navegador no es tu cuello de botella.
Cómo Solucionarlo
1. Usa el Chrome de marca
Ambos frameworks pueden controlar el Chrome que ya tienes:
browser = p.chromium.launch(channel="chrome", headless=False)
const browser = await puppeteer.launch({ channel: 'chrome', headless: false });
En una máquina sin Chrome, playwright install chrome instala Google Chrome en la ubicación por defecto del sistema (sustituye una instalación existente, así que hazlo en servidores, no en tu portátil). Como ventaja extra, el Chrome de marca se actualiza solo en los equipos de escritorio, así que dejas de quedarte atrás respecto a la población real.
2. Ejecútalo en modo visible, o al menos con el headless real
El modo visible es la opción más auténtica. En un servidor Linux, ejecútalo dentro de una pantalla virtual con xvfb-run python scraper.py. Si necesitas headless, usa channel="chrome" con headless=True: desde Playwright 1.49, el canal Chrome usa el nuevo modo headless de Chrome, que la documentación llama “el navegador Chrome real”, en lugar del headless shell.
3. Quita los flags de automatización más llamativos
Elimina --enable-automation y desactiva la función de Blink AutomationControlled. Así desaparecen las comprobaciones más baratas. No te vuelve invisible.
4. Mantén un perfil persistente
Un perfil nuevo y vacío en cada ejecución parece un visitante que llega por primera vez, una y otra vez. Un perfil persistente conserva cookies, caché y almacenamiento del sitio entre ejecuciones, igual que tu propio navegador. Combínalo con las ideas de reutilización de cookies de nuestro artículo sobre persistencia de sesión.
5. No finjas lo que no puedes respaldar
Poner un user agent personalizado encima de un build de navegador distinto crea una incoherencia entre lo que el navegador dice y lo que puede hacer. Deja que el Chrome real se presente tal como es.
6. Deja que el desafío se resuelva solo
Espera a que el desafío desaparezca en lugar de hacer clic. Todo junto:
from playwright.sync_api import sync_playwright, TimeoutError as PlaywrightTimeout
URL = "https://example.com/protected-page"
with sync_playwright() as p:
context = p.chromium.launch_persistent_context(
user_data_dir="./chrome-profile",
channel="chrome",
headless=False,
no_viewport=True,
ignore_default_args=["--enable-automation"],
args=["--disable-blink-features=AutomationControlled"],
)
page = context.pages[0] if context.pages else context.new_page()
page.goto(URL, wait_until="domcontentloaded")
try:
# Adapta esta comprobación al desafío que realmente ves.
page.wait_for_function(
"() => !document.title.toLowerCase().includes('just a moment')",
timeout=30_000,
)
html = page.content()
except PlaywrightTimeout:
html = None
print("Challenge did not clear: check the environment, IP and request rate")
context.close()
7. Valora un driver parcheado
Si quieres estas correcciones ya empaquetadas, Patchright es un sustituto directo de Playwright que parchea fugas conocidas como Runtime.enable y los flags de automatización. Su configuración recomendada es la misma que la de arriba: un contexto persistente, channel="chrome", modo visible, sin viewport fijo y sin user agent personalizado. nodriver, el sucesor de undetected-chromedriver, sigue un enfoque parecido y controla tu Chrome instalado sin WebDriver.
El Navegador Es Solo una Capa
Arreglar el navegador elimina un motivo para desafiarte. No elimina los demás. Un Chrome real perfecto que envía miles de peticiones por hora desde una sola IP seguirá quemando la reputación de esa IP, y un cliente HTTP puro sigue teniendo el problema del fingerprint TLS sin importar qué navegador usaste para conseguir las cookies. Para la lista completa sobre una de las protecciones más comunes, consulta cómo hacer scraping de un sitio protegido por Cloudflare sin recibir un 403. Y comprueba siempre el contenido, no el código de estado: una página de desafío puede llegar como una respuesta 200.
Respeta las condiciones del sitio y mantén un ritmo de peticiones razonable. Ser educado también es la forma más barata de no ser bloqueado.
Preguntas Frecuentes
¿Chrome for Testing es lo mismo que Google Chrome?
Se construye dentro del mismo proceso de lanzamiento y es “lo más parecido posible al Chrome normal”, en palabras de Google, pero es una variante aparte pensada solo para automatización y pruebas. No se actualiza sola y no es el build que usan los visitantes corrientes.
¿Poner navigator.webdriver a false hace que Playwright sea indetectable?
No. Elimina una comprobación barata. Las diferencias de build, el canal de control de DevTools, el entorno, tu IP y tu comportamiento siguen siendo visibles. Tómalo como higiene básica, no como solución.
¿Debería mi scraper hacer clic automáticamente en la casilla de Turnstile?
Normalmente no como primer paso. La casilla aparece porque el entorno ya parece dudoso, y un clic programado dentro de ese mismo entorno rara vez cambia el veredicto. Ajusta la configuración del navegador hasta que el desafío se resuelva de forma pasiva.
¿Puedo ejecutar el Chrome de marca en Docker?
Sí. Instala Google Chrome en la imagen (playwright install chrome puede hacerlo por ti) y ejecútalo en modo visible bajo Xvfb, o en headless con channel="chrome" para obtener el nuevo modo headless en lugar del headless shell.
Olvídate del Mantenimiento del Navegador
Mantener los builds del navegador al día, los perfiles calientes y los flags limpios es un trabajo continuo, y se rompe cada vez que se actualiza un framework o un sistema anti-bot. Si prefieres dedicar ese tiempo a los datos, ScrapeUnblocker renderiza la página en un navegador real por ti y devuelve el HTML con una sola petición a la API. La documentación muestra cómo hacer tu primera llamada en pocos minutos.
Prueba ScrapeUnblocker gratis
Tasa de éxito del 95%+ · desde 0,55 € por cada 1000 llamadas · 500 solicitudes gratis al registrarte.