Playwright por sí solo no resuelve un CAPTCHA: espera, hace clic y rellena formularios, pero un reCAPTCHA o un Cloudflare Turnstile lo frenan igual que a cualquier navegador. El patrón que funciona es dividir el trabajo: Playwright controla la interacción en Chromium, Firefox o WebKit, y la API de CaptchaAI resuelve el desafío en el servidor. En cada página el flujo es siempre el mismo:
- Detectar el widget y leer su
data-sitekey. - Enviar ese sitekey a la API de CaptchaAI.
- Sondear hasta recibir el token resuelto.
- Inyectar el token en el formulario y enviarlo.
En esta guía verás ese flujo completo con código en Python y Node.js, más el manejo de Turnstile.
Lo que necesitas antes de empezar
Solo hacen falta la librería de Playwright, un cliente HTTP para hablar con la API y tu clave de CaptchaAI. Ninguna configuración especial del navegador: el modo headless estándar es suficiente porque la resolución ocurre fuera del navegador.
| Requisito | Detalles |
|---|---|
| Python | pip install playwright requests luego playwright install |
| Node.js | npm install playwright axios |
| Clave API de CaptchaAI | De captchaai.com |
¿Playwright, Selenium o Puppeteer?
La elección del framework no cambia cómo resuelves el CAPTCHA: en los tres extraes el sitekey, resuelves por API e inyectas el token. Lo que cambia es la ergonomía del navegador. Playwright destaca por su espera automática integrada y su soporte de tres motores desde una sola API.
| Característica | Playwright | Selenium | Puppeteer |
|---|---|---|---|
| Lenguajes | Python, Node.js, C#, Java | Python, Java, C#, Ruby, JS | Node.js |
| Navegadores | Chromium, Firefox, WebKit | Chrome, Firefox, Edge, Safari | Chromium |
| Espera automática | ✅ Integrada | ❌ Esperas manuales | ⚠️ Parcial |
| Intercepción de red | ✅ | ⚠️ Limitada | ✅ |
| Integración con CaptchaAI | ✅ Misma API | ✅ Misma API | ✅ Misma API |
CaptchaAI se comporta igual con los tres. El resto de la guía usa Playwright, pero el patrón se traslada tal cual a los otros dos.
Python: resolver reCAPTCHA con Playwright y CaptchaAI
Configura el cliente de resolución
La función siguiente encapsula el ciclo completo contra la API: envía la tarea al endpoint in.php, luego sondea res.php cada 5 segundos hasta que el token está listo. Mantén esta lógica separada de Playwright para poder reutilizarla en cualquier script.
from playwright.sync_api import sync_playwright
import requests
import time
API_KEY = "YOUR_API_KEY"
def solve_recaptcha(site_key, page_url):
resp = requests.get("https://ocr.captchaai.com/in.php", params={
"key": API_KEY,
"method": "userrecaptcha",
"googlekey": site_key,
"pageurl": page_url
})
if not resp.text.startswith("OK|"):
raise Exception(resp.text)
task_id = resp.text.split("|")[1]
for _ in range(60):
time.sleep(5)
result = requests.get("https://ocr.captchaai.com/res.php", params={
"key": API_KEY, "action": "get", "id": task_id
})
if result.text == "CAPCHA_NOT_READY": continue
if result.text.startswith("OK|"): return result.text.split("|")[1]
raise Exception(result.text)
raise TimeoutError()
Ejemplo completo: login con reCAPTCHA
Aquí Playwright rellena el formulario, detecta si hay un .g-recaptcha en la página y, cuando lo hay, extrae el data-sitekey, delega la resolución a la función anterior e inyecta el token en el textarea oculto g-recaptcha-response. Fíjate en que la URL apunta a un entorno de staging propio: prueba siempre contra tus propios flujos autorizados.
def login_with_captcha(url, username, password):
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
context = browser.new_context(
user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
)
page = context.new_page()
page.goto(url)
# Fill login form
page.fill("#username", username)
page.fill("#password", password)
# Check for reCAPTCHA
recaptcha = page.query_selector(".g-recaptcha")
if recaptcha:
site_key = recaptcha.get_attribute("data-sitekey")
print(f"Solving reCAPTCHA: {site_key}")
token = solve_recaptcha(site_key, page.url)
# Inject token
page.evaluate(f"""
document.getElementById('g-recaptcha-response').innerHTML = '{token}';
document.getElementById('g-recaptcha-response').style.display = '';
""")
# Submit
page.click('button[type="submit"]')
page.wait_for_load_state("networkidle")
print(f"Current URL: {page.url}")
content = page.content()
browser.close()
return content
result = login_with_captcha(
"https://staging.example.com/qa-login",
"user@example.com",
"password123"
)
Versión asíncrona con aiohttp
Si automatizas muchas páginas en paralelo, la variante async de Playwright con aiohttp evita bloquear el bucle de eventos durante el sondeo. La secuencia es idéntica; solo cambia el modelo de concurrencia.
from playwright.async_api import async_playwright
import aiohttp
import asyncio
async def solve_recaptcha_async(site_key, page_url):
async with aiohttp.ClientSession() as session:
params = {
"key": API_KEY, "method": "userrecaptcha",
"googlekey": site_key, "pageurl": page_url
}
async with session.get("https://ocr.captchaai.com/in.php", params=params) as resp:
text = await resp.text()
task_id = text.split("|")[1]
for _ in range(60):
await asyncio.sleep(5)
params = {"key": API_KEY, "action": "get", "id": task_id}
async with session.get("https://ocr.captchaai.com/res.php", params=params) as resp:
text = await resp.text()
if text == "CAPCHA_NOT_READY": continue
if text.startswith("OK|"): return text.split("|")[1]
raise Exception(text)
raise TimeoutError()
async def main():
async with async_playwright() as p:
browser = await p.chromium.launch(headless=True)
page = await browser.new_page()
await page.goto("https://example.com/form")
site_key = await page.get_attribute(".g-recaptcha", "data-sitekey")
token = await solve_recaptcha_async(site_key, page.url)
await page.evaluate(f"document.getElementById('g-recaptcha-response').innerHTML = '{token}'")
await page.click('button[type="submit"]')
await browser.close()
asyncio.run(main())
Node.js: Playwright + CaptchaAI
El mismo patrón traducido a JavaScript. solveRecaptcha envía la tarea con Axios y sondea hasta recibir el token; el bloque principal lanza Chromium, rellena el formulario e inyecta la respuesta. Si trabajas en el ecosistema Node.js, esta es la ruta más directa.
const { chromium } = require("playwright");
const axios = require("axios");
const API_KEY = "YOUR_API_KEY";
async function solveRecaptcha(siteKey, pageUrl) {
const submit = await axios.get("https://ocr.captchaai.com/in.php", {
params: {
key: API_KEY,
method: "userrecaptcha",
googlekey: siteKey,
pageurl: pageUrl,
},
});
const taskId = submit.data.split("|")[1];
while (true) {
await new Promise((r) => setTimeout(r, 5000));
const result = await axios.get("https://ocr.captchaai.com/res.php", {
params: { key: API_KEY, action: "get", id: taskId },
});
if (result.data === "CAPCHA_NOT_READY") continue;
if (result.data.startsWith("OK|")) return result.data.split("|")[1];
throw new Error(result.data);
}
}
(async () => {
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage();
await page.goto("https://staging.example.com/qa-login");
// Fill form
await page.fill("#username", "user@example.com");
await page.fill("#password", "password123");
// Solve CAPTCHA
const siteKey = await page.getAttribute(".g-recaptcha", "data-sitekey");
if (siteKey) {
const token = await solveRecaptcha(siteKey, page.url());
await page.evaluate(
(t) => (document.getElementById("g-recaptcha-response").innerHTML = t),
token
);
}
// Submit
await page.click('button[type="submit"]');
await page.waitForLoadState("networkidle");
console.log("Logged in:", page.url());
await browser.close();
})();
Resolver Cloudflare Turnstile en Playwright
Turnstile funciona con el mismo esquema, cambiando el método a turnstile y detectando el contenedor .cf-turnstile en lugar de .g-recaptcha. El token resultante se inyecta en el campo cf-turnstile-response antes de enviar el formulario.
# Detect Turnstile
turnstile = page.query_selector(".cf-turnstile")
if turnstile:
site_key = turnstile.get_attribute("data-sitekey")
resp = requests.get("https://ocr.captchaai.com/in.php", params={
"key": API_KEY, "method": "turnstile",
"sitekey": site_key, "pageurl": page.url
})
task_id = resp.text.split("|")[1]
# Poll and inject...
Cuánto cuesta automatizar esto
CaptchaAI cobra por thread concurrente, no por resolución, así que el costo es predecible en USD mes a mes: útil si facturas en una moneda local volátil y necesitas cerrar un presupuesto de agencia. Un thread es un CAPTCHA en curso; en cuanto termina, queda libre para el siguiente. Todos los planes incluyen resoluciones ilimitadas por thread, así que solo eliges según cuántas resoluciones simultáneas necesites:
- Un script de QA que valida un login cada pocos minutos: el plan BASIC ($15/mes, 5 threads) sobra.
- Scraping o pruebas en paralelo sobre un marketplace regional: ADVANCE ($90/mes, 50 threads).
- Automatización intensiva de portales de trámites o formularios de alto volumen: PREMIUM ($170/mes, 100 threads).
Recuerda respetar los términos de servicio del sitio y la normativa de protección de datos aplicable en tu operación.
Solución de problemas
Cuando el flujo no completa, casi siempre es una de estas causas: el widget aún no ha cargado, el id del campo de respuesta no es el esperado o falta un callback. Antes de depurar a fondo, revisa en este orden:
- Que el selector (
.g-recaptchao.cf-turnstile) exista realmente en el DOM. - Que el token llegue completo desde la API antes de inyectarlo.
- Que el envío del formulario no dependa de un evento adicional.
| Problema | Solución |
|---|---|
page.query_selector devuelve null |
El CAPTCHA se carga de forma dinámica; usa page.wait_for_selector() |
| La inyección del token no surte efecto | Comprueba si el textarea de respuesta tiene otro id |
| Playwright falla dentro de Docker | Instala las dependencias del navegador: playwright install-deps |
| El CAPTCHA reaparece tras resolverlo | El sitio puede exigir ejecutar un callback; dispáralo con page.evaluate() |
Preguntas frecuentes
¿Tengo que desactivar el modo headless para que funcione?
No. Como la resolución ocurre en el servidor de CaptchaAI y no en el navegador, headless=True funciona sin problemas. Playwright solo lee el sitekey e inyecta el token.
¿Cómo inyecto el token una vez resuelto el CAPTCHA?
Escribes el valor en el campo de respuesta con page.evaluate(): g-recaptcha-response para reCAPTCHA y cf-turnstile-response para Turnstile. Después envías el formulario con normalidad.
¿Qué tipos de CAPTCHA cubre este flujo con CaptchaAI?
Los que CaptchaAI resuelve por API: reCAPTCHA v2 y v3, Cloudflare Turnstile y Challenge, GeeTest v3, e imágenes/OCR. hCaptcha y FunCaptcha no son compatibles hoy, así que no dependas de ellos en tus scripts.
¿Playwright funciona igual con Chromium, Firefox y WebKit?
Sí. El código de detección e inyección es idéntico en los tres motores; solo cambias el navegador en p.chromium, p.firefox o p.webkit. La integración con CaptchaAI no varía.