Casos de Uso

Manejo de CAPTCHA para la automatización de búsqueda de registros públicos

Sí, los CAPTCHA de un portal de registros públicos se resuelven por API, y casi siempre con el método más antiguo del catálogo: descargas la imagen dentro de la misma sesión que cargó el formulario, la envías en base64 al endpoint in.php, recuperas el texto en res.php y lo devuelves antes de que caduque el token oculto. El reconocimiento es la parte fácil; la sesión es donde se rompe casi todo.

Aquí no negocias con un anti-bot moderno como Cloudflare Turnstile, sino con software heredado de juzgados, catastros y registros mercantiles. Abajo tienes qué CAPTCHA usa cada categoría de portal, qué parámetros suben la tasa de acierto, código en Python y Node.js listo para copiar y un diagnóstico para cuando la búsqueda vuelve vacía.

Qué CAPTCHA usa cada tipo de portal

Categoría del portal CAPTCHA habitual Cómo se ve el desafío
Consulta de expedientes judiciales CAPTCHA de texto a medida Cadena alfanumérica de 5–6 caracteres deformada
Registro de la propiedad CAPTCHA matemático "¿Cuánto es 4 + 7?"
Registro mercantil y de sociedades Texto sobre imagen Letras onduladas con ruido de línea
Registro civil reCAPTCHA v2 Selección en cuadrícula de imágenes
Licencias de obra CAPTCHA de texto simple Código numérico de 4 dígitos
Garantías mobiliarias OCR a medida Mayúsculas y minúsculas mezcladas sobre fondo con ruido

Casi todas usan el mismo método de la API: imagen/OCR, con más de 27.500 variantes reconocidas y tiempos por debajo de 0,5 s. Si el portal ya migró, reCAPTCHA v2, reCAPTCHA v3, Cloudflare Turnstile y GeeTest v3 también están cubiertos. hCaptcha y FunCaptcha no son compatibles por ahora, y GeeTest v4 figura como próximamente.

Parámetros de la API que suben la tasa de acierto

Estos CAPTCHA son predecibles: normalmente sabes si el código es numérico, cuántos caracteres tiene o si es una operación aritmética. Pasar ese dato en la tarea es la mejora más barata posible.

Parámetro Valor Cuándo usarlo
method base64 Ya descargaste la imagen como bytes
method post Envías el archivo de imagen directamente
language 0 CAPTCHA de texto con alfabeto latino
numeric 1 El código es solo de dígitos
min_len / max_len Varía La longitud del código es predecible
textinstructions Instrucción libre Operaciones matemáticas o formatos poco habituales

min_len y max_len descartan respuestas imposibles antes de enviar el formulario: en portales que bloquean tras varios fallos, eso vale más que unos milisegundos de latencia.

Consulta de expedientes judiciales en Python

El patrón es siempre el mismo: cargar la página con una Session persistente, localizar la etiqueta img, descargarla con esa misma sesión y enviar el texto resuelto con el resto del formulario.

import requests
import base64
import time
from urllib.parse import urljoin

class PublicRecordsSearcher:
    def __init__(self, api_key):
        self.api_key = api_key
        self.session = requests.Session()
        self.session.headers.update({
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
        })

    def search_court_records(self, portal_url, case_number):
        """Search court records, solving image CAPTCHAs as needed."""
        # Load the search page
        page = self.session.get(f"{portal_url}/search")

        # Extract CAPTCHA image
        captcha_img_url = self._extract_captcha_url(page.text, portal_url)
        if not captcha_img_url:
            # No CAPTCHA on this page
            return self._submit_search(portal_url, case_number)

        # Download and solve CAPTCHA
        img_response = self.session.get(captcha_img_url)
        captcha_text = self._solve_image_captcha(img_response.content)

        # Submit search with solved CAPTCHA
        return self._submit_search(portal_url, case_number, captcha_text)

    def _extract_captcha_url(self, html, base_url):
        from bs4 import BeautifulSoup
        soup = BeautifulSoup(html, "html.parser")

        # Look for common CAPTCHA image patterns
        captcha_img = (
            soup.find("img", {"id": "captchaImage"}) or
            soup.find("img", {"class": "captcha"}) or
            soup.find("img", attrs={"src": lambda s: s and "captcha" in s.lower()})
        )

        if captcha_img and captcha_img.get("src"):
            return urljoin(base_url, captcha_img["src"])
        return None

    def _solve_image_captcha(self, image_bytes):
        img_base64 = base64.b64encode(image_bytes).decode("utf-8")

        resp = requests.post("https://ocr.captchaai.com/in.php", data={
            "key": self.api_key,
            "method": "base64",
            "body": img_base64,
            "json": 1
        })
        task_id = resp.json()["request"]

        for _ in range(30):
            time.sleep(3)
            result = requests.get("https://ocr.captchaai.com/res.php", params={
                "key": self.api_key,
                "action": "get",
                "id": task_id,
                "json": 1
            })
            data = result.json()
            if data["status"] == 1:
                return data["request"]

        raise TimeoutError("CAPTCHA solve timed out")

    def _submit_search(self, portal_url, case_number, captcha_text=None):
        form_data = {"caseNumber": case_number}
        if captcha_text:
            form_data["captcha"] = captcha_text

        response = self.session.post(
            f"{portal_url}/search/results",
            data=form_data
        )
        return response.text

