Scarnox · Blog

Resolver captchas en PHP con cURL: ejemplo funcional (2026)

PHP solo necesita la extensión cURL, que viene con la mayoría de las instalaciones, para resolver un captcha. Envías un POST JSON a la API de Scarnox, lees solution.token de la respuesta y envías ese token al formulario del sitio. Esta guía recorre el flujo completo en PHP para PopularCaptcha, que Scarnox resuelve hoy sin proxy.

Empieza con crédito de prueba gratis, sin tarjeta y prueba el script en una página que controles antes de usarlo en cualquier otro sitio.

Requisitos

La función del solver

Esta función hace un POST a la API, comprueba errorId y devuelve el token. Lanza una excepción ante cualquier fallo, para que quien llama decida si reintentar:

<?php
declare(strict_types=1);

const SCARNOX_API_KEY = 'scarnox_YOURAPIKEY';
const SCARNOX_API_URL = 'https://api.scarnox.com/api/tasks/create';

function solveCaptcha(string $sitekey, string $pageUrl, bool $invisible = false): string
{
    $body = [
        'captcha_type' => 'PopularCaptchaTaskProxyless',
        'site_key'     => $sitekey,
        'site_url'     => $pageUrl,
    ];
    if ($invisible) {
        $body['is_invisible'] = true;
    }

    $ch = curl_init(SCARNOX_API_URL);
    curl_setopt_array($ch, [
        CURLOPT_POST           => true,
        CURLOPT_POSTFIELDS     => json_encode($body, JSON_THROW_ON_ERROR),
        CURLOPT_HTTPHEADER     => [
            'Authorization: Bearer ' . SCARNOX_API_KEY,
            'Content-Type: application/json',
        ],
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT        => 185, // the call returns when the token is ready
    ]);

    $raw = curl_exec($ch);
    if ($raw === false) {
        $error = curl_error($ch);
        throw new RuntimeException('cURL error: ' . $error);
    }

    $data = json_decode((string) $raw, true);
    if (!is_array($data)) {
        throw new RuntimeException('Response was not JSON');
    }
    if (($data['errorId'] ?? 1) !== 0) {
        throw new RuntimeException(
            ($data['errorCode'] ?? 'ERROR') . ': ' . ($data['errorDescription'] ?? 'unknown')
        );
    }

    return (string) $data['solution']['token'];
}

La cabecera Authorization usa el prefijo Bearer. El timeout es de 185 segundos porque la API se bloquea hasta que el token está listo, sin ningún paso de sondeo.

Reintentar los fallos temporales

Una resolución fallida no se cobra, así que unos pocos reintentos salen baratos. Pon el bucle alrededor de la llamada y espera un poco más después de cada fallo:

function solveWithRetry(string $sitekey, string $pageUrl, int $attempts = 3): string
{
    $lastError = null;
    for ($i = 1; $i <= $attempts; $i++) {
        try {
            return solveCaptcha($sitekey, $pageUrl);
        } catch (RuntimeException $e) {
            $lastError = $e;
            fwrite(STDERR, "Attempt $i failed: " . $e->getMessage() . PHP_EOL);
            sleep($i);
        }
    }
    throw $lastError;
}

Para una política de errores completa, incluidos los fallos en los que conviene parar, consulta gestión de errores del solver de captchas.

Muchos sitios vinculan el captcha a la sesión que cargó el formulario. Un archivo de cookies permite a cURL enviar las mismas cookies en el GET y en el POST. La función siguiente envía el token al formulario de la página:

function submitToken(string $pageUrl, string $token): string
{
    $cookieFile = sys_get_temp_dir() . '/scarnox-cookies.txt';

    // GET first, so the site sets the session cookie.
    $ch = curl_init($pageUrl);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_FOLLOWLOCATION => true,
        CURLOPT_COOKIEFILE     => $cookieFile,
        CURLOPT_COOKIEJAR      => $cookieFile,
        CURLOPT_TIMEOUT        => 60,
    ]);
    curl_exec($ch);

    // Then POST the form with the token in both captcha fields.
    $fields = http_build_query([
        'email'                 => 'you@example.com',
        'h-captcha-response'    => $token,
        'g-recaptcha-response'  => $token,
    ]);

    curl_setopt_array($ch, [
        CURLOPT_POST       => true,
        CURLOPT_POSTFIELDS => $fields,
        CURLOPT_TIMEOUT    => 60,
    ]);
    $body = curl_exec($ch);
    if ($body === false) {
        throw new RuntimeException('Form submit failed: ' . curl_error($ch));
    }

    return (string) $body;
}

Hay dos cosas que importan. El GET se ejecuta antes de la resolución para que la cookie ya esté en su sitio. Y el token sale justo después de resolverlo, porque caduca en unos dos minutos. Coloca la llamada de resolución justo antes de esta función, no al principio de un lote largo.

El archivo de cookies se comparte entre todas las llamadas del mismo proceso, así que dale a cada cuenta o sesión su propio archivo si ejecutas varios registros en paralelo. Si no, una sesión puede sobrescribir las cookies de otra, y el segundo POST fallará por motivos que parecen un problema de captcha pero no lo son.

Juntándolo todo

$pageUrl = 'https://example.com/signup';
$token   = solveWithRetry('YOUR-SITEKEY-UUID', $pageUrl);
$html    = submitToken($pageUrl, $token);

echo strlen($html) . " bytes returned\n";

Ejecútalo desde la línea de comandos:

php signup.php

Widgets invisibles

Si el widget es invisible, llama a solveCaptcha($sitekey, $pageUrl, true). El campo is_invisible se enviará entonces con la petición. Algunas páginas también necesitan un callback de JavaScript que se llame con el token, algo que un script del lado del servidor no puede activar. En ese caso usa una herramienta de navegador. La guía sobre captchas invisibles cubre los casos.

Tiempos y caducidad

El token es válido durante unos dos minutos. Mantén la resolución y el envío juntos en el tiempo. Caducidad del token de captcha explica cómo distinguir un token caducado de uno rechazado.

Scrapea de forma responsable

Si tu script de PHP hace scraping de páginas públicas, revisa el robots.txt y los términos del sitio antes de empezar. Añade una pausa entre peticiones y no almacenes datos personales que no necesitas. Resolver un captcha no cambia lo que tienes permiso para recopilar. Para el lado legal, consulta ¿es legal el web scraping?.

Precios

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

Próximos pasos