Use Cases

Collecte de données de recherche sur les réseaux sociaux avec gestion CAPTCHA

Un pipeline de veille sociale ne casse presque jamais sur le parsing HTML : il casse à la troisième requête, quand la plateforme remplace la page par un défi CAPTCHA. La parade tient en quatre gestes : repérer le défi dans la réponse, envoyer le sitekey à l'API CaptchaAI, réinjecter le token, puis ralentir assez pour ne pas le rappeler aussitôt.

Ce que vous collectez, et ce que vous n'enregistrez pas

Tranchez le périmètre avant la première requête : contenus publics uniquement, et seulement les champs utiles à votre analyse. La minimisation RGPD vaut aussi pour un corpus de recherche. Un laboratoire de sciences sociales à Lyon qui étudie la circulation d'un hashtag n'a que faire des noms d'utilisateur : un identifiant pseudonymisé et un horodatage suffisent. Une agence de veille à Bruxelles garde, elle, l'URL publique du message pour le citer.

Défis et cadence par plateforme

Le type de défi et le rythme tenable changent d'une plateforme à l'autre : plus l'intervalle est respecté, moins vous déclenchez de défis facturés.

Les intervalles ci-dessous reposent sur des mesures observées et des retours d'utilisateurs ; ils varient selon l'environnement, le volume et l'heure de la journée.

Plateforme Type de CAPTCHA Intervalle, durée de session
Instagram reCAPTCHA v2 10 s, 5 min
Facebook reCAPTCHA v2 5 s, 10 min
Twitter/X Cloudflare Turnstile 3 s, 15 min
TikTok reCAPTCHA v3 5 s, 5 min
LinkedIn Cloudflare Challenge 10 s, 5 min
Reddit reCAPTCHA v2 2 s, 30 min

Un collecteur Python qui repère le défi et le résout

La classe ci-dessous garde un User-Agent mobile cohérent, cherche dans chaque réponse un marqueur de défi (data-sitekey, cf-turnstile, challenge-platform), puis rejoue la requête avec le token. Le champ dépend du type : g-recaptcha-response pour reCAPTCHA, cf-turnstile-response pour Turnstile. Le polling sur res.php attend que le statut quitte CAPCHA_NOT_READY.

import requests
import time
import re

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("Solve timeout")


class SocialMediaResearcher:
    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 (iPhone; CPU iPhone OS 17_5 like Mac OS X) "
            "AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 "
            "Mobile/15E148 Safari/604.1",
            "Accept-Language": "en-US,en;q=0.9",
        })

    def authenticate(self, login_url, credentials, sitekey):
        """Login with CAPTCHA handling."""
        # Load login page
        self.session.get(login_url)

        # Solve CAPTCHA
        token = solve_captcha("userrecaptcha", sitekey, login_url)

        # Submit login
        resp = self.session.post(login_url, data={
            **credentials,
            "g-recaptcha-response": token,
        })
        return resp.status_code == 200

    def collect_profiles(self, profile_urls):
        """Collect public profile data with CAPTCHA handling."""
        profiles = []

        for url in profile_urls:
            try:
                resp = self.session.get(url, timeout=30)

                # Handle CAPTCHA if triggered
                if self._has_captcha(resp.text):
                    resp = self._handle_captcha(resp.text, url)

                profiles.append({
                    "url": url,
                    "data": self._parse_profile(resp.text),
                    "status": "success",
                })
                time.sleep(5)  # Slow down between profiles

            except Exception as e:
                profiles.append({
                    "url": url,
                    "error": str(e),
                    "status": "failed",
                })

        return profiles

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

    def _handle_captcha(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})
        else:
            token = solve_captcha("userrecaptcha", sitekey, url)
            return self.session.post(url, data={"g-recaptcha-response": token})

    def _parse_profile(self, html):
        from bs4 import BeautifulSoup
        soup = BeautifulSoup(html, "html.parser")
        return {
            "name": self._safe_text(soup, "h1, .profile-name"),
            "bio": self._safe_text(soup, ".bio, .profile-bio"),
            "followers": self._safe_text(soup, "[data-followers], .followers"),
            "posts": self._safe_text(soup, "[data-posts], .posts-count"),
        }

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

Suivre un hashtag page par page

Même boucle, appliquée à la pagination d'un flux thématique. Le time.sleep(5) entre deux pages n'est pas décoratif : c'est lui qui évite un défi à chaque appel.

def research_hashtag(hashtag, platform_url, pages=5):
    """Collect posts for a specific hashtag."""
    researcher = SocialMediaResearcher(
        proxy="http://user:pass@mobile.proxy.com:5000"
    )

    all_posts = []
    for page in range(pages):
        url = f"{platform_url}/explore/tags/{hashtag}?page={page}"
        resp = researcher.session.get(url, timeout=30)

        if researcher._has_captcha(resp.text):
            resp = researcher._handle_captcha(resp.text, url)

        from bs4 import BeautifulSoup
        soup = BeautifulSoup(resp.text, "html.parser")
        posts = soup.select(".post-item, article")
        for post in posts:
            all_posts.append({
                "text": post.get_text(strip=True)[:500],
                "hashtag": hashtag,
                "page": page,
            })

        time.sleep(5)

    return all_posts

