Tutoriales

Registros de auditoría para resolución CAPTCHA: trazabilidad y cumplimiento

Cuando tu organización resuelve miles de CAPTCHA diariamente, necesitas un registro de cada solicitud. Los registros de auditoría responden preguntas como: ¿Quién desencadenó esta resolución? ¿Para qué sitio fue? ¿Cuánto costó? ¿Cuándo ocurrió? Esta guía muestra cómo implementar un registro de auditoría integral para operaciones de CaptchaAI.

Qué registrar

Cada solución CAPTCHA debe registrar:

campo Propósito Ejemplo
timestamp Cuando se hizo la solicitud 2026-04-04T14:30:00Z
request_id Identificador único para esta solución. uuid4()
captcha_type Método CAPTCHA utilizado userrecaptcha
target_site La URL de la página se está resolviendo https://staging.example.com/qa-login
task_id ID de tarea CaptchaAI 73829451
status Resultado solved, failed, timeout
solve_time_ms Tiempo desde el envío hasta el resultado 18432
error_code Error si falla ERROR_CAPTCHA_UNSOLVABLE
initiator ¿Quién o qué desencadenó la solución? scraper-job-42
cost Costo estimado 0.003

No registre: claves API, tokens CAPTCHA (son temporales) o información de identificación personal de los sitios de destino.

Implementación de Python

# audit_solver.py
import os
import uuid
import time
import json
import logging
from datetime import datetime, timezone
import requests

API_KEY = os.environ.get("CAPTCHAAI_KEY", "YOUR_API_KEY")

# Configure audit logger — separate from application logs
audit_logger = logging.getLogger("captcha_audit")
audit_logger.setLevel(logging.INFO)

# File handler with rotation
from logging.handlers import RotatingFileHandler
handler = RotatingFileHandler(
    "captcha_audit.jsonl",
    maxBytes=50_000_000,  # 50 MB per file
    backupCount=10,
)
handler.setFormatter(logging.Formatter("%(message)s"))
audit_logger.addHandler(handler)

def log_audit(record):
    """Write a structured audit record."""
    audit_logger.info(json.dumps(record, default=str))

def solve_with_audit(sitekey, pageurl, captcha_type="userrecaptcha",
                      initiator="unknown"):
    """Solve a CAPTCHA with full audit logging."""
    request_id = str(uuid.uuid4())
    start = time.time()

    audit_record = {
        "request_id": request_id,
        "timestamp": datetime.now(timezone.utc).isoformat(),
        "captcha_type": captcha_type,
        "target_site": pageurl,
        "initiator": initiator,
        "status": "submitted",
    }

    session = requests.Session()

    try:
        # Submit
        resp = session.get("https://ocr.captchaai.com/in.php", params={
            "key": API_KEY,
            "method": captcha_type,
            "googlekey": sitekey,
            "pageurl": pageurl,
            "json": "1",
        })
        result = resp.json()

        if result.get("status") != 1:
            audit_record.update({
                "status": "submit_failed",
                "error_code": result.get("request"),
                "solve_time_ms": int((time.time() - start) * 1000),
            })
            log_audit(audit_record)
            return None

        task_id = result["request"]
        audit_record["task_id"] = task_id

        # Poll
        time.sleep(15)
        for _ in range(25):
            poll = session.get("https://ocr.captchaai.com/res.php", params={
                "key": API_KEY, "action": "get",
                "id": task_id, "json": "1",
            })
            poll_result = poll.json()

            if poll_result.get("status") == 1:
                solve_time = int((time.time() - start) * 1000)
                audit_record.update({
                    "status": "solved",
                    "solve_time_ms": solve_time,
                    "cost_estimate": 0.003,  # Adjust per your rate
                })
                log_audit(audit_record)
                return poll_result["request"]

            if poll_result.get("request") != "CAPCHA_NOT_READY":
                audit_record.update({
                    "status": "failed",
                    "error_code": poll_result.get("request"),
                    "solve_time_ms": int((time.time() - start) * 1000),
                })
                log_audit(audit_record)
                return None

            time.sleep(5)

        audit_record.update({
            "status": "timeout",
            "solve_time_ms": int((time.time() - start) * 1000),
        })
        log_audit(audit_record)
        return None

    except Exception as e:
        audit_record.update({
            "status": "error",
            "error_code": str(e)[:200],
            "solve_time_ms": int((time.time() - start) * 1000),
        })
        log_audit(audit_record)
        raise

# Usage
token = solve_with_audit(
    sitekey="6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-",
    pageurl="https://www.google.com/recaptcha/api2/demo",
    initiator="price-scraper-v2",
)

Salida del registro de auditoría (formato JSONL)

{"request_id":"a1b2c3d4-...","timestamp":"2026-04-04T14:30:00+00:00","captcha_type":"userrecaptcha","target_site":"https://www.google.com/recaptcha/api2/demo","initiator":"price-scraper-v2","status":"solved","task_id":"73829451","solve_time_ms":18432,"cost_estimate":0.003}

Implementación de JavaScript

