Use Cases

Agrégation d'actualités et de médias avec gestion CAPTCHA

Sur un panel de cent sources de presse, trois protections expliquent presque toutes les collectes qui échouent : Cloudflare Turnstile sur les grands quotidiens, reCAPTCHA v2 sur les reprises de dépêches, reCAPTCHA v3 sur les pages abonnés. La réponse tient en deux gestes : espacer les passages pour ne pas réveiller les protections, puis envoyer à l'API CaptchaAI les défis qui se déclenchent quand même. Ce guide donne la cadence, le code Python, le dimensionnement des threads et les limites juridiques à poser avant de lancer une veille.


Où les CAPTCHA apparaissent dans la presse en ligne

La protection n'est jamais uniforme sur un même site : la page d'accueil passe souvent, alors que la page article ou les archives déclenchent un défi dès la deuxième requête. Cartographiez vos sources : le type de CAPTCHA détermine la method envoyée à l'API.

Type de source CAPTCHA Déclencheur Contenu visé
Grands quotidiens nationaux Cloudflare Turnstile Trafic automatisé Articles, titres
Reprises de dépêches d'agence reCAPTCHA v2 Accès en volume Fil chaud
Titres réservés aux abonnés reCAPTCHA v3 Score de session Articles premium
Presse quotidienne régionale reCAPTCHA v2 Limitation de débit Actualités locales
Agrégateurs et portails Cloudflare Challenge Scraping détecté Flux agrégés
Sites de communiqués CAPTCHA image (OCR) Page de téléchargement Contenu RP

CaptchaAI prend en charge reCAPTCHA v2 et v3, Cloudflare Turnstile, Cloudflare Challenge, GeeTest v3 et les CAPTCHA image. hCaptcha et FunCaptcha ne le sont pas : une source qui en est équipée se traite hors automatisation.


Cadrer le périmètre avant d'écrire une ligne de code

Une revue de presse automatisée se cadre d'abord juridiquement. Un cabinet de relations publiques bruxellois qui suit quarante titres francophones n'a pas besoin du texte intégral : titre, URL, date, signature et extrait court suffisent au rapport quotidien — et cette collecte réduite appelle moins de pages, donc rencontre moins de défis.

Trois points à trancher en amont :

  • Droit voisin des éditeurs de presse. Dans l'Union européenne, la reprise d'extraits est encadrée : citez court, liez vers la source, n'archivez le texte intégral que si votre licence le permet.
  • RGPD. Minimisez les données personnelles conservées, documentez la finalité de la veille et fixez une durée de rétention.
  • Conditions d'utilisation et robots.txt. Une source qui interdit la collecte automatisée sort du panel.

Fixer une cadence de collecte qui ne déclenche rien

Le défi CAPTCHA le plus économique reste celui qui ne s'affiche pas : moitié moins de passages, à peu près moitié moins de résolutions consommées.

Type de source Cadence raisonnable Pourquoi
Grands médias généralistes Plusieurs fois par heure Les titres tombent vite, la pression reste contrôlée
Sources spécialisées ou B2B Quelques passages par jour Rythme éditorial nettement plus lent
Paywalls et sources sensibles Cadence prudente et ciblée Protections plus précoces, coût plus élevé
Veille par mot-clé large Lots étalés dans la journée Évite les pointes de trafic et les défis inutiles

Ajoutez un délai de 3 à 5 secondes entre deux sources, une rotation de proxys et un User-Agent cohérent. Sur une instance OVHcloud ou Scaleway, répartissez les lots dans la journée plutôt que de tout lancer à 6 h : la pointe simultanée est le signal qui réveille Cloudflare Turnstile.


Un agrégateur d'actualités en Python

La classe ci-dessous récupère les titres d'une rubrique, détecte un défi via les marqueurs data-sitekey, g-recaptcha et cf-turnstile, puis rejoue la requête avec le token obtenu. Celui-ci part dans le champ attendu : cf-turnstile-response pour Turnstile, g-recaptcha-response pour reCAPTCHA. Remplacez YOUR_API_KEY par votre clé et adaptez les sélecteurs CSS source par source.

import requests
import time
import re
from bs4 import BeautifulSoup
from datetime import datetime
import json

CAPTCHAAI_KEY = "YOUR_API_KEY"
CAPTCHAAI_URL = "https://ocr.captchaai.com"


