Un pipeline de vérification publicitaire s'arrête net dès qu'un éditeur affiche un CAPTCHA : la page ne se charge plus, la mesure de campagne saute et le rapport se retrouve troué au moment où vos annonceurs attendent des preuves de diffusion. CaptchaAI résout ce défi automatiquement, pour que le contrôle des placements, de la brand safety et de la visibilité continue de tourner sur des milliers de pages. L'enjeu n'est pas de « visiter des sites » : c'est de garder une collecte fiable quand chaque éditeur ajoute sa propre couche de protection anti-bot.
Ce que vous contrôlez, et pourquoi un CAPTCHA surgit
La vérification publicitaire consiste à visiter en continu des pages d'éditeurs pour vérifier où, à côté de quoi et pour qui une annonce s'affiche. Le tableau ci-dessous relie chaque contrôle métier à la raison technique pour laquelle il déclenche un défi.
| Contrôle | Description | Pourquoi un CAPTCHA peut apparaître |
|---|---|---|
| Placement publicitaire | L'annonce est-elle visible au bon endroit sur la page ? | Les visites automatisées en série ressemblent à un comportement bot |
| Brand safety | L'annonce apparaît-elle à côté d'un contenu sensible ou problématique ? | Le balayage massif d'URL ressemble à du scraping intensif |
| Visibilité | L'annonce a-t-elle réellement été chargée et affichée ? | Les navigateurs headless et sessions uniformes sont vite détectés |
| Ciblage géographique | La bonne création est-elle servie dans la bonne région ? | Les proxys et changements de localisation déclenchent plus souvent des contrôles |
| Veille concurrentielle | Quelles annonces diffusent les concurrents sur quels supports ? | Le volume de consultations augmente le risque de challenge |
Plus l'opération grandit, plus le vrai sujet devient la fiabilité du pipeline. Un contrôle qui échoue sans laisser de trace vaut moins qu'un échec explicite : votre équipe ad ops doit pouvoir distinguer « annonce absente » de « page bloquée par un CAPTCHA ». Pour chaque page, journalisez au minimum :
- l'URL et l'horodatage du contrôle ;
- si un défi a été rencontré et résolu, et de quel type ;
- les réseaux publicitaires détectés dans le rendu ;
- le proxy et la région utilisés, pour rejouer le contrôle à l'identique.
Détecter, résoudre et journaliser chaque page
Le cœur du pipeline tient en une boucle : récupérer la page, repérer une clé de site si un défi est présent, envoyer la tâche à CaptchaAI, puis rejouer la requête avec le token obtenu. Le résultat n'est pas un simple succès ou échec : on enregistre si un CAPTCHA a été résolu, quels réseaux publicitaires ont été détectés et si un signal de risque a été relevé.
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)
Rendre le rapport exploitable
Ce niveau de détail rend le rapport exploitable : les équipes ad ops, conformité et brand safety y lisent non seulement le verdict, mais aussi le contexte qui l'explique. Côté RGPD, la logique reste sobre : vous inspectez des balises publicitaires et du contenu de page, pas des données personnelles. Gardez cette discipline dans vos logs et ne collectez que ce dont le contrôle a réellement besoin.
Cohérence multi-marchés : aligner proxy et session
Prenons une campagne diffusée en France, en Belgique et en Suisse romande, à vérifier depuis vos workers hébergés sur OVHcloud ou en région eu-west-3 (Paris). Chaque marché exige un proxy local pour voir la bonne création, et chaque changement de géolocalisation augmente la probabilité d'un défi. En verrouillant un couple proxy + session par marché et en passant ce même proxy à CaptchaAI lors de la résolution, vous obtenez des captures cohérentes : l'annonce vue à Genève correspond bien à ce que verrait un internaute suisse, pas à un rendu français validé par erreur.
Éditeurs derrière Cloudflare : Turnstile et pages de challenge
De nombreux grands éditeurs européens passent par Cloudflare. Vous devez donc traiter à la fois les widgets Turnstile légers et les pages de challenge plus lourdes, servies avec un code 403 tant que la session n'est pas validée.
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 cf_clearance and use same proxy
return data
return resp
Le point sensible est la cohérence du contexte. Sur des protections strictes, une incohérence entre la session navigateur et le solveur suffit souvent à invalider le résultat.
Gardez le même proxy entre la navigation et la résolution du challenge : c'est la règle qui fait le plus de différence sur les éditeurs derrière Cloudflare.
FAQ
CaptchaAI prend-il en charge Turnstile et les pages de challenge Cloudflare ?
Oui. CaptchaAI résout Cloudflare Turnstile ainsi que les pages Cloudflare Challenge, en plus de reCAPTCHA v2 et v3, GeeTest v3 et les CAPTCHA image/OCR. En revanche, hCaptcha et FunCaptcha ne sont pas pris en charge : prévoyez une autre stratégie pour les éditeurs qui les utilisent.
Comment rester sobre côté RGPD lors d'une collecte à grande échelle ?
Limitez la collecte aux éléments publicitaires et au contexte de page, sans stocker de données personnelles superflues. La résolution du défi ne change rien à vos obligations : documentez la finalité, minimisez ce que vos logs conservent et vérifiez vos propres obligations RGPD avant d'élargir le périmètre.
Faut-il un navigateur réel pour vérifier les formats vidéo ?
Pour le display et le natif, l'approche par requêtes suffit à confirmer la présence des balises. Pour la vidéo et les créations plus dynamiques, prévoyez un vrai rendu avec Selenium ou Playwright afin de confirmer la lecture et la visibilité effective.
Combien de threads faut-il pour vérifier des milliers de pages ?
Cela dépend de votre parallélisme, pas d'un quota de résolutions. Un thread traite un CAPTCHA à la fois puis enchaîne le suivant ; le plan BASIC ($15/mois, 5 threads) convient à un pilote, tandis qu'ADVANCE ($90/mois, 50 threads) absorbe une vérification continue à fort volume, avec des résolutions illimitées par thread.
Guides connexes
- Scraper des sites protégés par CAPTCHA
- Gérer les CAPTCHA en navigateur headless
- Améliorer le taux de réussite avec des proxys mobiles
- Erreurs Cloudflare Challenge et corrections
Pour une vérification publicitaire à grande échelle avec des journaux exploitables, obtenez votre clé CaptchaAI et construisez un pipeline qui gère le défi comme la trace de chaque contrôle.