Casos de Uso

Flujos de trabajo de verificación de anuncios con manejo de CAPTCHA

Un equipo de ad ops que audita dónde aparecen sus anuncios carga cientos de páginas al día. Cuando una responde con un CAPTCHA, el crawler se detiene y el informe de brand safety queda incompleto. CaptchaAI resuelve ese desafío dentro del propio flujo: recibe el CAPTCHA, devuelve el token y la verificación continúa.

Piensa en una agencia de Madrid que verifica campañas en España, México y Argentina. Cada mercado tiene sus editores, muchos del estilo de MercadoLibre, y varios protegen sus páginas con Cloudflare o reCAPTCHA. Con resolución automática, el mismo script recorre los tres mercados y marca dónde el anuncio falta o queda junto a contenido no apto, respetando los términos de servicio y la normativa de datos aplicable (RGPD, LFPDPPP).

Qué comprueba la verificación de anuncios y por qué se bloquea

Un flujo de verificación reúne varias comprobaciones sobre cada página, y casi todas se rompen en cuanto aparece un desafío. Esta tabla resume qué se mira en cada visita y por qué el CAPTCHA lo interrumpe:

Comprobación Descripción Por qué el CAPTCHA la bloquea
Colocación del anuncio ¿Aparece en la mitad superior? Las visitas automáticas activan la detección de bots
Brand safety Sin anuncios junto a contenido dañino La consulta masiva de URL parece scraping
Visibilidad ¿El anuncio era visible? Cloudflare marca los navegadores headless
Segmentación geográfica El anuncio correcto en cada región El tráfico por proxy dispara CAPTCHA
Vigilancia de la competencia ¿Qué anuncios muestran los rivales? Búsquedas de anuncios de gran volumen

Cómo encaja CaptchaAI en el pipeline de verificación

El patrón se repite independientemente del editor o del tipo de anuncio:

  1. Carga la página del editor y detecta si hay un CAPTCHA (por ejemplo, el data-sitekey de reCAPTCHA o el widget de Turnstile).
  2. Envía el desafío a CaptchaAI con el método correspondiente y la URL de la página.
  3. Consulta el resultado hasta que el token está listo; mientras tanto, la respuesta es CAPCHA_NOT_READY.
  4. Reenvía el formulario o la petición con el token para acceder al contenido real.
  5. Analiza el HTML: etiquetas de anuncios, posición, marca y señales de brand safety.

Con este bucle, el CAPTCHA deja de ser un muro y pasa a ser un paso más del pipeline, igual que una redirección o una cookie de sesión.

Implementación en Python

El script recorre las URL de editores, detecta el data-sitekey y pide a CaptchaAI el reCAPTCHA. solve_captcha envía la tarea a in.php y consulta res.php hasta que está listo; luego reenvía el formulario con el token:

import requests
import time
import re
import json
import os
from datetime import datetime

API_KEY = os.environ["CAPTCHAAI_API_KEY"]


def solve_captcha(method, params):
    params["key"] = API_KEY
    params["method"] = method

    resp = requests.get("https://ocr.captchaai.com/in.php", params=params)
    if not resp.text.startswith("OK|"):
        raise Exception(resp.text)

    task_id = resp.text.split("|")[1]
    for _ in range(60):
        time.sleep(5)
        result = requests.get("https://ocr.captchaai.com/res.php", params={
            "key": API_KEY, "action": "get", "id": task_id,
        })
        if result.text == "CAPCHA_NOT_READY":
            continue
        if result.text.startswith("OK|"):
            return result.text.split("|", 1)[1]
        raise Exception(result.text)
    raise TimeoutError()


def verify_ad_placement(url, session):
    """Verify ad placement on a publisher page."""
    resp = session.get(url)

    # Solve CAPTCHA if present
    match = re.search(r'data-sitekey=["\']([A-Za-z0-9_-]+)["\']', resp.text)
    if match:
        token = solve_captcha("userrecaptcha", {
            "googlekey": match.group(1),
            "pageurl": url,
        })
        resp = session.post(url, data={"g-recaptcha-response": token})

    html = resp.text

    # Check for ad elements
    result = {
        "url": url,
        "timestamp": datetime.utcnow().isoformat(),
        "ads_found": [],
        "brand_safety": True,
        "captcha_solved": match is not None,
    }

    # Detect ad tags
    ad_patterns = [
        (r'googletag\.pubads', "Google Ad Manager"),
        (r'doubleclick\.net', "DFP/DoubleClick"),
        (r'ad\.doubleclick', "DoubleClick"),
        (r'amazon-adsystem', "Amazon Ads"),
        (r'criteo\.com/.*\.js', "Criteo"),
    ]

    for pattern, name in ad_patterns:
        if re.search(pattern, html):
            result["ads_found"].append(name)

    # Brand safety check — flag problematic content
    safety_keywords = [
        "violence", "hate speech", "explicit",
        "gambling", "illegal",
    ]
    page_text = re.sub(r'<[^>]+>', '', html).lower()
    for keyword in safety_keywords:
        if keyword in page_text:
            result["brand_safety"] = False
            break

    return result


