Un CAPTCHA no te obliga a levantar un navegador completo. CaptchaAI resuelve el desafío de forma remota y te devuelve un token; tu código en Node.js solo tiene que enviar una petición HTTP con Axios y adjuntar ese token al formulario. El resultado son procesos más ligeros, sin el arranque de Chrome ni los cientos de megabytes de RAM que consume cada instancia.
Para una agencia o un freelance que monitorea precios en marketplaces regionales o valida flujos de checkout de prueba desde un VPS modesto, esa diferencia es directa: más tareas concurrentes en la misma máquina y un costo mensual predecible en USD frente al pago por resolución. En esta guía integramos Axios con CaptchaAI para resolver reCAPTCHA, Turnstile y CAPTCHA de imagen usando solo peticiones HTTP.
Lo que necesitas
| Requisito | Detalles |
|---|---|
| Node.js | 16+ |
| axios | 1.x |
| Clave API CaptchaAI | Consíguela aquí |
npm install axios
Un cliente CaptchaAI reutilizable
Empieza por encapsular el flujo de envío y sondeo en una sola clase. Así reutilizas la misma lógica —enviar la tarea, consultar el resultado, gestionar el tiempo de espera— en cada tipo de CAPTCHA sin repetir código:
const axios = require("axios");
class CaptchaAI {
constructor(apiKey) {
this.apiKey = apiKey;
this.baseUrl = "https://ocr.captchaai.com";
}
async submit(params) {
params.key = this.apiKey;
const resp = await axios.get(`${this.baseUrl}/in.php`, { params });
const text = resp.data;
if (!String(text).startsWith("OK|")) {
throw new Error(`Submit failed: ${text}`);
}
return String(text).split("|")[1];
}
async poll(taskId, timeoutMs = 300000) {
const deadline = Date.now() + timeoutMs;
const params = { key: this.apiKey, action: "get", id: taskId };
while (Date.now() < deadline) {
await new Promise((r) => setTimeout(r, 5000));
const resp = await axios.get(`${this.baseUrl}/res.php`, { params });
const text = String(resp.data);
if (text === "CAPCHA_NOT_READY") continue;
if (text.startsWith("OK|")) return text.split("|").slice(1).join("|");
throw new Error(`Solve failed: ${text}`);
}
throw new Error(`Timeout after ${timeoutMs}ms for task ${taskId}`);
}
async solve(params, timeoutMs = 300000) {
const taskId = await this.submit(params);
return this.poll(taskId, timeoutMs);
}
async getBalance() {
const resp = await axios.get(`${this.baseUrl}/res.php`, {
params: { key: this.apiKey, action: "getbalance" },
});
return parseFloat(resp.data);
}
}
module.exports = CaptchaAI;
El método submit envía la tarea a in.php, poll consulta el resultado en res.php cada 5 segundos hasta que llega el token, y solve une ambos pasos. El método getBalance te deja comprobar el saldo antes de disparar un lote grande, para no quedarte a mitad de camino con ERROR_ZERO_BALANCE.
Resolver reCAPTCHA v2 sin navegador
Para reCAPTCHA v2 solo necesitas el sitekey del sitio y la URL de la página. CaptchaAI hace el resto y devuelve el g-recaptcha-response, que adjuntas al formulario con la misma petición de Axios:
const CaptchaAI = require("./captchaai");
async function main() {
const solver = new CaptchaAI(process.env.CAPTCHAAI_API_KEY);
// Solve the CAPTCHA without opening any browser
const token = await solver.solve({
method: "userrecaptcha",
googlekey: "6Le-wvkS...",
pageurl: "https://staging.example.com/qa-login",
});
// Submit form with the token using Axios
const resp = await axios.post("https://staging.example.com/qa-login", {
username: "user",
password: "pass",
"g-recaptcha-response": token,
});
console.log(`Login response: ${resp.status}`);
}
main().catch(console.error);
Resolver Turnstile sin navegador
Cloudflare Turnstile sigue el mismo patrón: cambias el method, pasas el sitekey en lugar del googlekey y el token viaja en el campo cf-turnstile-response. La clase reutilizable no cambia; solo varían los parámetros de la tarea.
const token = await solver.solve({
method: "turnstile",
sitekey: "0x4AAAAA...",
pageurl: "https://example.com",
});
// Submit with Turnstile token
const resp = await axios.post("https://example.com/api/verify", {
"cf-turnstile-response": token,
data: "payload",
});
Resolver CAPTCHA de imagen (OCR)
Cuando el desafío es una imagen con texto, la lees del disco, la codificas en base64 y la envías con method: "base64". La respuesta es directamente el texto resuelto:
const fs = require("fs");
const imageBuffer = fs.readFileSync("captcha.png");
const imageB64 = imageBuffer.toString("base64");
const text = await solver.solve({
method: "base64",
body: imageB64,
});
console.log(`CAPTCHA text: ${text}`);
// Submit form with solved text
const resp = await axios.post("https://example.com/verify", {
captcha: text,
other_data: "value",
});
Extraer una página protegida por CAPTCHA
Este es el flujo completo de scraping sin navegador: descargas el HTML con Axios, extraes el sitekey con Cheerio, resuelves el CAPTCHA y reenvías el formulario con el token. Todo sobre HTTP, sin renderizar nada:
const CaptchaAI = require("./captchaai");
const axios = require("axios");
const cheerio = require("cheerio");
async function scrapeProtectedPage(url) {
const solver = new CaptchaAI(process.env.CAPTCHAAI_API_KEY);
// Step 1: Fetch the page
const page = await axios.get(url);
const $ = cheerio.load(page.data);
// Step 2: Extract the reCAPTCHA site key
const siteKey = $(".g-recaptcha").attr("data-sitekey");
if (!siteKey) {
console.log("No CAPTCHA found, returning page content");
return page.data;
}
// Step 3: Solve the CAPTCHA
console.log(`Solving CAPTCHA for ${url}...`);
const token = await solver.solve({
method: "userrecaptcha",
googlekey: siteKey,
pageurl: url,
});
// Step 4: Submit form with token
const formAction = $("form").attr("action") || url;
const formData = {};
$("form input").each((_, el) => {
const name = $(el).attr("name");
const value = $(el).attr("value") || "";
if (name) formData[name] = value;
});
formData["g-recaptcha-response"] = token;
const result = await axios.post(formAction, new URLSearchParams(formData), {
headers: { "Content-Type": "application/x-www-form-urlencoded" },
});
return result.data;
}
scrapeProtectedPage("https://example.com/data")
.then((data) => console.log("Success:", typeof data))
.catch(console.error);
Recuerda respetar los términos de servicio del sitio y la normativa de protección de datos aplicable (GDPR y LOPDGDD en España, LFPDPPP en México) cuando automatices la extracción.
Resolución concurrente con Promise.all
Como no hay navegadores que gestionar, lanzar decenas de resoluciones en paralelo es tan simple como mapear un array de URL a promesas y esperarlas con Promise.all:
async function solveBatch(urls, siteKey) {
const solver = new CaptchaAI(process.env.CAPTCHAAI_API_KEY);
const promises = urls.map(async (url) => {
try {
const token = await solver.solve({
method: "userrecaptcha",
googlekey: siteKey,
pageurl: url,
});
return { url, token, error: null };
} catch (error) {
return { url, token: null, error: error.message };
}
});
const results = await Promise.all(promises);
const solved = results.filter((r) => r.token);
console.log(`Solved ${solved.length}/${urls.length}`);
return results;
}
Cuántas de esas resoluciones avanzan a la vez lo marca tu plan: CaptchaAI factura por threads concurrentes, no por resolución, así que BASIC ($15/mes, 5 threads) procesa 5 CAPTCHA en paralelo y STANDARD ($30/mes, 15 threads) llega a 15.
Errores comunes y cómo resolverlos
| Error | Causa | Solución |
|---|---|---|
AxiosError: getaddrinfo ENOTFOUND |
Problema de DNS | Verifica la conectividad de la red |
Submit failed: ERROR_WRONG_USER_KEY |
Clave API incorrecta | Revisa la clave en tu panel de control |
Submit failed: ERROR_ZERO_BALANCE |
Sin fondos | Añade saldo a tu cuenta |
| El sitio de destino rechaza el token | Token caducado | Envía el token en menos de 60 segundos |
Preguntas frecuentes
¿Cuántos CAPTCHA puedo resolver en paralelo?
Depende de los threads de tu plan. Un Promise.all puede lanzar cientos de promesas, pero CaptchaAI procesa tantas de forma simultánea como threads tengas contratados: 5 en BASIC ($15/mes), 15 en STANDARD ($30/mes) y así en adelante. El resto queda en cola hasta que se libera un thread.
¿Qué tipos de CAPTCHA resuelve este cliente Axios?
Los mismos que expone la API de CaptchaAI: reCAPTCHA v2 y v3, Cloudflare Turnstile y Cloudflare Challenge, GeeTest v3, e imagen/OCR y grid. hCaptcha y FunCaptcha (Arkose Labs) no son compatibles por ahora, así que no dependas de ellos en tu flujo.
El sitio rechaza el token, ¿qué reviso?
Casi siempre es caducidad: el token de reCAPTCHA vive unos 60 segundos, así que envíalo de inmediato tras resolverlo, no lo guardes. Confirma también que el pageurl y el sitekey coincidan exactamente con los de la página real.
¿Puedo usar el fetch nativo en lugar de Axios?
Sí. Node.js 18+ trae fetch incorporado y los parámetros de la API de CaptchaAI son idénticos. Axios solo aporta comodidades como el manejo de params y de errores; el flujo in.php / res.php no cambia.
Guías relacionadas
- Scraping de CAPTCHA con Node.js paso a paso
- Integrar HTTPX con CaptchaAI
- Usar CaptchaAI desde la línea de comandos con cURL