Tutoriales

BLS CAPTCHA: Comprensión de códigos de instrucciones y resolución

Si automatizas cualquier flujo que pase por un centro de visados BLS, el BLS CAPTCHA suele ser el último obstáculo antes de enviar el formulario: una cuadrícula de 3×3 con un código numérico que decide qué imágenes hay que marcar. La respuesta corta es que no tienes que interpretarlo a mano. Con la API de CaptchaAI extraes las nueve celdas, envías el código de instrucción junto con las imágenes y recibes de vuelta la lista exacta de celdas que hay que pulsar. En esta guía montamos ese flujo completo, paso a paso, en Python y Node.js.

Qué es el BLS CAPTCHA y dónde te lo encuentras

El BLS CAPTCHA es el desafío de imágenes que protege los portales de los centros de visados BLS, muy presentes en trámites que afectan a solicitantes de toda Latinoamérica y España. A diferencia de reCAPTCHA o Cloudflare Turnstile, no devuelve un token que puedas inyectar en un campo oculto: exige que un usuario mire una cuadrícula y seleccione las celdas correctas según una instrucción numérica. Por eso no basta con llamar a una API y pegar una respuesta; hace falta un navegador —Selenium o Puppeteer— que lea la cuadrícula, la resuelva y vuelva a pulsar sobre el DOM.

CaptchaAI resuelve BLS CAPTCHA con una alta tasa de éxito y expone el método dedicado method=bls en el mismo endpoint OCR que ya usas para otros retos de imagen. Trátalo como parte de la QA y la automatización de tu propio flujo autorizado: respeta siempre los términos de servicio del portal y la normativa de protección de datos aplicable (GDPR y LOPDGDD en la UE, o su equivalente local).

Cómo funciona la cuadrícula y el código de instrucción

Un BLS CAPTCHA muestra tres piezas:

  1. Una cuadrícula de 3×3 con 9 celdas de imagen.
  2. Un código de instrucción numérico (por ejemplo, 664, 123 o 546) que indica qué celdas seleccionar.
  3. Las celdas se numeran de izquierda a derecha y de arriba abajo:
1  2  3
4  5  6
7  8  9

El código de instrucción le dice al solver qué patrón buscar. La respuesta que recibes es un array de índices de celda (del 1 al 9) que coinciden con ese patrón. Tu trabajo, entonces, se reduce a tres tareas: capturar las 9 imágenes, leer el código y traducir el array de vuelta a clics sobre la cuadrícula real.

Paso 1: extrae las imágenes de la cuadrícula y el código

Primero necesitas las 9 imágenes como data URI en base64 y el número de instrucción como texto plano. Si las imágenes se sirven desde otro origen, conviene descargarlas del lado del servidor para esquivar restricciones CORS.

Python (Selenium)

import base64
from selenium import webdriver
from selenium.webdriver.common.by import By

driver = webdriver.Chrome()
driver.get("https://example.com/bls-protected-page")

# Find the grid container
grid_cells = driver.find_elements(By.CSS_SELECTOR, ".captcha-grid img")

images = []
for cell in grid_cells:
    src = cell.get_attribute("src")
    if src.startswith("data:image"):
        images.append(src)
    else:
        # Download and convert to base64
        import requests
        img_data = requests.get(src).content
        b64 = base64.b64encode(img_data).decode()
        images.append(f"data:image/png;base64,{b64}")

# Extract the instruction code
instruction_el = driver.find_element(By.CSS_SELECTOR, ".captcha-instruction")
instruction_code = instruction_el.text.strip()
# e.g., "664" or parsed from "Select all boxes with number 664"

import re
code_match = re.search(r'(\d{3,})', instruction_code)
instruction = code_match.group(1) if code_match else instruction_code

print(f"Instruction: {instruction}")
print(f"Images extracted: {len(images)}")

JavaScript (Puppeteer)

const puppeteer = require('puppeteer');

const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://example.com/bls-protected-page');

// Extract grid images as base64
const images = await page.evaluate(() => {
  const cells = document.querySelectorAll('.captcha-grid img');
  return Array.from(cells).map(img => {
    const canvas = document.createElement('canvas');
    canvas.width = img.naturalWidth;
    canvas.height = img.naturalHeight;
    canvas.getContext('2d').drawImage(img, 0, 0);
    return canvas.toDataURL('image/png');
  });
});