Veille de marque : le balayage quotidien

Un suivi de mentions devient un job planifié : mots-clés, plateformes, rapport JSON horodaté. Les erreurs restent dans le rapport plutôt que de faire tomber le balayage.

import json
from datetime import datetime


class BrandMonitor:
    def __init__(self, brand_name, keywords, proxy=None):
        self.brand = brand_name
        self.keywords = keywords
        self.researcher = SocialMediaResearcher(proxy=proxy)

    def daily_scan(self, platform_urls):
        """Run daily brand mention scan across platforms."""
        report = {
            "brand": self.brand,
            "date": datetime.now().isoformat(),
            "platforms": {},
        }

        for name, url in platform_urls.items():
            mentions = []
            for keyword in self.keywords:
                search_url = f"{url}/search?q={keyword}"
                try:
                    resp = self.researcher.session.get(search_url, timeout=30)

                    if self.researcher._has_captcha(resp.text):
                        resp = self.researcher._handle_captcha(
                            resp.text, search_url,
                        )

                    from bs4 import BeautifulSoup
                    soup = BeautifulSoup(resp.text, "html.parser")
                    results = soup.select(".search-result, .post")
                    mentions.append({
                        "keyword": keyword,
                        "count": len(results),
                    })
                    time.sleep(5)
                except Exception as e:
                    mentions.append({
                        "keyword": keyword,
                        "error": str(e),
                    })

            report["platforms"][name] = mentions

        return report


# Usage
monitor = BrandMonitor(
    brand_name="CaptchaAI",
    keywords=["captchaai", "captcha ai", "captcha solver"],
    proxy="http://user:pass@mobile.proxy.com:5000",
)
report = monitor.daily_scan({
    "twitter": "https://twitter-alternative.example.com",
    "reddit": "https://www.reddit.com",
})
print(json.dumps(report, indent=2))

Quel proxy pour quelle plateforme

Le proxy pèse autant que la cadence. Instagram et TikTok attendent du trafic mobile (4G) ; Facebook et Twitter/X filtrent durement le datacenter et réclament du résidentiel ; LinkedIn s'attend à des IP d'entreprise, donc du résidentiel FAI ; Reddit limite le débit par IP, donc du résidentiel rotatif.

Dimensionner les threads et le budget

Un thread correspond à une résolution en cours : à cette cadence, un collecteur attend plus qu'il ne résout. Quelques centaines de profils par jour tiennent sur BASIC ($15/mois, 5 threads) ; un suivi de marque multiplateforme respire mieux sur STANDARD ($30/mois, 15 threads), une collecte continue sur ADVANCE ($90/mois, 50 threads). Dimensionnez sur le pic de parallélisme, pas sur le volume mensuel.

Dépannage

Problème Cause probable Correctif
Un défi à chaque requête IP signalée Passez en proxy mobile
Session refusée en 2 min Cadence trop soutenue Allongez les intervalles
Page vide renvoyée Contenu réservé aux membres Sortez l'URL du périmètre
Boucle de défi Cloudflare Session incohérente Alignez langue et fuseau sur le proxy

FAQ

Quels types de CAPTCHA sont pris en charge ici ?

reCAPTCHA v2 et v3, Turnstile, Cloudflare Challenge, GeeTest v3 et les CAPTCHA image ou texte. hCaptcha et FunCaptcha (Arkose Labs) ne sont pas pris en charge ; GeeTest v4 est à venir. CaptchaFox, Friendly Captcha et Lemin restent en bêta.

Combien de threads faut-il pour un balayage quotidien ?

Comptez les collecteurs simultanés, pas les profils. Cinq threads (BASIC, $15/mois) couvrent trois à quatre workers espacés ; au-delà de dix en parallèle, passez à STANDARD ($30/mois, 15 threads).

Le token est accepté mais la page reste bloquée : que vérifier ?

Trois causes reviennent : le token part d'une autre session que celle qui a chargé la page, le pageurl transmis diffère de l'URL réelle, ou le token a expiré. Renvoyez-le immédiatement, depuis la même session.

Le RGPD s'applique-t-il à des profils publics ?

Oui : « public » ne signifie pas « hors du champ du RGPD » dès qu'une personne reste identifiable. Limitez les champs collectés, pseudonymisez et fixez une durée de conservation.


Guides connexes


Fiabilisez votre collecte de données sociales : créez votre clé API CaptchaAI et laissez la résolution du défi se faire dans le flux.

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