Casos de Uso

Manejo de CAPTCHA en Playwright con CaptchaAI

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-recaptcha o .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.

Guías relacionadas

Los comentarios están deshabilitados para este artículo.