Cas d'Usage

Vérification publicitaire sur des sites protégés par CAPTCHA

La vérification publicitaire implique de visiter un grand nombre de pages éditeurs pour contrôler l'emplacement des annonces, leur contexte de diffusion et leur visibilité réelle. Dès que ce trafic devient répétitif, géo-distribué ou piloté par navigateur automatisé, beaucoup de sites affichent un CAPTCHA. CaptchaAI permet de maintenir ce workflow opérationnel au lieu de perdre la mesure des campagnes dès que la première page de challenge apparaît.

Ce que les équipes de vérification contrôlent

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 de vérification s'étend, plus le vrai sujet devient la fiabilité du pipeline : détecter le CAPTCHA, relancer proprement la requête, conserver un proxy cohérent et journaliser ce qui s'est passé sur chaque page vérifiée.

Mise en oeuvre

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)

Dans un vrai pipeline, chaque page ne devrait pas produire uniquement un statut succès ou échec. Il est utile d'enregistrer si un CAPTCHA a été résolu, quels réseaux publicitaires ont été détectés et si des indicateurs de risque ont été relevés. C'est ce qui rend le rapport exploitable pour les équipes ad ops, conformité ou brand safety.

Monter en charge avec des éditeurs protégés par Cloudflare

De nombreux grands éditeurs utilisent Cloudflare. Il faut donc souvent gérer à la fois les widgets Turnstile et les pages de challenge plus lourdes.

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

Si vous vérifiez des campagnes dans plusieurs pays, gardez le même contexte proxy entre la navigation et la résolution du challenge. Sur des protections strictes, une incohérence entre la session navigateur et le solveur suffit souvent à invalider le résultat.

FAQ

Combien de pages peut-on vérifier par heure ?

Le volume réel dépend de la fréquence des CAPTCHA, du temps de rendu de chaque page et du niveau de protection du site. En pratique, un pipeline bien réglé peut traiter des centaines de pages par heure, mais il faut raisonner à partir du taux de challenge observé chez chaque éditeur.

Est-ce adapté à la vérification des publicités vidéo ?

L'approche fonctionne bien pour les formats display et natifs. Pour la vidéo ou les créatives plus dynamiques, il faut souvent un vrai rendu navigateur avec Selenium ou Playwright pour confirmer la lecture et la visibilité.

Comment gérer plusieurs régions ?

Utilisez des proxys situés dans les zones géographiques à vérifier et alignez ce contexte avec votre session de navigation. CaptchaAI accepte les paramètres proxy, ce qui aide à garder un contexte de résolution cohérent avec le ciblage géographique contrôlé.

Guides connexes


Si votre équipe a besoin d'une vérification publicitaire à grande échelle avec des journaux exploitables, obtenez votre clé CaptchaAI et construisez un pipeline qui gère à la fois le challenge et la trace de chaque contrôle.

Les commentaires sont désactivés pour cet article.