def solve_captcha(method, sitekey, pageurl, **kwargs):
    data = {
        "key": CAPTCHAAI_KEY, "method": method,
        "googlekey": sitekey, "pageurl": pageurl, "json": 1,
    }
    data.update(kwargs)
    resp = requests.post(f"{CAPTCHAAI_URL}/in.php", data=data)
    task_id = resp.json()["request"]
    for _ in range(60):
        time.sleep(5)
        result = requests.get(f"{CAPTCHAAI_URL}/res.php", params={
            "key": CAPTCHAAI_KEY, "action": "get",
            "id": task_id, "json": 1,
        })
        r = result.json()
        if r["request"] != "CAPCHA_NOT_READY":
            return r["request"]
    raise TimeoutError("Timeout")


class NewsAggregator:
    def __init__(self, proxy=None):
        self.session = requests.Session()
        if proxy:
            self.session.proxies = {"http": proxy, "https": proxy}
        self.session.headers.update({
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
            "AppleWebKit/537.36 Chrome/126.0.0.0 Safari/537.36",
            "Accept-Language": "en-US,en;q=0.9",
        })

    def collect_headlines(self, source_url, section=None):
        """Collect headlines from a news source."""
        url = f"{source_url}/{section}" if section else source_url
        resp = self.session.get(url, timeout=30)

        if self._has_captcha(resp.text):
            resp = self._solve_and_retry(resp.text, url)

        soup = BeautifulSoup(resp.text, "html.parser")
        articles = []

        for item in soup.select("article, .story, .headline-item, h2 a, h3 a"):
            link = item if item.name == "a" else item.select_one("a")
            if link:
                articles.append({
                    "title": link.get_text(strip=True),
                    "url": self._abs_url(source_url, link.get("href", "")),
                    "source": source_url,
                    "collected_at": datetime.now().isoformat(),
                })

        return articles

    def get_article(self, article_url):
        """Fetch full article content."""
        resp = self.session.get(article_url, timeout=30)

        if self._has_captcha(resp.text):
            resp = self._solve_and_retry(resp.text, article_url)

        soup = BeautifulSoup(resp.text, "html.parser")

        # Remove unwanted elements
        for tag in soup.select("script, style, nav, footer, .ad, .sidebar"):
            tag.decompose()

        content_el = soup.select_one(
            "article, .article-body, .story-body, .entry-content"
        )

        return {
            "url": article_url,
            "title": self._text(soup, "h1, .article-title"),
            "author": self._text(soup, ".author, .byline, [rel='author']"),
            "date": self._text(soup, "time, .publish-date, .article-date"),
            "content": content_el.get_text(separator="\n", strip=True) if content_el else "",
            "word_count": len(content_el.get_text().split()) if content_el else 0,
        }

    def aggregate_sources(self, sources, max_articles_per=20):
        """Aggregate headlines across multiple sources."""
        all_articles = []

        for source in sources:
            try:
                articles = self.collect_headlines(source["url"], source.get("section"))
                all_articles.extend(articles[:max_articles_per])
                print(f"{source['name']}: {len(articles)} headlines")
            except Exception as e:
                print(f"{source['name']}: Error - {e}")
            time.sleep(3)

        return all_articles

    def _has_captcha(self, html):
        return any(tag in html.lower() for tag in [
            'data-sitekey', 'g-recaptcha', 'cf-turnstile',
        ])

    def _solve_and_retry(self, html, url):
        match = re.search(r'data-sitekey="([^"]+)"', html)
        if not match:
            return self.session.get(url)
        sitekey = match.group(1)
        if 'cf-turnstile' in html:
            token = solve_captcha("turnstile", sitekey, url)
            return self.session.post(url, data={"cf-turnstile-response": token})
        token = solve_captcha("userrecaptcha", sitekey, url)
        return self.session.post(url, data={"g-recaptcha-response": token})

    def _text(self, soup, selector):
        el = soup.select_one(selector)
        return el.get_text(strip=True) if el else ""

    def _abs_url(self, base, href):
        if href.startswith("http"):
            return href
        return base.rstrip("/") + "/" + href.lstrip("/")


# Usage
aggregator = NewsAggregator(
    proxy="http://user:pass@residential.proxy.com:5000"
)

sources = [
    {"name": "Tech News A", "url": "https://technews-a.example.com", "section": "latest"},
    {"name": "Business B", "url": "https://business-b.example.com", "section": "tech"},
    {"name": "Industry C", "url": "https://industry-c.example.com"},
]

headlines = aggregator.aggregate_sources(sources)
print(f"Total: {len(headlines)} headlines collected")

Surveiller des mots-clés en continu

La veille par mot-clé se greffe sur l'agrégateur : parcourir les titres collectés, ne garder que les nouveautés, déclencher l'alerte. Le jeu seen_urls évite de notifier deux fois la même dépêche reprise par plusieurs titres ; l'intervalle de trente minutes est un bon point de départ pour une veille de marque.

