Aquí no hay sitekey ni token: subes el PNG a la API OCR de CaptchaAI y recuperas en texto plano las letras que muestra. Con axios y fs, unas veinte líneas de Node.js.
Los formularios que aún piden letras deformadas son casi siempre los mismos: portales de trámites públicos, intranets antiguas y registros sin tocar en años. Ese recuadro gris detiene tu script; CaptchaAI lee la imagen y devuelve el texto.
Lo que necesitas antes de empezar
| Elemento | Valor |
|---|---|
| Clave API de CaptchaAI | Desde captchaai.com |
| Node.js | 14 o superior |
| Librerías | axios, fs |
| Formato de imagen | JPG, PNG o GIF (100 bytes – 100 KB) |
Captura solo el elemento del CAPTCHA, nunca la página entera: el límite son 100 KB.
Enviar la imagen: base64 o archivo
Ninguna vía es mejor: elige según cómo llega la imagen.
En base64, cuando está en memoria
Codificas el buffer y lo mandas en el parámetro body.
const axios = require('axios');
const fs = require('fs');
const API_KEY = 'YOUR_API_KEY';
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
// Read and encode the image
const imageB64 = fs.readFileSync('captcha.png').toString('base64');
// Submit to CaptchaAI
const { data: submitData } = await axios.post('https://ocr.captchaai.com/in.php', null, {
params: {
key: API_KEY,
method: 'base64',
body: imageB64,
json: 1,
},
});
if (submitData.status !== 1) throw new Error(submitData.request);
const taskId = submitData.request;
console.log(`Task submitted: ${taskId}`);
Con json: 1 la respuesta trae status y request. Si status no es 1, request trae el código de error.
Como archivo, cuando la imagen está en disco
Si tu pipeline ya los guarda en disco, envíalos con form-data y el método post.
const FormData = require('form-data');
const form = new FormData();
form.append('key', API_KEY);
form.append('method', 'post');
form.append('json', '1');
form.append('file', fs.createReadStream('captcha.png'));
const { data: submitData } = await axios.post('https://ocr.captchaai.com/in.php', form, {
headers: form.getHeaders(),
});
const taskId = submitData.request;
Consultar el resultado en res.php
Mientras la tarea siga en cola, res.php responde CAPCHA_NOT_READY.
await sleep(5000);
let captchaText;
for (let i = 0; i < 30; i++) {
const { data: pollData } = await axios.get('https://ocr.captchaai.com/res.php', {
params: { key: API_KEY, action: 'get', id: taskId, json: 1 },
});
if (pollData.status === 1) {
captchaText = pollData.request;
console.log(`CAPTCHA text: ${captchaText}`);
break;
}
if (pollData.request !== 'CAPCHA_NOT_READY') {
throw new Error(pollData.request);
}
await sleep(5000);
}
La asimetría del bucle importa: CAPCHA_NOT_READY es "sigue esperando"; cualquier otro valor, "para y registra el código".
Parámetros que mejoran la precisión
Si conoces el formato, decláralo: leído como texto libre, un 0 se confunde con una O.
// Digits only, 4-6 characters
const { data } = await axios.post('https://ocr.captchaai.com/in.php', null, {
params: {
key: API_KEY,
method: 'base64',
body: imageB64,
numeric: 1, // digits only
min_len: 4, // minimum length
max_len: 6, // maximum length
json: 1,
},
});
| Parámetro | Valor | Propósito |
|---|---|---|
numeric |
1 = dígitos, 2 = letras |
Limita los caracteres admitidos |
min_len / max_len |
Entero | Restricciones de longitud |
calc |
1 |
Calcula la expresión matemática |
regsense |
1 |
Distingue mayúsculas de minúsculas |
Escenario típico en la región: un portal de cita previa o de trámites fiscales que siempre muestra cinco dígitos. Con numeric: 1, min_len: 5 y max_len: 5 bajan los reintentos por lecturas ambiguas. Revisa varias capturas antes: si el portal alterna letras y números, restringir de más genera fallos tuyos.
Ejemplo completo con Puppeteer
Todas las piezas juntas: abrir la página, capturar el CAPTCHA, enviarlo, sondear y rellenar.
const axios = require('axios');
const puppeteer = require('puppeteer');
const fs = require('fs');
const API_KEY = 'YOUR_API_KEY';
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
async function solveImageCaptcha() {
// 1. Load page and screenshot CAPTCHA
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://example.com/register');
const captchaEl = await page.$('#captcha-image');
await captchaEl.screenshot({ path: 'captcha.png' });
// 2. Encode and submit
const imageB64 = fs.readFileSync('captcha.png').toString('base64');
const { data: submit } = await axios.post('https://ocr.captchaai.com/in.php', null, {
params: { key: API_KEY, method: 'base64', body: imageB64, json: 1 },
});
const taskId = submit.request;
// 3. Poll for text
await sleep(5000);
let text;
for (let i = 0; i < 30; i++) {
const { data: poll } = await axios.get('https://ocr.captchaai.com/res.php', {
params: { key: API_KEY, action: 'get', id: taskId, json: 1 },
});
if (poll.status === 1) { text = poll.request; break; }
if (poll.request !== 'CAPCHA_NOT_READY') throw new Error(poll.request);
await sleep(5000);
}
// 4. Type and submit
await page.type('#captcha-input', text);
await page.click('form [type="submit"]');
console.log(`Solved: ${text}`);
await browser.close();
}
solveImageCaptcha().catch(console.error);
Resultado esperado:
Solved: ABC123
La captura es la parte frágil: si #captcha-image apunta al contenedor y no a la imagen, recibirás ERROR_ZERO_CAPTCHA_FILESIZE.
Errores comunes y qué hacer con ellos
| Error | Causa | Solución |
|---|---|---|
ERROR_WRONG_FILE_EXTENSION |
Formato no compatible | Usa JPG, PNG o GIF |
ERROR_TOO_BIG_CAPTCHA_FILESIZE |
Imagen mayor de 100 KB | Comprime antes de enviar |
ERROR_ZERO_CAPTCHA_FILESIZE |
Imagen menor de 100 bytes | Revisa el selector y la captura |
CAPCHA_NOT_READY |
Todavía en proceso | Sigue consultando cada 5 segundos |
Los tres primeros se arreglan antes de la llamada. El cuarto no es un error: es una tarea en curso.
Coste y concurrencia
CaptchaAI factura por thread concurrente, no por resolución: cada plan incluye resoluciones ilimitadas por thread. Un thread es un CAPTCHA en vuelo y, al terminar, queda libre. Para un scraper de un proceso basta BASIC ($15/mes, 5 threads); con varios workers, STANDARD ($30/mes, 15 threads) o ADVANCE ($90/mes, 50 threads). Precios en USD, con coste mensual predecible.
Preguntas frecuentes
¿Necesito Puppeteer para resolver un CAPTCHA de imagen?
No. Solo captura la imagen de una página renderizada; si tu backend ya recibe el PNG, bastan axios y fs.
¿Qué hago si el texto devuelto es incorrecto?
Repórtalo con action=reportbad sobre res.php indicando el id de la tarea, y declara el formato con numeric y min_len/max_len.
¿Sirve este mismo código para reCAPTCHA o Turnstile?
No. Ahí se envía el sitekey y la URL, y la API devuelve un token. CaptchaAI cubre reCAPTCHA v2/v3, Cloudflare Turnstile y Challenge, GeeTest v3 e imagen/OCR; hCaptcha y FunCaptcha no son compatibles.
¿Puedo resolver CAPTCHA con operaciones matemáticas?
Sí. Añade calc: 1 y recibirás el resultado ya calculado, no la expresión.
Guías relacionadas
Empieza a resolver CAPTCHA de imagen con CaptchaAI
Configuración de navegador recomendada
Usa la misma configuración en QA, staging y CI: evita el test que pasa en local y falla en el runner.
from selenium import webdriver
def make_driver(headless: bool = True) -> webdriver.Chrome:
options = webdriver.ChromeOptions()
if headless:
options.add_argument('--headless=new')
options.add_argument('--window-size=1280,800')
options.add_argument('--lang=es-ES')
return webdriver.Chrome(options=options)
Igualar viewport, idioma y user-agent reduce la varianza entre ejecuciones.
Cómo encaja CaptchaAI en tu pipeline
El patrón no cambia con el framework de pruebas:
- Tu test detecta el CAPTCHA en tu aplicación (QA, staging o preproducción).
- Envía a CaptchaAI los datos públicos: la imagen en OCR, o el
sitekeyy la URL en los tipos basados en token. - CaptchaAI devuelve el texto o el token.
- Tu test lo escribe en el campo y envía la petición.
- Tu backend lo valida contra el proveedor.
Aplica a integraciones que tú controlas. Respeta los términos de servicio y la normativa de protección de datos.
Métricas y buenas prácticas
Instrumenta el CAPTCHA como cualquier dependencia externa: tiempo de resolución, tasa de éxito por endpoint, errores por código y latencia extremo a extremo. Conserva logs y capturas: sin ellos un fallo intermitente no se reproduce.
- Prueba siempre sobre tu aplicación o entornos autorizados.
- Mantén una clave API de QA separada de la de producción.
- Define tiempos de espera y reintentos con backoff exponencial.
- Versiona la configuración de los tests (selectores, formato, longitudes).
Diagnóstico rápido
| Síntoma | Acción recomendada |
|---|---|
| El test no encuentra la imagen del CAPTCHA | Revisa selectores y esperas en staging |
CaptchaAI devuelve ERROR_NO_SLOT_AVAILABLE |
Reintenta con backoff |
| El backend rechaza el texto enviado | Compara mayúsculas y longitud con el formato real |
| El test pasa en local y falla en CI | Iguala viewport, idioma y user-agent |
| Tiempos de resolución muy variables | Revisa los threads de tu plan |
Valida tus integraciones en entornos propios con CaptchaAI.