Tutoriales de API

CAPTCHA personalizados: cómo enviar desafíos inusuales a CaptchaAI

¿Te topaste con un CAPTCHA que ningún solver reconoce —un slider raro, una imagen que hay que girar o un widget hecho a mano? La respuesta casi siempre es la misma: haz una captura del reto y envíala a CaptchaAI junto con una instrucción en texto que diga exactamente qué debe devolver el resolvedor. Con ese único patrón —imagen en base64 más el campo textinstructions— cubres la mayoría de los CAPTCHA propietarios que no encajan en reCAPTCHA, Turnstile ni en las imágenes de texto habituales.

El resto de la guía aterriza ese patrón en código real, en este orden:

  • La función base que hace el trabajo pesado y se reutiliza en cada ejemplo.
  • Una variante por cada tipo: slider, rotación, orden, audio y widget a medida.
  • Un detector que elige la rama correcta antes de gastar una llamada.

Cómo identificar un CAPTCHA fuera de lo común

Antes de escribir código conviene saber ante qué tipo estás. Esta tabla resume los formatos más frecuentes y el enfoque que funciona con cada uno:

Tipo En qué consiste Enfoque
CAPTCHA slider Arrastrar una pieza hasta la posición correcta Captura de pantalla + instrucciones de texto
Puzzle (rompecabezas) Encajar una pieza en su hueco Asignable a una resolución estilo GeeTest
CAPTCHA de audio Escuchar y transcribir Enviar el archivo de audio
Rotación de imagen Girar hasta la orientación correcta Captura de pantalla + instrucciones
Selección en orden Hacer clic en elementos en secuencia Enfoque de cuadrícula de imágenes
Ecuación matemática Resolver una operación aritmética Parámetro calc=1
Widget interactivo a medida Widget JS específico del sitio Captura de pantalla + instrucciones de texto

El patrón universal: captura más instrucciones de texto

Cualquier CAPTCHA visual se reduce a lo mismo: una imagen y una orden clara sobre qué responder. Esta función envía ambas cosas al endpoint in.php, espera y sondea el resultado en res.php. Reutilízala en todos los ejemplos posteriores:

import requests
import base64
import time
import os

API_KEY = os.environ["CAPTCHAAI_API_KEY"]


def solve_custom_captcha(image_b64, instructions):
    """Solve any visual CAPTCHA using image + text instructions."""
    resp = requests.post("https://ocr.captchaai.com/in.php", data={
        "key": API_KEY,
        "method": "base64",
        "body": image_b64,
        "textinstructions": instructions,
        "json": 1,
    }, timeout=30)

    result = resp.json()
    if result.get("status") != 1:
        raise RuntimeError(result.get("request"))

    task_id = result["request"]

    time.sleep(10)
    for _ in range(30):
        resp = requests.get("https://ocr.captchaai.com/res.php", params={
            "key": API_KEY, "action": "get",
            "id": task_id, "json": 1,
        }, timeout=15)
        data = resp.json()
        if data.get("status") == 1:
            return data["request"]
        if data["request"] != "CAPCHA_NOT_READY":
            raise RuntimeError(data["request"])
        time.sleep(5)

    raise TimeoutError("Solve timeout")

Cómo redactar textinstructions

La clave está en textinstructions: cuanto más concreta sea la orden, más limpia será la respuesta. Dos reglas:

  • Pide un formato de salida cerrado: "devuelve solo el número" o "solo la lista separada por comas".
  • Evita la descripción libre; el resolvedor debe devolver un valor parseable, no una frase.

Sliders: arrastrar la pieza a su posición

Piensa en el QA de un portal de cita previa o de un marketplace tipo MercadoLibre: muchos protegen el formulario con un slider propietario que Selenium no sabe mover por sí solo. Captura el widget, pregunta a CaptchaAI a qué desplazamiento en píxeles debe llegar la pieza y arrástrala con ActionChains:

# slider_captcha.py
from selenium.webdriver.common.by import By
from selenium.webdriver.common.action_chains import ActionChains


def solve_slider_captcha(driver, captcha_selector):
    """Screenshot slider CAPTCHA and solve via CaptchaAI."""
    captcha = driver.find_element(By.CSS_SELECTOR, captcha_selector)
    image_b64 = captcha.screenshot_as_base64

    result = solve_custom_captcha(
        image_b64,
        "What pixel position should the slider be dragged to? "
        "Return only the X offset number."
    )

    try:
        offset = int(result)
    except ValueError:
        return False

    # Drag slider to position
    slider = driver.find_element(By.CSS_SELECTOR, ".slider-handle")
    ActionChains(driver).click_and_hold(slider).move_by_offset(offset, 0).release().perform()

    return True

