Casos de Uso

Envío de formularios automatizado con manejo de CAPTCHA

Para automatizar un formulario protegido por CAPTCHA solo hay que ordenar bien tres pasos: rellenar los campos, resolver el desafío y escribir el token en el campo oculto correcto antes de pulsar enviar. Con Selenium y la API de CaptchaAI puedes montar ese flujo una sola vez y reutilizarlo en formularios de contacto, registro o inicio de sesión sin tocar el navegador a mano. En esta guía construimos ese automatizador pieza a pieza, con código en Python que detecta el tipo de CAPTCHA sobre la marcha.


Por qué los formularios con CAPTCHA frenan la automatización

Un formulario con CAPTCHA no acepta el envío hasta que demuestres que hay una persona detrás. Ese es justo el punto donde se rompe un script de QA: el navegador rellena los campos, pulsa enviar y el servidor responde con un error de validación porque falta el token del CAPTCHA.

El caso típico en equipos de habla hispana es el de una agencia que necesita validar decenas de veces al día el formulario de contacto de un cliente antes de cada despliegue, o el de un portal de trámites (tipo cita previa) que hay que monitorizar de forma autorizada. En todos ellos el patrón es el mismo: sin resolver el CAPTCHA, no hay envío que valga. La solución es delegar esa parte en un servicio de resolución y quedarte con un flujo predecible en Python.


Arquitectura del envío de formularios automatizado

El flujo es lineal: Selenium carga la página, rellena los campos, detecta y resuelve el CAPTCHA, y solo entonces envía. Resolver el desafío en el último momento evita que el token caduque antes de llegar al servidor.

┌────────────┐     ┌──────────────┐     ┌────────────┐     ┌──────────────┐
│ Load Form  │────▶│ Fill Fields  │────▶│ Detect &   │────▶│ Submit Form  │
│ (Selenium) │     │              │     │ Solve      │     │              │
│            │     │              │     │ CAPTCHA    │     │              │
└────────────┘     └──────────────┘     └────────────┘     └──────────────┘

Las tres piezas para automatizar formularios con CAPTCHA

El automatizador se apoya en tres clases con responsabilidades separadas: una habla con la API, otra reconoce el CAPTCHA en la página y la tercera orquesta el envío. Definirlas en este orden importa, porque cada una depende de la anterior.

El solver que habla con la API de CaptchaAI

Esta clase envía la tarea al endpoint in.php, espera y sondea el resultado en res.php hasta obtener el token. El sondeo respeta el estado CAPCHA_NOT_READY y reintenta cada pocos segundos en lugar de golpear la API sin pausa.

import time
import requests


class FormCaptchaSolver:
    BASE = "https://ocr.captchaai.com"

    def __init__(self, api_key):
        self.api_key = api_key

    def solve(self, params, initial_wait=10):
        params["key"] = self.api_key
        params["json"] = 1
        resp = requests.post(f"{self.BASE}/in.php", data=params).json()
        if resp["status"] != 1:
            raise Exception(f"Submit error: {resp['request']}")

        task_id = resp["request"]
        time.sleep(initial_wait)

        for _ in range(60):
            result = requests.get(
                f"{self.BASE}/res.php",
                params={"key": self.api_key, "action": "get", "id": task_id, "json": 1},
            ).json()
            if result["request"] == "CAPCHA_NOT_READY":
                time.sleep(5)
                continue
            if result["status"] == 1:
                return result["request"]
            raise Exception(f"Solve error: {result['request']}")
        raise TimeoutError("CAPTCHA solve timed out")

El detector que identifica el tipo de CAPTCHA

Antes de resolver nada hay que saber qué se está resolviendo. El detector inspecciona el DOM y devuelve el tipo junto con el dato que necesita el solver: el sitekey para Turnstile y reCAPTCHA, o la URL de la imagen para un CAPTCHA de texto. El orden de comprobación cuenta: Turnstile primero, luego reCAPTCHA v2 y por último la imagen.

import re
from selenium.webdriver.common.by import By