// audit_solver.js
const fs = require('fs');
const { v4: uuidv4 } = require('uuid');
const axios = require('axios');

const API_KEY = process.env.CAPTCHAAI_KEY || 'YOUR_API_KEY';
const AUDIT_FILE = 'captcha_audit.jsonl';

function logAudit(record) {
  fs.appendFileSync(AUDIT_FILE, JSON.stringify(record) + '\n');
}

async function solveWithAudit(sitekey, pageurl, initiator = 'unknown') {
  const requestId = uuidv4();
  const start = Date.now();
  const record = {
    request_id: requestId,
    timestamp: new Date().toISOString(),
    captcha_type: 'userrecaptcha',
    target_site: pageurl,
    initiator,
    status: 'submitted',
  };

  try {
    const submit = await axios.get('https://ocr.captchaai.com/in.php', {
      params: {
        key: API_KEY, method: 'userrecaptcha',
        googlekey: sitekey, pageurl, json: '1',
      },
    });

    if (submit.data.status !== 1) {
      record.status = 'submit_failed';
      record.error_code = submit.data.request;
      record.solve_time_ms = Date.now() - start;
      logAudit(record);
      return null;
    }

    record.task_id = submit.data.request;
    await new Promise(r => setTimeout(r, 15000));

    for (let i = 0; i < 25; i++) {
      const poll = await axios.get('https://ocr.captchaai.com/res.php', {
        params: { key: API_KEY, action: 'get', id: submit.data.request, json: '1' },
      });

      if (poll.data.status === 1) {
        record.status = 'solved';
        record.solve_time_ms = Date.now() - start;
        record.cost_estimate = 0.003;
        logAudit(record);
        return poll.data.request;
      }
      if (poll.data.request !== 'CAPCHA_NOT_READY') {
        record.status = 'failed';
        record.error_code = poll.data.request;
        record.solve_time_ms = Date.now() - start;
        logAudit(record);
        return null;
      }
      await new Promise(r => setTimeout(r, 5000));
    }

    record.status = 'timeout';
    record.solve_time_ms = Date.now() - start;
    logAudit(record);
    return null;
  } catch (e) {
    record.status = 'error';
    record.error_code = e.message.slice(0, 200);
    record.solve_time_ms = Date.now() - start;
    logAudit(record);
    throw e;
  }
}

Consulta de registros de auditoría

Resumen diario

import json
from collections import Counter
from datetime import date

def daily_summary(log_file, target_date=None):
    """Generate a daily summary from audit logs."""
    target = target_date or date.today().isoformat()
    statuses = Counter()
    total_cost = 0
    solve_times = []

    with open(log_file) as f:
        for line in f:
            record = json.loads(line)
            if record["timestamp"].startswith(target):
                statuses[record["status"]] += 1
                total_cost += record.get("cost_estimate", 0)
                if record.get("solve_time_ms"):
                    solve_times.append(record["solve_time_ms"])

    print(f"Date: {target}")
    print(f"Total requests: {sum(statuses.values())}")
    print(f"Statuses: {dict(statuses)}")
    print(f"Estimated cost: ${total_cost:.2f}")
    if solve_times:
        print(f"Median solve time: {sorted(solve_times)[len(solve_times)//2]}ms")

daily_summary("captcha_audit.jsonl")

Retención y almacenamiento

Volumen Tamaño del registro diario Almacenamiento mensual Recomendación
100 resuelve/day ~30 KB ~1MB archivo local
1.000 soluciones/day ~300 KB ~10MB Archivo local + rotación
10.000 soluciones/day ~3MB ~100MB Enviar al agregador de registros
100.000 soluciones/day ~30MB ~1GB Registro centralizado (ELK, Datadog)

Solución de problemas

Problema causa Solución
El archivo de registro crece demasiado No hay rotación configurada Utilice RotatingFileHandler o logrotate
Faltan registros de auditoría Excepción antes de iniciar sesión Iniciar sesión en el bloque finally
Escrituras lentas a alto volumen Archivo sincrónico I/O Utilice escrituras de archivos asíncronos o búfer
Marcas de tiempo inconsistentes Desviación del reloj del sistema Utilice NTP; iniciar sesiónUTC

Preguntas frecuentes

¿Debo registrar el token CAPTCHA en la pista de auditoría?

No. Los tokens son temporales (caducan en 60 a 300 segundos) y no tienen valor de auditoría. Registrarlos aumenta el almacenamiento sin ningún beneficio.

¿Puedo utilizar registros de auditoría para la conciliación de facturación?

Sí. Compare los totales de su registro de auditoría con el panel de uso de CaptchaAI para verificar la precisión de la facturación.

¿Qué período de retención debo establecer?

90 días es el estándar para los registros de auditoría operativa. Para el registro basado en el cumplimiento, consulte los requisitos de su industria (SOC 2, GDPR, HIPAA).

Artículos relacionados

  • Seguimiento de resolución de Captcha sin servidor de Dynamodb

Añade responsabilidad a cada resolución CAPTCHA: obtén tu API key de CaptchaAI.

Guías relacionadas:

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