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:
- Carga la página del editor y detecta si hay un CAPTCHA (por ejemplo, el
data-sitekeyde reCAPTCHA o el widget de Turnstile). - Envía el desafío a CaptchaAI con el método correspondiente y la URL de la página.
- Consulta el resultado hasta que el token está listo; mientras tanto, la respuesta es
CAPCHA_NOT_READY. - Reenvía el formulario o la petición con el token para acceder al contenido real.
- 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 consultandores.phpcada 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.