class CaptchaDetector:
    def __init__(self, driver):
        self.driver = driver

    def detect(self):
        """Detect CAPTCHA type on current page."""
        html = self.driver.page_source

        # Turnstile
        turnstile = self.driver.find_elements(By.CSS_SELECTOR, ".cf-turnstile, [data-sitekey]")
        for el in turnstile:
            if "cf-turnstile" in (el.get_attribute("class") or ""):
                return "turnstile", el.get_attribute("data-sitekey")

        # reCAPTCHA
        recaptcha = self.driver.find_elements(By.CSS_SELECTOR, "[data-sitekey]")
        if recaptcha:
            sitekey = recaptcha[0].get_attribute("data-sitekey")
            if "recaptcha" in html.lower():
                return "recaptcha_v2", sitekey

        # Image CAPTCHA
        img = self.driver.find_elements(By.CSS_SELECTOR, "img[src*='captcha'], img.captcha")
        if img:
            return "image", img[0].get_attribute("src")

        return "none", None

El automatizador que rellena y envía

La tercera clase une las dos anteriores. Rellena cada campo con esperas explícitas, llama a solve_captcha() y escribe el token en el campo oculto correspondiente: g-recaptcha-response para reCAPTCHA, cf-turnstile-response para Turnstile. Fíjate en que cada tipo usa su propio nombre de campo; confundirlos es una de las causas más habituales de tokens rechazados.

import base64
import requests as req
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC


class FormAutomator:
    def __init__(self, api_key):
        self.solver = FormCaptchaSolver(api_key)
        self.driver = webdriver.Chrome()
        self.detector = CaptchaDetector(self.driver)

    def fill_field(self, selector, value):
        field = WebDriverWait(self.driver, 10).until(
            EC.presence_of_element_located((By.CSS_SELECTOR, selector))
        )
        field.clear()
        field.send_keys(value)

    def select_option(self, selector, value):
        from selenium.webdriver.support.ui import Select
        select = Select(self.driver.find_element(By.CSS_SELECTOR, selector))
        select.select_by_value(value)

    def solve_captcha(self):
        captcha_type, data = self.detector.detect()
        page_url = self.driver.current_url

        if captcha_type == "recaptcha_v2":
            token = self.solver.solve({
                "method": "userrecaptcha",
                "googlekey": data,
                "pageurl": page_url,
            })
            self.driver.execute_script(
                f'document.querySelector("[name=g-recaptcha-response]").value = "{token}";'
            )
            return True

        if captcha_type == "turnstile":
            token = self.solver.solve({
                "method": "turnstile",
                "sitekey": data,
                "pageurl": page_url,
            })
            self.driver.execute_script(
                f'document.querySelector("[name=cf-turnstile-response]").value = "{token}";'
            )
            return True

        if captcha_type == "image":
            img_data = req.get(data).content
            img_b64 = base64.b64encode(img_data).decode()
            text = self.solver.solve({"method": "base64", "body": img_b64})
            captcha_input = self.driver.find_element(
                By.CSS_SELECTOR, "input[name*='captcha']"
            )
            captcha_input.clear()
            captcha_input.send_keys(text)
            return True

        return False  # No CAPTCHA detected

    def submit_form(self, url, fields, submit_selector="button[type='submit']"):
        """
        fields: list of (selector, value) tuples
        """
        self.driver.get(url)

        for selector, value in fields:
            self.fill_field(selector, value)

        self.solve_captcha()

        submit = self.driver.find_element(By.CSS_SELECTOR, submit_selector)
        submit.click()

        return self.driver.current_url

    def close(self):
        self.driver.quit()

Ejemplo: QA automatizado de un formulario de contacto

Con las tres piezas montadas, automatizar un formulario de contacto se reduce a una llamada. Este es el escenario más común para un equipo de QA: cargar la página, rellenar nombre, correo y mensaje, dejar que el automatizador resuelva el CAPTCHA y comprobar la URL de redirección para saber si el envío se aceptó.

automator = FormAutomator("YOUR_API_KEY")