# Usage
searcher = PublicRecordsSearcher("YOUR_API_KEY")
results = searcher.search_court_records(
    "https://courts.example.gov",
    "2024-CV-12345"
)

Fíjate en que la imagen se descarga con self.session.get() y no con un requests.get() suelto: la cookie que emitió la página de búsqueda es la que valida el CAPTCHA.

CAPTCHA matemáticos, el patrón más común en registros de propiedad

Los catastros suelen usar sumas o restas renderizadas como imagen. No hace falta un método especial: se tratan como reconocimiento de texto y se guía la respuesta con textinstructions para que devuelva el resultado, no la operación.

def solve_math_captcha(self, image_bytes):
    """Solve math CAPTCHAs like '4 + 7 = ?'"""
    img_base64 = base64.b64encode(image_bytes).decode("utf-8")

    resp = requests.post("https://ocr.captchaai.com/in.php", data={
        "key": self.api_key,
        "method": "base64",
        "body": img_base64,
        "textinstructions": "solve the math equation and return only the number",
        "json": 1
    })
    task_id = resp.json()["request"]

    # Poll for result
    for _ in range(30):
        time.sleep(3)
        result = requests.get("https://ocr.captchaai.com/res.php", params={
            "key": self.api_key,
            "action": "get",
            "id": task_id,
            "json": 1
        })
        data = result.json()
        if data["status"] == 1:
            return data["request"]

    raise TimeoutError("Math CAPTCHA solve timed out")

Recorrer varios portales desde Node.js

Cuando consultas varios registros a la vez, aísla cada portal en su propio try y devuelve el error como dato en lugar de abortar el lote: un juzgado caído no debería tumbar la búsqueda en el registro mercantil.

class RecordsAggregator {
  constructor(apiKey) {
    this.apiKey = apiKey;
  }

  async searchAcrossPortals(query, portals) {
    const results = [];

    for (const portal of portals) {
      try {
        const data = await this.searchPortal(portal, query);
        results.push({ portal: portal.name, records: data });
      } catch (error) {
        results.push({ portal: portal.name, error: error.message });
      }
    }

    return results;
  }