Consejo: el offset de la API es relativo al inicio del arrastre; si el widget parte de un desfase, réstalo antes de mover la pieza.


Rotación: cuántos grados girar la imagen

En los CAPTCHA de rotación tienes que dejar una imagen en su orientación vertical correcta. Pide a la API los grados en el sentido de las agujas del reloj y traduce ese número a clicks sobre el botón de girar (uno cada 90°):

# rotation_captcha.py


def solve_rotation_captcha(driver, captcha_selector):
    """Solve rotation CAPTCHA."""
    captcha = driver.find_element(By.CSS_SELECTOR, captcha_selector)
    image_b64 = captcha.screenshot_as_base64

    result = solve_custom_captcha(
        image_b64,
        "How many degrees should this image be rotated clockwise "
        "to be in the correct upright orientation? Return only the number."
    )

    try:
        degrees = int(result)
    except ValueError:
        return False

    # Click rotation button the correct number of times
    rotate_btn = driver.find_element(By.CSS_SELECTOR, ".rotate-button")
    clicks = degrees // 90  # Each click rotates 90 degrees

    for _ in range(clicks):
        rotate_btn.click()
        time.sleep(0.3)

    return True

Consejo: divide los grados entre 90 solo si el botón gira en pasos fijos; con ángulos libres, aplícalos de una vez.


Selección en orden: clicks en secuencia

Aquí hay que pulsar varios elementos siguiendo un orden concreto (por ejemplo, "haz clic en los iconos del más pequeño al más grande"). Pide la secuencia como una lista de posiciones y recórrela para hacer clic en el elemento correcto:

# order_captcha.py


def solve_order_captcha(driver, captcha_selector, item_selector):
    """Solve click-in-order CAPTCHA."""
    captcha = driver.find_element(By.CSS_SELECTOR, captcha_selector)
    image_b64 = captcha.screenshot_as_base64

    result = solve_custom_captcha(
        image_b64,
        "What is the correct order? Return as comma-separated "
        "numbers (1-indexed) representing positions left-to-right, top-to-bottom."
    )

    # Parse order
    try:
        order = [int(x.strip()) for x in result.split(",")]
    except ValueError:
        return False

    # Click items in order
    items = driver.find_elements(By.CSS_SELECTOR, item_selector)
    for idx in order:
        if 1 <= idx <= len(items):
            items[idx - 1].click()
            time.sleep(0.5)

    return True

CAPTCHA de audio: transcribir el sonido

Muchos sitios ofrecen una alternativa en audio junto al reto visual. Descarga el archivo, conviértelo a base64 y envíalo con una instrucción de transcripción.

# audio_captcha.py
import requests


def solve_audio_captcha(audio_url):
    """Download and solve an audio CAPTCHA."""
    # Download audio
    resp = requests.get(audio_url, timeout=30)
    audio_b64 = base64.b64encode(resp.content).decode("ascii")

    # Submit as image with instructions
    # CaptchaAI may support audio via the base64 method
    result = solve_custom_captcha(
        audio_b64,
        "This is an audio CAPTCHA. Transcribe the spoken characters."
    )
    return result

Trátalo como un flujo experimental: CaptchaAI puede aceptar audio por el método base64 en algunos casos, así que valida siempre la respuesta antes de darla por buena.


Widgets JS a medida: el caso más difícil

Cuando el reto es un componente JavaScript propio del sitio, no hay un tipo predefinido al que asignarlo. La estrategia es genérica: captura el widget completo, lee cualquier instrucción visible en la propia página, mándala como contexto y luego intenta escribir la respuesta en el input o inyectarla por JavaScript si no hay campo evidente:

# custom_widget.py
from selenium import webdriver
from selenium.webdriver.common.by import By