try:
    result_url = automator.submit_form(
        url="https://example.com/contact",
        fields=[
            ("#name", "John Doe"),
            ("#email", "john@example.com"),
            ("#subject", "Sales inquiry"),
            ("#message", "I'd like to learn more about your services."),
        ],
        submit_selector="#submit-btn",
    )
    print(f"Form submitted. Redirected to: {result_url}")
finally:
    automator.close()

Reutilizar el flujo en otros formularios con CAPTCHA

La ventaja de encapsular la lógica en FormAutomator es que el mismo objeto sirve para cualquier formulario: solo cambian la URL, la lista de campos y el selector del botón. Usa siempre entornos de staging propios o autorizados, y respeta los términos de servicio de cada sitio.

Formulario de inicio de sesión

result = automator.submit_form(
    url="https://staging.example.com/qa-login",
    fields=[
        ("#username", "testuser"),
        ("#password", "testpass123"),
    ],
    submit_selector="#login-btn",
)

Formulario de registro

result = automator.submit_form(
    url="https://example.com/register",
    fields=[
        ("#first-name", "Jane"),
        ("#last-name", "Smith"),
        ("#email", "jane@example.com"),
        ("#password", "SecurePass!123"),
        ("#confirm-password", "SecurePass!123"),
    ],
    submit_selector="#register-btn",
)

Formulario de búsqueda con CAPTCHA

result = automator.submit_form(
    url="https://example.com/search",
    fields=[
        ("#query", "python developer"),
        ("#location", "San Francisco"),
    ],
    submit_selector="#search-btn",
)

Diagnóstico cuando el envío falla

Cuando un envío no pasa, casi siempre es por una de estas causas. Empieza por el token: resolverlo demasiado pronto es el fallo más frecuente, porque expira antes de llegar al servidor.

Problema Causa Solución
Token rechazado El token expiró antes del envío Resuelve el CAPTCHA al final y envía de inmediato
Campo no encontrado Carga de página dinámica Añade esperas explícitas
Se detectó un tipo de CAPTCHA incorrecto Varios elementos CAPTCHA en la página Revisa el orden de detección
El formulario se recarga tras enviarlo Falló la validación del lado del servidor Verifica todos los campos obligatorios
El callback de reCAPTCHA no se dispara Hay que invocar la función de callback Usa grecaptcha.execute() tras la inyección

Preguntas frecuentes

¿Qué plan de CaptchaAI necesito para automatizar formularios?

Depende de tu concurrencia, no del número de envíos. CaptchaAI factura por thread (CAPTCHA en curso), con resoluciones ilimitadas dentro del plan. Para un flujo de QA que valida formularios de uno en uno, el plan BASIC ($15/mes, 5 threads) suele bastar; si lanzas envíos en paralelo desde varios workers, sube a STANDARD ($30/mes, 15 threads) o superior. El coste mensual fijo en USD es predecible frente al pago por resolución.

¿Puedo resolver el CAPTCHA sin abrir un navegador?

Sí, para reCAPTCHA v2 y Turnstile puedes resolver el CAPTCHA por HTTP y enviar el formulario con un POST directo, sin Selenium. Ahora bien, si el formulario depende de validación en JavaScript o de un callback, necesitas un navegador real para que la página acepte el token.

¿Funciona con reCAPTCHA v3 o con hCaptcha?

CaptchaAI resuelve reCAPTCHA v2 y v3, Cloudflare Turnstile y CAPTCHA de imagen, así que puedes ampliar el detector para cubrir v3. hCaptcha no es compatible por ahora, de modo que no lo incluyas como tipo objetivo en tu automatización.

¿Por qué me rechazan el token justo al enviar?

Lo más habitual es que lo estés resolviendo demasiado pronto o escribiéndolo en el campo equivocado. Resuelve el CAPTCHA como último paso antes de pulsar enviar, y confirma que el token va a g-recaptcha-response para reCAPTCHA o a cf-turnstile-response para Turnstile.


Guías relacionadas

  • Guía de inicio rápido de CaptchaAI
  • Formatos de respuesta de la API

Automatiza cualquier formulario. Resuelve el CAPTCHA con CaptchaAI.

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