// Extract instruction code
const instruction = await page.evaluate(() => {
  const el = document.querySelector('.captcha-instruction');
  const match = el.textContent.match(/(\d{3,})/);
  return match ? match[1] : el.textContent.trim();
});

console.log(`Instruction: ${instruction}, Images: ${images.length}`);

En Puppeteer el truco es dibujar cada img en un canvas y llamar a toDataURL, con lo que obtienes el base64 sin una segunda descarga. En ambos lenguajes, la expresión regular \d{3,} recorta el número de instrucción aunque venga envuelto en una frase.

Paso 2: envía la cuadrícula a CaptchaAI

El solver de BLS espera method=bls, el campo instructions con el código y las 9 imágenes en image_base64_1 hasta image_base64_9. Envías la tarea al endpoint in.php y luego consultas el resultado en res.php hasta que esté lista.

Python

import requests
import time
import json

API_KEY = "YOUR_API_KEY"

# Prepare submission data
data = {
    "key": API_KEY,
    "method": "bls",
    "instructions": instruction,
    "json": "1",
}

# Add all 9 images
files = {}
for i, img in enumerate(images):
    files[f"image_base64_{i+1}"] = (None, img)

# Submit
resp = requests.post(
    "https://ocr.captchaai.com/in.php",
    data=data,
    files=files
).json()

if resp["status"] != 1:
    raise Exception(f"Submit error: {resp['request']}")

task_id = resp["request"]
print(f"Task ID: {task_id}")

# Poll for result
for _ in range(20):
    time.sleep(5)
    result = requests.get("https://ocr.captchaai.com/res.php", params={
        "key": API_KEY, "action": "get", "id": task_id, "json": "1"
    }).json()

    if result["status"] == 1:
        solution = json.loads(result["request"])
        print(f"Selected cells: {solution}")  # e.g., [1, 4, 7, 8]
        break
    if result["request"] != "CAPCHA_NOT_READY":
        raise Exception(f"Error: {result['request']}")

JavaScript

const axios = require('axios');
const FormData = require('form-data');

const form = new FormData();
form.append('key', 'YOUR_API_KEY');
form.append('method', 'bls');
form.append('instructions', instruction);
form.append('json', '1');

images.forEach((img, i) => {
  form.append(`image_base64_${i + 1}`, img);
});

const submit = await axios.post('https://ocr.captchaai.com/in.php', form, {
  headers: form.getHeaders(),
});
const taskId = submit.data.request;

// Poll
let solution = null;
for (let i = 0; i < 20; i++) {
  await new Promise(r => setTimeout(r, 5000));
  const poll = await axios.get('https://ocr.captchaai.com/res.php', {
    params: { key: 'YOUR_API_KEY', action: 'get', id: taskId, json: 1 }
  });
  if (poll.data.status === 1) {
    solution = JSON.parse(poll.data.request);
    break;
  }
}
console.log('Selected cells:', solution); // e.g., [2, 4, 7]

Mientras la tarea sigue en cola, res.php responde CAPCHA_NOT_READY; por eso el bucle espera 5 segundos entre cada consulta y reintenta hasta 20 veces antes de rendirse. Cuando status vale 1, el campo request trae el array de celdas en formato JSON.

Paso 3: marca las celdas resueltas

CaptchaAI te devuelve índices con base 1 (del 1 al 9). Como las listas de elementos del navegador empiezan en 0, resta uno a cada índice antes de hacer clic, y termina enviando el formulario.

# Selenium — click the cells returned by CaptchaAI
grid_cells = driver.find_elements(By.CSS_SELECTOR, ".captcha-grid .cell")

for cell_index in solution:
    # cell_index is 1-based
    grid_cells[cell_index - 1].click()

# Submit the form
submit_btn = driver.find_element(By.CSS_SELECTOR, ".captcha-submit")
submit_btn.click()
// Puppeteer
const cells = await page.$$('.captcha-grid .cell');
for (const idx of solution) {
  await cells[idx - 1].click();
}
await page.click('.captcha-submit');