  async searchPortal(portal, query) {
    const pageResponse = await fetch(portal.searchUrl);
    const html = await pageResponse.text();

    // Check for image CAPTCHA
    const captchaMatch = html.match(/captcha[^"]*\.(?:png|jpg|gif)/i);
    let captchaAnswer = null;

    if (captchaMatch) {
      const imgUrl = new URL(captchaMatch[0], portal.searchUrl).href;
      const imgData = await fetch(imgUrl);
      const buffer = await imgData.arrayBuffer();
      const base64 = Buffer.from(buffer).toString('base64');

      captchaAnswer = await this.solveImageCaptcha(base64);
    }

    // Submit search
    const formData = new URLSearchParams({ q: query });
    if (captchaAnswer) formData.append('captcha', captchaAnswer);

    const response = await fetch(portal.searchUrl, {
      method: 'POST',
      body: formData
    });

    return response.text();
  }

  async solveImageCaptcha(base64Image) {
    const submitResp = await fetch('https://ocr.captchaai.com/in.php', {
      method: 'POST',
      body: new URLSearchParams({
        key: this.apiKey,
        method: 'base64',
        body: base64Image,
        json: '1'
      })
    });

    const { request: taskId } = await submitResp.json();

    for (let i = 0; i < 30; i++) {
      await new Promise(r => setTimeout(r, 3000));
      const result = await fetch(
        `https://ocr.captchaai.com/res.php?key=${this.apiKey}&action=get&id=${taskId}&json=1`
      );
      const data = await result.json();
      if (data.status === 1) return data.request;
    }

    throw new Error('CAPTCHA solve timed out');
  }
}

// Usage
const aggregator = new RecordsAggregator('YOUR_API_KEY');
const results = await aggregator.searchAcrossPortals('Smith LLC', [
  { name: 'State Business Registry', searchUrl: 'https://sos.example.gov/search' },
  { name: 'County Court Records', searchUrl: 'https://courts.example.gov/search' }
]);

Escenario: verificar un proveedor en tres registros distintos

Una consultora en Ciudad de México verifica a un proveedor antes de firmar: la matriz en el registro mercantil español, un expediente judicial estatal en México y una inscripción provincial en Argentina. Tres portales, tres CAPTCHA distintos, unas 120 consultas al día en temporada alta.

Ese volumen cabe de sobra en el plan BASIC ($15/mes, 5 threads). Si el equipo pasa a un barrido nocturno de miles de expedientes, STANDARD ($30/mes, 15 threads) o ADVANCE ($90/mes, 50 threads) amplían la concurrencia sin tocar el código: se factura por thread simultáneo, con resoluciones ilimitadas dentro del plan, así que el coste mensual en USD es previsible aunque el volumen diario baile.

Una nota que no es opcional: consulta solo información de acceso público y respeta los términos de cada portal y la normativa de protección de datos aplicable (RGPD y LOPDGDD en España, LFPDPPP en México) antes de almacenar o cruzar los datos.

Cuando la búsqueda vuelve vacía: diagnóstico

Síntoma Causa habitual Qué hacer
La imagen del CAPTCHA devuelve 403 Falta la cookie de sesión Carga primero la página de búsqueda y descarga la imagen con la misma sesión
El texto resuelto se rechaza Imagen de muy baja calidad Preprocesa la imagen y acota la longitud con min_len / max_len
El CAPTCHA se regenera al enviar El token oculto del formulario caducó Extrae los campos ocultos en la misma petición que la imagen
La búsqueda responde sin resultados Se perdieron las cookies en la redirección Usa allow_redirects=True y reutiliza la sesión en todo el flujo

Si la culpa es de la imagen, subir el contraste y limpiar el ruido cambia el resultado: la guía de preprocesamiento de imágenes detalla las técnicas.

Preguntas frecuentes

¿Cuántos threads necesito para consultar varios portales en paralelo?

Un thread es un CAPTCHA en curso, no un portal. Con tres portales y una consulta simultánea en cada uno sobran los 5 threads de BASIC ($15/mes); a dos consultas por portal ya necesitas 6 y toca STANDARD ($30/mes, 15 threads). Para barridos masivos, ADVANCE ($90/mes, 50 threads).

¿Qué hago si el portal cambia el CAPTCHA cada vez que envío el formulario?

Casi siempre es el token oculto del formulario, no el CAPTCHA. Extrae los campos hidden en la misma petición en la que descargas la imagen y envíalo todo junto; si tardas más de un par de minutos, recarga la página.

¿CaptchaAI resuelve el reCAPTCHA v2 de los registros civiles?

Sí. reCAPTCHA v2 (incluidas la invisible y Enterprise), reCAPTCHA v3, Cloudflare Turnstile y GeeTest v3 se resuelven con la misma clave API. hCaptcha y FunCaptcha no son compatibles por ahora.

¿Puedo automatizar consultas en portales públicos sin problemas legales?

Depende del portal: cada sede electrónica tiene sus propias condiciones de uso y límites de frecuencia. Consulta solo datos abiertos, mantén una frecuencia razonable y revisa la normativa de protección de datos antes de almacenar resultados.

Próximos pasos

Obtén tu clave API de CaptchaAI y resuelve los CAPTCHA de imagen de los portales gubernamentales desde tu propio código.


Siguientes pasos

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