¿Resolviste un CAPTCHA ruso, el texto se ve perfecto, pero el formulario lo rechaza igual? El culpable casi siempre son los homoglifos: letras cirílicas como А, В, С o Е que se dibujan idénticas a las latinas pero ocupan puntos de código Unicode distintos. La respuesta corta es enviar la imagen a CaptchaAI con el parámetro language=2, que activa los modelos OCR compatibles con cirílico y devuelve los caracteres Unicode correctos. El resto de esta guía explica por qué el OCR latino se equivoca con el cirílico y cómo montar el flujo en Python y en JavaScript.
Por qué el OCR latino tropieza con el cirílico
Los sitios rusos, ucranianos, búlgaros y serbios usan CAPTCHA de texto cirílico precisamente porque muchos de sus caracteres son gemelos visuales de letras latinas. A ojo son indistinguibles; para la máquina son códigos completamente distintos. Si tu OCR devuelve la letra latina donde el sitio espera la cirílica, la validación del formulario rechaza un texto que se lee correcto.
| Se ve como | Latín | Cirílico | Nombre y código |
|---|---|---|---|
| A | A (U+0041) | А (U+0410) | Punto de código distinto |
| B | B (U+0042) | В (U+0412) | Cirílico "Ve" |
| C | C (U+0043) | С (U+0421) | Cirílico "Es" |
| E | E (U+0045) | Е (U+0415) | Codificación distinta |
| H | H (U+0048) | Н (U+041D) | Cirílico "En" |
| O | O (U+004F) | О (U+041E) | Punto de código distinto |
| P | P (U+0050) | Р (U+0420) | Cirílico "Er" |
Imagina que tu equipo extrae datos públicos de un marketplace ruso o monitoriza precios en un portal ucraniano para un cliente. Cada sesión pasa por un CAPTCHA de texto cirílico, y un solo carácter mal codificado tumba el envío del formulario. Multiplicado por miles de solicitudes al día, la diferencia entre А (U+0410) y A (U+0041) deja de ser un detalle y se convierte en tu tasa de fallo. Respeta siempre los términos de servicio del sitio y la normativa de protección de datos aplicable.
Tipos de CAPTCHA cirílico que te vas a encontrar
No todos los desafíos cirílicos son iguales. Antes de escribir código conviene saber qué formatos aparecen en formularios rusos y ucranianos, porque el manejo del texto devuelto cambia según el caso:
| Tipo | En qué consiste | Ejemplo |
|---|---|---|
| Palabra cirílica pura | Una palabra rusa al azar | ШКАФ, ПИРОГ |
| Mezcla de latín y cirílico | Ambos alfabetos en la misma imagen | ABС De (A, B y D latinas; С y e cirílicas) |
| Números escritos con letras | Cifras en palabras | ПЯТЬ (cinco), ТРИ (tres) |
| Operación aritmética en ruso | Suma o resta en palabras | два плюс три = ? |
| Cirílico deformado | Texto ruso distorsionado | Reto de OCR clásico, ahora en cirílico |
El flujo de resolución, paso a paso
Sea cual sea el formato, el ciclo con la API OCR de CaptchaAI es siempre el mismo:
- Codifica la imagen del CAPTCHA en base64.
- Envíala al endpoint
in.phpconmethod=base64,language=2yjson=1. - Sondea
res.phpcada cinco segundos hasta que el estado sea1. - Verifica que el texto devuelto contiene caracteres cirílicos antes de reenviarlo al formulario.
Ese paso final de verificación es el que evita la mayoría de rechazos silenciosos: confirma que recibiste puntos de código del rango cirílico y no sus sosias latinos.
Resolver un CAPTCHA cirílico en Python
La función solve_cyrillic_captcha() implementa el ciclo completo, y verify_cyrillic() hace de red de seguridad comprobando que la respuesta cae dentro del rango cirílico (U+0400–U+04FF). La variante solve_russian_form() muestra cómo encajarlo en una sesión que ya trae la cabecera Accept-Language: ru-RU.
import requests
import base64
import time
API_KEY = "YOUR_API_KEY"
SUBMIT_URL = "https://ocr.captchaai.com/in.php"
RESULT_URL = "https://ocr.captchaai.com/res.php"
def solve_cyrillic_captcha(image_path: str) -> str:
"""Solve a Cyrillic text image CAPTCHA."""
with open(image_path, "rb") as f:
image_b64 = base64.b64encode(f.read()).decode()
resp = requests.post(SUBMIT_URL, data={
"key": API_KEY,
"method": "base64",
"body": image_b64,
"language": 2, # Non-Latin character support
"json": 1,
}, timeout=30).json()
if resp.get("status") != 1:
raise RuntimeError(f"Submit: {resp.get('request')}")
task_id = resp["request"]
for _ in range(24):
time.sleep(5)
poll = requests.get(RESULT_URL, params={
"key": API_KEY, "action": "get", "id": task_id, "json": 1,
}, timeout=15).json()
if poll.get("request") == "CAPCHA_NOT_READY":
continue
if poll.get("status") == 1:
return poll["request"]
raise RuntimeError(f"Solve: {poll.get('request')}")
raise RuntimeError("Timeout")
def solve_cyrillic_from_session(session: requests.Session,
captcha_url: str) -> str:
"""Solve a Cyrillic CAPTCHA within a session context."""
resp = session.get(captcha_url, timeout=15)
image_b64 = base64.b64encode(resp.content).decode()
submit = requests.post(SUBMIT_URL, data={
"key": API_KEY,
"method": "base64",
"body": image_b64,
"language": 2,
"json": 1,
}, timeout=30).json()
if submit.get("status") != 1:
raise RuntimeError(f"Submit: {submit.get('request')}")
task_id = submit["request"]
for _ in range(24):
time.sleep(5)
poll = requests.get(RESULT_URL, params={
"key": API_KEY, "action": "get", "id": task_id, "json": 1,
}, timeout=15).json()
if poll.get("request") == "CAPCHA_NOT_READY":
continue
if poll.get("status") == 1:
return poll["request"]
raise RuntimeError(f"Solve: {poll.get('request')}")
raise RuntimeError("Timeout")
def verify_cyrillic(text: str) -> bool:
"""Verify that solved text contains Cyrillic characters."""
return any('\u0400' <= ch <= '\u04FF' for ch in text)
# --- Russian website form flow ---
def solve_russian_form(form_url: str, captcha_url: str,
form_data: dict) -> requests.Response:
"""Complete a Russian website form with CAPTCHA."""
session = requests.Session()
session.headers.update({
"Accept-Language": "ru-RU,ru;q=0.9",
})
# Establish session
session.get(form_url, timeout=15)
# Solve CAPTCHA
captcha_text = solve_cyrillic_from_session(session, captcha_url)
print(f"Cyrillic CAPTCHA: {captcha_text}")
if verify_cyrillic(captcha_text):
print("Confirmed: contains Cyrillic characters")
form_data["captcha"] = captcha_text
return session.post(form_url, data=form_data, timeout=30)
# --- Usage ---
text = solve_cyrillic_captcha("russian_captcha.png")
print(f"Solved: {text}")
print(f"Is Cyrillic: {verify_cyrillic(text)}")
print(f"Unicode codepoints: {[hex(ord(c)) for c in text]}")
El mismo flujo en JavaScript
Cuando tu pipeline vive en Node.js la lógica no cambia: mismo language=2, mismo sondeo cada cinco segundos. Añade isCyrillic() y showCodepoints() para volcar en tus logs los puntos de código exactos que devuelve el solver y cazar de un vistazo cualquier confusión entre alfabetos.
const API_KEY = "YOUR_API_KEY";
const SUBMIT_URL = "https://ocr.captchaai.com/in.php";
const RESULT_URL = "https://ocr.captchaai.com/res.php";
const fs = require("fs");
async function solveCyrillicCaptcha(imagePath) {
const imageB64 = fs.readFileSync(imagePath, "base64");
const body = new URLSearchParams({
key: API_KEY,
method: "base64",
body: imageB64,
language: "2",
json: "1",
});
const resp = await (await fetch(SUBMIT_URL, { method: "POST", body })).json();
if (resp.status !== 1) throw new Error(`Submit: ${resp.request}`);
const taskId = resp.request;
for (let i = 0; i < 24; i++) {
await new Promise((r) => setTimeout(r, 5000));
const url = `${RESULT_URL}?key=${API_KEY}&action=get&id=${taskId}&json=1`;
const poll = await (await fetch(url)).json();
if (poll.request === "CAPCHA_NOT_READY") continue;
if (poll.status === 1) return poll.request;
throw new Error(`Solve: ${poll.request}`);
}
throw new Error("Timeout");
}
function isCyrillic(text) {
return /[\u0400-\u04FF]/.test(text);
}
function showCodepoints(text) {
return [...text].map((ch) => `${ch}=U+${ch.codePointAt(0).toString(16).padStart(4, "0")}`);
}
// Usage
const text = await solveCyrillicCaptcha("russian_captcha.png");
console.log(`Solved: ${text}`);
console.log(`Is Cyrillic: ${isCyrillic(text)}`);
console.log(`Codepoints: ${showCodepoints(text).join(", ")}`);
Errores frecuentes y cómo solucionarlos
La mayoría de los fallos con cirílico se reducen a dos causas: homoglifos intercambiados y codificación mal gestionada. Esta tabla reúne los que más se repiten en producción:
| Síntoma | Causa probable | Cómo resolverlo |
|---|---|---|
| El formulario rechaza un texto que se ve correcto | Homoglifo latín/cirílico intercambiado | Compara los puntos de código: А (U+0410) ≠ A (U+0041) |
| Caracteres ilegibles al mostrar el texto | Codificación equivocada | Usa UTF-8 de principio a fin; fija response.encoding = 'utf-8' |
| Escritura mixta con parte del texto mal | El OCR confundió latín y cirílico | Envíalo con CaptchaAI y language=2 para separar los alfabetos |
| Faltan letras propias del ucraniano | Ґ, Є, І, Ї sin reconocer | Se reconocen con language=2 |
| Mayúsculas y minúsculas alteradas | El sitio distingue el caso | Envía el texto tal cual lo devuelve CaptchaAI |
Preguntas frecuentes
¿Qué hace exactamente el parámetro language=2?
Le indica al OCR de CaptchaAI que el texto no es latino y que debe usar los modelos compatibles con cirílico. Con ese ajuste, el solver devuelve los caracteres dentro del rango Unicode U+0400–U+04FF en lugar de sustituirlos por sus gemelos latinos. Es el único cambio que necesitas frente a un CAPTCHA de imagen normal.
¿Sirve también para búlgaro y serbio, no solo para ruso y ucraniano?
Sí. language=2 cubre todas las escrituras cirílicas, incluidas la rusa, la ucraniana, la búlgara y la serbia. Las letras exclusivas del ucraniano —ґ (U+0491), є (U+0454), і (U+0456), ї (U+0457)— también se reconocen, así que no necesitas configurar nada distinto según el país.
¿Cuánto cuesta resolver miles de CAPTCHA cirílicos al mes?
CaptchaAI factura por threads concurrentes, no por CAPTCHA resuelto, y cada plan incluye resoluciones ilimitadas por thread. El plan BASIC ($15/mes, 5 threads) sirve para pruebas y volúmenes bajos; para scraping intensivo en ruso, ADVANCE ($90/mes, 50 threads) o PREMIUM ($170/mes, 100 threads) resuelven muchos más CAPTCHA en paralelo. Tienes las tarifas completas en captchaai.com/pricing.
¿Por qué el formulario sigue rechazando el texto aunque parezca correcto?
Casi siempre es un homoglifo o un problema de codificación. Verifica con la función verify_cyrillic() que la respuesta contiene caracteres cirílicos reales y guarda el texto en UTF-8 sin reencodings intermedios. Si envías А (U+0410) donde el sitio espera esa letra cirílica —y no la A latina— la validación pasa.
Guías relacionadas
- Cómo resolver CAPTCHA en sitios web chinos
- CAPTCHA de imagen con varios juegos de caracteres
- Ajustes de idioma y localización en CAPTCHA