class NewsMonitor:
    def __init__(self, keywords, sources, proxy=None):
        self.keywords = [kw.lower() for kw in keywords]
        self.aggregator = NewsAggregator(proxy=proxy)
        self.sources = sources
        self.seen_urls = set()

    def scan(self):
        """Scan for articles matching keywords."""
        headlines = self.aggregator.aggregate_sources(self.sources)
        matches = []

        for article in headlines:
            if article["url"] in self.seen_urls:
                continue

            title_lower = article["title"].lower()
            matched_kws = [kw for kw in self.keywords if kw in title_lower]

            if matched_kws:
                article["matched_keywords"] = matched_kws
                matches.append(article)
                self.seen_urls.add(article["url"])

        return matches

    def continuous_monitor(self, interval_min=30):
        """Run continuous monitoring with alerts."""
        while True:
            matches = self.scan()
            if matches:
                print(f"\n=== {len(matches)} new matches found ===")
                for m in matches:
                    print(f"  [{', '.join(m['matched_keywords'])}] {m['title']}")
                    print(f"    {m['url']}")
            else:
                print(f"No new matches at {datetime.now().strftime('%H:%M')}")

            time.sleep(interval_min * 60)


# Monitor for specific topics
monitor = NewsMonitor(
    keywords=["captcha", "bot detection", "web scraping", "automation"],
    sources=sources,
    proxy="http://user:pass@residential.proxy.com:5000",
)
matches = monitor.scan()

Dimensionner vos threads CaptchaAI

La facturation CaptchaAI se fait par thread simultané, résolutions illimitées par thread : ce qui compte n'est pas votre volume mensuel, mais le nombre de défis résolus en parallèle.

L'agence de veille qui suit 40 sources, quatre passages par jour, avec un défi pour dix pages appelées, tient largement dans BASIC ($15/mois, 5 threads). Un observatoire média qui surveille 300 sources en continu, en lots parallèles, sature ces 5 threads et passe sur STANDARD ($30/mois, 15 threads) ; un pipeline temps réel multi-pays se dimensionne sur ADVANCE ($90/mois, 50 threads). La facturation est en dollars US, les tarifs à jour figurent sur la page tarifaire CaptchaAI.

Mesurez avant de monter en gamme : une file d'attente permanente à l'envoi des tâches signale un manque de threads ; des threads inactifs signalent une cadence à ajuster.


Dépannage

Problème Cause probable Correctif
Cloudflare bloque toutes les requêtes Détection du trafic automatisé Proxy résidentiel + User-Agent réaliste
Un paywall à la place de l'article Contenu réservé aux abonnés Détecter le paywall, marquer, passer au suivant
Un défi sur chaque page Adresse IP signalée Faire tourner les proxys, espacer de plus de 5 secondes
Contenu d'article vide Page rendue en JavaScript Basculer sur Selenium ou Puppeteer
Doublons dans le rapport Dépêche reprise par plusieurs titres Dédoublonner par titre et URL canonique
CAPCHA_NOT_READY en boucle Résultat interrogé trop tôt Interroger res.php toutes les 5 secondes, avec timeout

FAQ

Quel plan CaptchaAI choisir pour une veille de 300 sources ?

Regardez le parallélisme, pas le volume. Un scan séquentiel tient dans BASIC ($15/mois, 5 threads) ; des lots parallèles avec texte intégral justifient STANDARD ($30/mois, 15 threads).

CaptchaAI prend-il en charge hCaptcha sur les sites d'information ?

Non — ni hCaptcha, ni FunCaptcha. Les types couverts ici sont reCAPTCHA v2 et v3, Cloudflare Turnstile, Cloudflare Challenge, GeeTest v3 et les CAPTCHA image. GeeTest v4 est annoncé comme à venir.

Que faire quand le contenu de l'article est rendu en JavaScript ?

Le HTML brut renvoyé par requests est alors vide de texte utile. Rendez la page avec Selenium ou Playwright, injectez le token, puis extrayez le contenu une fois le rendu terminé. Réservez ce traitement aux sources qui l'exigent : il coûte cher en ressources.

Faut-il un proxy résidentiel pour toutes les sources ?

Non. Beaucoup de sites spécialisés et de portails de communiqués répondent normalement depuis un proxy datacenter. Réservez les proxys résidentiels, plus coûteux, aux quotidiens protégés par Cloudflare, et mesurez le taux de réussite source par source.


Guides connexes


Pour une revue de presse qui tourne sans surveillance : récupérez votre clé CaptchaAI.

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