def handle_custom_widget(driver, widget_selector):
    """Handle an unknown custom CAPTCHA widget."""

    # Step 1: Screenshot the entire widget
    widget = driver.find_element(By.CSS_SELECTOR, widget_selector)
    image_b64 = widget.screenshot_as_base64

    # Step 2: Get any visible instructions
    try:
        instructions_el = widget.find_element(By.CSS_SELECTOR, ".instructions, .prompt, p")
        visible_instructions = instructions_el.text
    except Exception:
        visible_instructions = "Solve this CAPTCHA"

    # Step 3: Submit with descriptive instructions
    result = solve_custom_captcha(
        image_b64,
        f"CAPTCHA instructions: {visible_instructions}. "
        f"Return the answer text."
    )

    # Step 4: Try to submit result
    try:
        input_el = widget.find_element(By.CSS_SELECTOR, "input")
        input_el.clear()
        input_el.send_keys(result)
    except Exception:
        # No input — try clicking based on result
        driver.execute_script("""
            var input = document.querySelector('input[name*="captcha"]');
            if (input) input.value = arguments[0];
        """, result)

    return result

Reutilizar la instrucción que el propio sitio muestra al usuario es lo que más sube la tasa de éxito: le das al resolvedor el mismo contexto que ve una persona.


Detectar el tipo de CAPTCHA antes de resolver

Si tu scraper visita páginas variadas, conviene decidir qué rama usar antes de enviar nada. Este detector revisa el HTML y devuelve los tipos que reconoce, para enrutar cada página al resolvedor adecuado:

# detector.py
import re


def detect_captcha_type(page_html):
    """Detect which CAPTCHA type is on a page."""
    checks = {
        "recaptcha_v2": r'data-sitekey.*g-recaptcha',
        "recaptcha_v3": r'recaptcha/api\.js\?render=',
        "turnstile": r'cf-turnstile|challenges\.cloudflare\.com/turnstile',
        "geetest": r'gt\b.*challenge|geetest',
        "bls": r'method.*bls|bls-captcha',
        "image_text": r'captcha.*\.(png|jpg|gif|jpeg)',
        "slider": r'slider.*captcha|slide.*verify',
        "audio": r'audio.*captcha|captcha.*audio',
    }

    detected = []
    for captcha_type, pattern in checks.items():
        if re.search(pattern, page_html, re.IGNORECASE):
            detected.append(captcha_type)

    return detected if detected else ["unknown"]

Errores comunes y cómo resolverlos

La mayoría de fallos con CAPTCHA personalizados vienen de una imagen borrosa o de una instrucción ambigua. Estos son los síntomas más habituales y su arreglo:

Problema Causa Solución
ERROR_CAPTCHA_UNSOLVABLE Imagen poco clara o instrucciones vagas Mejora la calidad de las capturas y precisa las instrucciones
Formato de respuesta incorrecto El resolvedor devolvió una descripción en lugar del valor Sé específico: "Devuelve solo el número"
Widget no capturado Elemento fuera de la ventana visible Desplázate al elemento antes de la captura de pantalla
La interacción falla Coordenadas de clic incorrectas Mapea con cuidado la respuesta a los elementos reales de la UI

Preguntas frecuentes

¿Qué escribo en el campo textinstructions?

Describe la tarea y, sobre todo, el formato exacto de salida. En vez de "resuelve este slider", pide "devuelve solo el número del desplazamiento en X". Una orden con formato cerrado evita que el resolvedor conteste con una frase que luego tu código no puede parsear.

¿Sirve el mismo enfoque para sliders, rotación y orden?

Sí. La función solve_custom_captcha es la misma en los tres casos; lo único que cambia es la instrucción de texto y cómo interpretas el resultado (un offset, unos grados o una lista de posiciones). Ese es justo el valor del patrón captura + instrucciones.

¿CaptchaAI es compatible con hCaptcha o FunCaptcha?

No. hCaptcha y FunCaptcha (Arkose Labs) no están soportados actualmente. Este enfoque cubre CAPTCHA de imagen, sliders, rotación, audio y widgets a medida; para reCAPTCHA v2/v3, Cloudflare Turnstile o GeeTest v3 usa los métodos dedicados con sus guías específicas.

¿Cuánto cuesta procesar muchos CAPTCHA personalizados?

CaptchaAI cobra por thread concurrente, no por resolución, así que el volumen no dispara la factura. El plan BASIC ($15/mes, 5 threads) incluye resoluciones ilimitadas por thread; si tu scraper procesa páginas en paralelo, sube de plan para tener más threads en vez de pagar por cada CAPTCHA. Es un costo mensual predecible en USD, cómodo para agencias y freelancers que facturan en monedas locales volátiles.


Guías relacionadas

  • Estrategias para CAPTCHA de varios caracteres
  • Buenas prácticas de codificación en Base64

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