Scarnox · Blog

Solver de captcha con Selenium en Python: ejemplo práctico (2026)

Selenium controla un navegador real, así que la página se renderiza y sus scripts se ejecutan como lo harían para una persona. El único paso que no puede dar es resolver el widget de PopularCaptcha. Esta guía añade ese paso: leer la sitekey de la página renderizada, obtener un token con una sola llamada a la API, escribir el token en el formulario y enviarlo.

Cubre el widget de PopularCaptcha, que Scarnox resuelve hoy sin proxy. Empieza con crédito de prueba gratis, sin tarjeta y prueba el script en un formulario de prueba primero.

Lo que Selenium tiene que hacer

El flujo tiene cuatro partes:

  1. Cargar la página y esperar a que el widget se renderice.
  2. Leer el atributo data-sitekey del widget.
  3. Obtener un token de la API y ponerlo en el formulario.
  4. Enviar el formulario antes de que caduque el token.

La tercera parte es la única que toca el captcha. Todo lo demás es trabajo normal de Selenium.

Instala los paquetes

pip install selenium requests

Selenium 4.6 y posteriores encuentran por sí mismos un ChromeDriver compatible, así que no necesitas descargar el driver a mano.

El script completo

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

API_KEY = "scarnox_YOURAPIKEY"
API_URL = "https://api.scarnox.com/api/tasks/create"
PAGE_URL = "https://example.com/signup"


def solve(sitekey: str, page_url: str, invisible: bool = False) -> str:
    body = {
        "captcha_type": "PopularCaptchaTaskProxyless",
        "site_key": sitekey,
        "site_url": page_url,
    }
    if invisible:
        body["is_invisible"] = True

    r = requests.post(
        API_URL,
        headers={"Authorization": f"Bearer {API_KEY}"},
        json=body,
        timeout=185,  # se bloquea hasta que el token está listo
    )
    data = r.json()
    if data.get("errorId") != 0:
        raise RuntimeError(f"{data.get('errorCode')}: {data.get('errorDescription')}")
    return data["solution"]["token"]


INJECT_TOKEN_JS = """
const token = arguments[0];
let field = document.querySelector('[name="h-captcha-response"]');
if (!field) {
    field = document.createElement("textarea");
    field.name = "h-captcha-response";
    field.style.display = "none";
    document.querySelector("form").appendChild(field);
}
field.value = token;
let extra = document.querySelector('[name="g-recaptcha-response"]');
if (!extra) {
    extra = document.createElement("textarea");
    extra.name = "g-recaptcha-response";
    extra.style.display = "none";
    document.querySelector("form").appendChild(extra);
}
extra.value = token;
"""


def main() -> None:
    driver = webdriver.Chrome()
    try:
        driver.get(PAGE_URL)

        widget = WebDriverWait(driver, 15).until(
            EC.presence_of_element_located((By.CSS_SELECTOR, "[data-sitekey]"))
        )
        sitekey = widget.get_attribute("data-sitekey")
        invisible = widget.get_attribute("data-size") == "invisible"

        token = solve(sitekey, driver.current_url, invisible=invisible)

        # Rellena primero los demás campos y luego inyecta el token justo antes de enviar,
        # porque el token caduca en unos dos minutos.
        driver.find_element(By.NAME, "email").send_keys("you@example.com")
        driver.execute_script(INJECT_TOKEN_JS, token)

        driver.find_element(By.CSS_SELECTOR, "form button[type=submit]").click()
        WebDriverWait(driver, 15).until(EC.url_changes(PAGE_URL))
        print("Submitted. Now at:", driver.current_url)
    finally:
        driver.quit()


if __name__ == "__main__":
    main()

El script no necesita proxy para una resolución de PopularCaptcha sin proxy. Omite el campo de proxy y deja que el sitio vea tu conexión normal.

El orden de las operaciones es la parte que más veces sale mal. Rellena el resto del formulario primero, luego resuelve, después inyecta y por último haz clic. Si resuelves antes de escribir en los demás campos, una página o un formulario lentos pueden hacer que el envío supere la vida útil del token. Mantén lo más corto posible el intervalo entre la resolución y el clic, y revisa la página de resultados en busca de errores antes de dar por hecho que el captcha se superó.

Por qué el token se inyecta con JavaScript

La mayoría de los widgets de PopularCaptcha escriben el token en un textarea oculto llamado h-captcha-response cuando una persona lo resuelve. Como Selenium no ve a ninguna persona, el script escribe ese mismo campo por sí mismo. El fragmento INJECT_TOKEN_JS hace dos cosas:

También rellena g-recaptcha-response, porque muchos sitios leen ese nombre por compatibilidad.

Widgets invisibles

Un widget invisible no tiene casilla. Se ejecuta en segundo plano y normalmente se dispara al enviar el formulario. El script comprueba data-size="invisible" y envía is_invisible: true cuando lo detecta. Algunos sitios además asocian una función de callback de JavaScript al widget. Si el formulario se queda donde está tras inyectar el token, busca un atributo data-callback y llama a esa función con el token. Nuestra guía sobre captcha invisible cubre este caso.

Uso en pruebas

Selenium es también la herramienta habitual para las pruebas end-to-end. Si tu propio sitio de staging muestra un captcha, el mismo código permite que una prueba lo supere. Mantén el entorno de pruebas separado de producción, y no ejecutes pruebas contra sitios que no sean tuyos. La configuración de las suites de pruebas está en captcha en pruebas automatizadas.

Problemas comunes

Precios

Pagas por resolución exitosa. Las resoluciones fallidas no se cobran. Consulta las tarifas actuales para ver los precios, y empieza con crédito de prueba gratis, sin tarjeta.

Próximos pasos