Flujo completo en una función

Una vez entendidas las tres etapas, conviene encapsularlas en una sola función reutilizable. La siguiente rutina de Python extrae, resuelve y hace clic en una llamada, ideal para integrarla en tu pipeline de automatización:

def solve_bls_captcha(driver, api_key):
    """Extract, solve, and submit a BLS CAPTCHA."""
    import base64, requests, time, json, re

    # 1. Extract images
    grid_cells = driver.find_elements(By.CSS_SELECTOR, ".captcha-grid img")
    images = []
    for cell in grid_cells:
        src = cell.get_attribute("src")
        if src.startswith("data:image"):
            images.append(src)
        else:
            img_data = requests.get(src).content
            b64 = base64.b64encode(img_data).decode()
            images.append(f"data:image/png;base64,{b64}")

    # 2. Extract instruction
    el = driver.find_element(By.CSS_SELECTOR, ".captcha-instruction")
    match = re.search(r'(\d{3,})', el.text)
    instruction = match.group(1)

    # 3. Submit to CaptchaAI
    data = {"key": api_key, "method": "bls", "instructions": instruction, "json": "1"}
    files = {f"image_base64_{i+1}": (None, img) for i, img in enumerate(images)}
    resp = requests.post("https://ocr.captchaai.com/in.php", data=data, files=files).json()
    task_id = resp["request"]

    # 4. Poll
    for _ in range(20):
        time.sleep(5)
        result = requests.get("https://ocr.captchaai.com/res.php", params={
            "key": api_key, "action": "get", "id": task_id, "json": "1"
        }).json()
        if result["status"] == 1:
            solution = json.loads(result["request"])
            break

    # 5. Click cells
    clickable = driver.find_elements(By.CSS_SELECTOR, ".captcha-grid .cell")
    for idx in solution:
        clickable[idx - 1].click()

    return solution

Errores frecuentes y cómo resolverlos

Problema Causa Solución
ERROR_BAD_PARAMETERS Faltan imágenes o el código de instrucción Comprueba que las 9 imágenes sean data URI base64 válidos
Celdas incorrectas seleccionadas Mapeo celda→índice equivocado Verifica que las celdas van del 1 al 9, de izquierda a derecha y de arriba abajo
Las imágenes no cargan Restricción de origen cruzado (CORS) Descarga las imágenes en el servidor y conviértelas a base64
Código de instrucción vacío La instrucción está dentro de una imagen Extrae el texto o aplica OCR a la imagen de la instrucción

Preguntas frecuentes

¿Puedo resolver el BLS CAPTCHA solo con la API, sin navegador?

No. La API resuelve la cuadrícula, pero necesitas un navegador con Selenium o Puppeteer (o acceso directo al DOM) para capturar las 9 imágenes y volver a marcar las celdas. El BLS CAPTCHA no entrega un token inyectable como reCAPTCHA o Turnstile.

¿Cuánto tarda en resolverse un BLS CAPTCHA?

Depende de la carga en ese momento. El código de ejemplo sondea res.php cada 5 segundos y reintenta hasta 20 veces; en la práctica la respuesta suele llegar en las primeras consultas, mucho antes de agotar ese margen.

¿Qué plan de CaptchaAI necesito para resolver BLS a volumen?

El plan BASIC ($15/mes, 5 threads) es suficiente para empezar. Como la facturación es por thread con resoluciones ilimitadas, si procesas muchas cuadrículas en paralelo escala a STANDARD ($30/mes, 15 threads) o ADVANCE ($90/mes, 50 threads).

¿Sirve el mismo código para otras cuadrículas de imágenes?

El patrón de extraer imágenes, enviarlas y traducir el array a clics es reutilizable, pero cada tipo usa su método: BLS usa method=bls, mientras que las cuadrículas genéricas de reCAPTCHA se resuelven con method=post. Ajusta el método y los campos según el reto.

Automatiza tu BLS CAPTCHA con CaptchaAI

Consigue tu API key en captchaai.com, conecta el método bls a tu flujo de Selenium o Puppeteer y deja de marcar cuadrículas a mano.

Guías relacionadas

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