Cuando automatizas Chrome con Selenium y el sitio te responde con un reCAPTCHA o un Cloudflare Turnstile, en realidad tienes dos problemas distintos: reducir las señales que delatan la automatización y resolver el CAPTCHA que aparece de todos modos. undetected-chromedriver se ocupa de lo primero; CaptchaAI, de lo segundo.
undetected-chromedriver (UC) es una biblioteca de Python que ajusta el ChromeDriver de Selenium: elimina algunas marcas de automatización y mantiene la compatibilidad de versiones entre Chrome y su controlador. Con eso aparecen menos CAPTCHA, pero no desaparecen; cuando el sitio insiste, CaptchaAI lo resuelve por API y devuelve un token que inyectas en la página. Esta guía encadena ambas piezas en un flujo real de Selenium: montar el navegador, extraer el sitekey, resolver con CaptchaAI e inyectar el token.
Por qué necesitas las dos herramientas
Un navegador con menos señales de automatización no deja de ver CAPTCHA. La detección combina reputación de la IP, comportamiento del ratón, cabeceras HTTP e historial de sesión, así que un reCAPTCHA v2 o un Turnstile puede activarse con una configuración limpia. Por eso conviene dividir responsabilidades:
- UC gestiona el navegador: mantiene ChromeDriver al día y ajusta las señales que revisan los sitios.
- CaptchaAI resuelve el desafío: recibe el sitekey y la URL, resuelve el CAPTCHA y devuelve el token listo para inyectar.
Separar ambas capas da una tasa de éxito alta y estable: cada herramienta hace solo lo que sabe hacer.
Lo que vas a necesitar
| Requisito | Detalles |
|---|---|
| Clave API CaptchaAI | captchaai.com |
| Python 3.8+ | |
| Navegador Chrome | Instalado en tu sistema |
Instala las dependencias:
pip install undetected-chromedriver requests
Configuración básica del navegador
Arranca una instancia de Chrome con UC. Los argumentos --no-sandbox y --window-size cubren la mayoría de los entornos de servidor y contenedores:
import undetected_chromedriver as uc
import requests
import time
def create_browser():
"""Create a Chrome browser instance."""
options = uc.ChromeOptions()
options.add_argument("--no-sandbox")
options.add_argument("--window-size=1920,1080")
driver = uc.Chrome(options=options)
return driver
Dos apuntes sobre las opciones:
--no-sandboxevita fallos de permisos en contenedores y servidores.--window-size=1920,1080fija una ventana de escritorio típica.
Resolver reCAPTCHA v2 paso a paso
El flujo tiene tres pasos y conviene respetarlos en orden:
- Localiza el sitekey en la página.
- Pide el token a CaptchaAI y espera la resolución.
- Inyecta el token y envía el formulario.
Paso 1: localiza el sitekey en la página
El sitekey suele estar en el atributo data-sitekey; si no, se extrae del src del iframe de reCAPTCHA:
API_KEY = "YOUR_API_KEY"
def extract_recaptcha_sitekey(driver):
"""Extract reCAPTCHA v2 sitekey from the page."""
try:
element = driver.find_element("css selector", "[data-sitekey]")
return element.get_attribute("data-sitekey")
except Exception:
# Try finding in iframe src
iframes = driver.find_elements("css selector", "iframe[src*='recaptcha']")
for iframe in iframes:
src = iframe.get_attribute("src")
if "k=" in src:
return src.split("k=")[1].split("&")[0]
return None
Paso 2: resuelve el CAPTCHA con CaptchaAI
Envía la tarea al endpoint in.php con el método userrecaptcha y consulta el resultado en res.php hasta que el token esté listo. El primer time.sleep(20) da margen antes de sondear:
def solve_recaptcha_v2(sitekey, pageurl):
"""Submit reCAPTCHA v2 to CaptchaAI and return the token."""
submit = requests.post("https://ocr.captchaai.com/in.php", data={
"key": API_KEY,
"method": "userrecaptcha",
"googlekey": sitekey,
"pageurl": pageurl,
"json": 1
}).json()
if submit.get("status") != 1:
raise RuntimeError(f"Submit error: {submit.get('request')}")
task_id = submit["request"]
time.sleep(20)
for _ in range(30):
result = requests.get("https://ocr.captchaai.com/res.php", params={
"key": API_KEY, "action": "get", "id": task_id, "json": 1
}).json()
if result.get("status") == 1:
return result["request"]
if result.get("request") != "CAPCHA_NOT_READY":
raise RuntimeError(f"Solve error: {result['request']}")
time.sleep(5)
raise TimeoutError("Solve timed out")
Paso 3: inyecta el token y envía el formulario
Con el token en la mano, escríbelo en el campo g-recaptcha-response y, si la página usa una función de callback, dispárala para que registre la resolución:
def inject_recaptcha_token(driver, token):
"""Inject the solved token into the page and submit."""
driver.execute_script(f'''
document.getElementById("g-recaptcha-response").innerHTML = "{token}";
document.getElementById("g-recaptcha-response").style.display = "block";
''')
# If there's a callback function, trigger it
driver.execute_script(f'''
if (typeof ___grecaptcha_cfg !== 'undefined') {{
var clients = ___grecaptcha_cfg.clients;
for (var key in clients) {{
var client = clients[key];
if (client && client.callback) {{
client.callback("{token}");
}}
}}
}}
''')
Flujo completo: iniciar sesión con reCAPTCHA
Este ejemplo une todo: abre un login de pruebas, rellena las credenciales, extrae el sitekey, resuelve con CaptchaAI, inyecta el token y envía el formulario. El try/finally cierra el navegador aunque algo falle:
import undetected_chromedriver as uc
import requests
import time
API_KEY = "YOUR_API_KEY"
def solve_recaptcha(sitekey, pageurl):
submit = requests.post("https://ocr.captchaai.com/in.php", data={
"key": API_KEY, "method": "userrecaptcha",
"googlekey": sitekey, "pageurl": pageurl, "json": 1
}).json()
if submit.get("status") != 1:
raise RuntimeError(f"Submit error: {submit.get('request')}")
task_id = submit["request"]
time.sleep(20)
for _ in range(30):
result = requests.get("https://ocr.captchaai.com/res.php", params={
"key": API_KEY, "action": "get", "id": task_id, "json": 1
}).json()
if result.get("status") == 1:
return result["request"]
if result.get("request") != "CAPCHA_NOT_READY":
raise RuntimeError(f"Solve error: {result['request']}")
time.sleep(5)
raise TimeoutError("Solve timed out")
def main():
driver = uc.Chrome()
try:
# Navigate to target page
driver.get("https://staging.example.com/qa-login")
time.sleep(3)
# Fill in form fields
driver.find_element("id", "username").send_keys("user")
driver.find_element("id", "password").send_keys("pass")
# Extract sitekey
element = driver.find_element("css selector", "[data-sitekey]")
sitekey = element.get_attribute("data-sitekey")
pageurl = driver.current_url
print(f"Sitekey: {sitekey}")
# Solve CAPTCHA
token = solve_recaptcha(sitekey, pageurl)
print(f"Token: {token[:50]}...")
# Inject token
driver.execute_script(
f'document.getElementById("g-recaptcha-response").innerHTML = "{token}";'
)
# Submit form
driver.find_element("id", "submit-btn").click()
time.sleep(3)
print(f"Current URL: {driver.current_url}")
finally:
driver.quit()
if __name__ == "__main__":
main()
Trabaja siempre contra un entorno propio (staging.example.com/qa-login en el ejemplo) y respeta los términos de servicio y la normativa de datos aplicable (GDPR, LOPDGDD, LFPDPPP).
Resolver Cloudflare Turnstile
El patrón es idéntico al de reCAPTCHA; solo cambian dos detalles:
- El método pasa de
userrecaptchaaturnstile. - El campo del token pasa de
g-recaptcha-responseacf-turnstile-response.
Extrae el sitekey y resuelve así:
def solve_turnstile(sitekey, pageurl):
submit = requests.post("https://ocr.captchaai.com/in.php", data={
"key": API_KEY, "method": "turnstile",
"sitekey": sitekey, "pageurl": pageurl, "json": 1
}).json()
if submit.get("status") != 1:
raise RuntimeError(f"Submit error: {submit.get('request')}")
task_id = submit["request"]
time.sleep(10)
for _ in range(30):
result = requests.get("https://ocr.captchaai.com/res.php", params={
"key": API_KEY, "action": "get", "id": task_id, "json": 1
}).json()
if result.get("status") == 1:
return result["request"]
if result.get("request") != "CAPCHA_NOT_READY":
raise RuntimeError(f"Solve error: {result['request']}")
time.sleep(5)
raise TimeoutError("Solve timed out")
# Inject Turnstile token
def inject_turnstile_token(driver, token):
driver.execute_script(f'''
var input = document.querySelector('[name="cf-turnstile-response"]');
if (input) input.value = "{token}";
''')
Un caso real: QA de un portal con cita previa
Un escenario habitual en el mercado hispanohablante son los portales públicos protegidos con CAPTCHA: los centros de visado BLS que atienden a solicitantes de toda Latinoamérica, las páginas de cita previa en España o los trámites del SAT en México. Si tu equipo mantiene un panel que verifica periódicamente que uno de esos formularios sigue funcionando —siempre sobre flujos autorizados—, UC mantiene la sesión estable y CaptchaAI resuelve el reCAPTCHA del envío. El mismo patrón sirve para monitorizar precios en marketplaces como MercadoLibre o Amazon.es, como tarea de QA, no de compra automatizada.
Qué plan de CaptchaAI elegir
CaptchaAI se factura por thread concurrente, no por CAPTCHA resuelto: un thread es un desafío en curso y, en cuanto termina, queda libre para el siguiente. Todos los planes incluyen resoluciones ilimitadas dentro del mes.
- BASIC — $15/mes, 5 threads. Suficiente para un scraper o una suite de pruebas de un proceso.
- STANDARD — $30/mes, 15 threads. Buen punto de partida con varios workers en paralelo.
- ADVANCE — $90/mes, 50 threads, y por encima PREMIUM ($170/mes, 100 threads) para volúmenes altos.
Para agencias que facturan en monedas locales volátiles, un costo mensual predecible en USD es más fácil de presupuestar que el pago por resolución.
Solución de problemas frecuentes
| Problema | Causa | Solución |
|---|---|---|
| La versión de Chrome no coincide | UC no encuentra el controlador correspondiente | Actualiza Chrome o especifica version_main en uc.Chrome() |
| El CAPTCHA sigue apareciendo | El sitio activa el desafío pese a UC | Es lo esperado: deja que CaptchaAI lo resuelva automáticamente |
| Falla la inyección del token | ID de elemento incorrecto o falta el callback | Revisa la implementación de reCAPTCHA de la página |
WebDriverException |
Chrome se cerró de forma inesperada | Añade --no-sandbox y --disable-dev-shm-usage |
Preguntas frecuentes
¿Necesito undetected-chromedriver si ya uso CaptchaAI?
No es obligatorio, pero ayuda. CaptchaAI resuelve el CAPTCHA venga de donde venga, así que puedes usarlo con Selenium estándar. UC reduce la cantidad de desafíos que se disparan, con lo que gastas menos resoluciones.
¿Cada cuánto cambia el sitekey de una página?
El sitekey es estable: pertenece al sitio y no rota por sesión. Conviene extraerlo en tiempo de ejecución en lugar de fijarlo en el código, así tu script sigue funcionando si el sitio lo actualiza.
¿Cuánto tarda CaptchaAI en devolver el token?
Normalmente unos pocos segundos. Por eso el código espera un margen inicial (time.sleep) y luego sondea res.php cada cinco segundos hasta que el token esté listo o se agote el tiempo.
¿Puedo resolver varios CAPTCHA a la vez?
Sí. Como la facturación es por thread concurrente, el número de resoluciones simultáneas lo marca tu plan: 5 threads en BASIC, 15 en STANDARD, y así sucesivamente. Lanza tus workers en paralelo hasta ese límite.
¿Es legal usar esto?
Depende del uso. Resolver CAPTCHA para automatizar o hacer QA de flujos que controlas o tienes autorización para usar es legítimo; hacerlo sobre servicios de terceros sin permiso puede violar sus términos. Revisa siempre las condiciones del sitio y la normativa aplicable.
Empieza a resolver CAPTCHA en tu automatización
Obtén tu clave API en captchaai.com y combina la automatización del navegador con una resolución de CAPTCHA fiable. Con UC delante y CaptchaAI detrás, tu pipeline de Selenium deja de detenerse cuando aparece un reCAPTCHA o un Turnstile.