def run_verification(urls, output_file="verification_report.json"):
    """Run ad verification across multiple publisher URLs."""
    session = requests.Session()
    session.headers["User-Agent"] = (
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
        "AppleWebKit/537.36 Chrome/120.0.0.0"
    )

    results = []
    for i, url in enumerate(urls):
        try:
            result = verify_ad_placement(url, session)
            results.append(result)
            ads = ", ".join(result["ads_found"]) or "None"
            safe = "SAFE" if result["brand_safety"] else "UNSAFE"
            print(f"  [{i+1}/{len(urls)}] {url}: {ads} [{safe}]")
        except Exception as e:
            results.append({
                "url": url,
                "error": str(e),
                "timestamp": datetime.utcnow().isoformat(),
            })
            print(f"  [{i+1}/{len(urls)}] {url}: ERROR - {e}")

        time.sleep(2)

    with open(output_file, "w") as f:
        json.dump(results, f, indent=2)

    # Summary
    total = len(results)
    safe = sum(1 for r in results if r.get("brand_safety"))
    captchas = sum(1 for r in results if r.get("captcha_solved"))
    errors = sum(1 for r in results if "error" in r)

    print(f"\n  Total: {total} | Safe: {safe} | CAPTCHAs solved: {captchas} | Errors: {errors}")

    return results


# Publisher URLs to verify
publisher_urls = [
    "https://publisher1.com/article/tech-news",
    "https://publisher2.com/sports/latest",
    "https://publisher3.com/finance/markets",
]

run_verification(publisher_urls)

Escalar con editores detrás de Cloudflare

Muchos editores premium usan Cloudflare. El código cubre los dos casos: un widget Turnstile o un 403 con el Cloudflare Challenge completo. CaptchaAI resuelve ambos y continúa la petición:

def handle_cloudflare(url, session):
    """Handle Cloudflare-protected publisher pages."""
    resp = session.get(url)

    if "cf-turnstile" in resp.text:
        match = re.search(r'data-sitekey=["\']([^"\']+)', resp.text)
        if match:
            token = solve_captcha("turnstile", {
                "sitekey": match.group(1),
                "pageurl": url,
            })
            return session.post(url, data={
                "cf-turnstile-response": token,
            })

    if resp.status_code == 403 and "cf-browser-verification" in resp.text:
        data = solve_captcha("cloudflare_challenge", {
            "pageurl": url,
            "proxy": "user:pass@proxy:port",
            "proxytype": "HTTP",
        })
        # Parse qa_validation_cookie and use same proxy
        return data

    return resp

Errores y reintentos a gran volumen

Cuando recorres miles de URL, los fallos son normales y el pipeline tiene que asumirlos sin detenerse:

  • CAPCHA_NOT_READY: no es un error; significa que el token aún se está resolviendo. Sigue consultando res.php cada pocos segundos.
  • Tiempo de espera agotado: si un desafío tarda demasiado, márcalo, descártalo y pasa a la siguiente URL en lugar de bloquear el lote entero.
  • Reintentos con retroceso exponencial: ante errores de red o respuestas 5xx del editor, reintenta con esperas crecientes antes de darte por vencido.
  • URL duplicadas: normaliza y deduplica la lista de entrada para no gastar threads resolviendo la misma página dos veces.

Registra cada resultado —resuelto, fallido o sin CAPTCHA— para poder auditar la cobertura de la campaña después.

Buenas prácticas para verificación a gran volumen

  • Ajusta los threads a tu plan: la concurrencia real la marca tu número de threads, así que dimensiona el plan según cuántas páginas necesites verificar en paralelo.
  • Respeta el ritmo del editor: introduce una pausa entre peticiones para no saturar el sitio ni provocar más bloqueos.
  • Guarda evidencias: almacena el HTML o una captura de cada comprobación para documentar dónde apareció el anuncio.
  • Verifica solo lo que puedes: revisa páginas públicas que tengas permiso de auditar y respeta los términos de servicio y la normativa de protección de datos de cada mercado.

Preguntas frecuentes

¿Qué tipos de CAPTCHA aparecen en las páginas de editores y cuáles resuelve CaptchaAI?

Lo más habitual es reCAPTCHA v2 y v3, Cloudflare Turnstile y el Challenge completo, y algún CAPTCHA de imagen u OCR. CaptchaAI resuelve todos esos tipos con la misma API.

¿Necesito un navegador headless o me basta con la API?

Para anuncios gráficos y nativos basta con la API y solicitudes HTTP: más ligero y escala mejor. Reserva el navegador headless (Selenium o Playwright) para lo que exige renderizado real, como los anuncios de vídeo.

¿Cuánto cuesta verificar a gran volumen?

El precio se basa en threads, no en resoluciones: cada plan incluye resoluciones ilimitadas por thread. BASIC ($15/mes, 5 threads) cubre auditorías puntuales; para muchos editores en paralelo, ADVANCE ($90/mes, 50 threads) da mucha más concurrencia.

¿Me sirve la extensión de navegador de CaptchaAI para esto?

La extensión resuelve CAPTCHA con un clic mientras navegas, ideal para revisiones manuales puntuales. Para verificación automatizada a escala, integra la API como en los ejemplos de arriba.

Guías relacionadas

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