Use Cases

Surveillance des prix du commerce électronique avec la gestion des CAPTCHA

Un CAPTCHA sur une fiche produit suffit à interrompre toute une campagne de veille tarifaire : la page ne se charge pas, le prix n'est pas relevé et votre historique se retrouve troué. La parade consiste à détecter ces défis à la volée et à les résoudre automatiquement. Voici comment bâtir un pipeline de surveillance des prix e-commerce qui gère reCAPTCHA v2 et Cloudflare Turnstile via l'API CaptchaAI, du premier relevé à l'alerte de changement de prix.

Pourquoi la surveillance des prix e-commerce déclenche des CAPTCHA

La veille tarifaire repose sur des relevés fréquents des mêmes fiches produit. C'est justement le profil de trafic que les défenses anti-bot cherchent à ralentir : au-delà d'un certain rythme, la page renvoie un CAPTCHA au lieu du prix attendu.

Plateforme Type de CAPTCHA observé Déclencheur habituel
Amazon Image CAPTCHA, reCAPTCHA Volume de requêtes élevé
Walmart Cloudflare Turnstile Détection de trafic automatisé
eBay reCAPTCHA v2 Motifs de navigation suspects
Best Buy Cloudflare Challenge Tout le trafic automatisé
Boutiques Shopify reCAPTCHA v3 Selon la configuration du marchand

Ces associations sont indicatives : un même site peut changer de protection selon la région ou la charge. Sans gestion des CAPTCHA, les conséquences s'enchaînent vite :

  • des trous dans vos séries de prix, là où un défi a remplacé le tarif ;
  • une analyse concurrentielle faussée, calculée sur des données incomplètes ;
  • des échecs silencieux : le script tourne sans signaler qu'il ne relève plus rien.

Architecture d'un pipeline de veille résilient

Le principe est simple : chaque relevé passe par une détection, et le CAPTCHA n'est résolu que lorsqu'il apparaît. Les pages qui se chargent normalement ne coûtent rien de plus.

Scheduler (every 30 min)
    → URL Queue
        → Scraper Workers (5-10 concurrent)
            → Fetch page
            → CAPTCHA detected?
                → Yes → CaptchaAI → Solve → Retry page
                → No → Parse prices
            → Store in database
        → Alert on price changes

Le pipeline enchaîne quatre étapes :

  • un planificateur alimente une file d'URL à intervalle régulier ;
  • des workers récupèrent les pages en parallèle (cinq à dix au départ) ;
  • seul le chemin « CAPTCHA détecté » appelle l'API, le reste part au parsing ;
  • les prix extraits partent en base, puis leur comparaison avec les valeurs précédentes déclenche les alertes.

Estimer le budget avec la facturation par threads

CaptchaAI facture au thread, pas à la résolution : un thread traite un CAPTCHA à la fois et se libère dès qu'une résolution se termine, avec un nombre illimité de résolutions par thread sur le mois. Ce qui compte pour une veille n'est donc pas le nombre total de CAPTCHA par jour, mais la concurrence : combien de résolutions tournent en même temps au pic.

Charge de surveillance Concurrence au pic Plan adapté
50 produits, toutes les 30 min quelques résolutions simultanées BASIC ($15/mois, 5 threads)
200 produits, toutes les 15 min pics modérés STANDARD ($30/mois, 15 threads)
1 000 produits, toutes les heures forte concurrence ADVANCE ($90/mois, 50 threads)

Gardez deux repères en tête avant de choisir un plan :

  • ces charges sont indicatives et varient selon l'environnement et le volume ;
  • toutes les pages ne déclenchent pas un CAPTCHA : en résolvant seulement à la détection, la concurrence réelle reste bien inférieure au volume brut.

Mise en œuvre

Moniteur de prix en Python

Le cœur du script est solve_captcha : elle envoie le défi à in.php, puis interroge res.php toutes les cinq secondes tant que la réponse reste CAPCHA_NOT_READY. fetch_with_captcha repère ensuite un sitekey reCAPTCHA ou Turnstile et rejoue la requête avec le champ g-recaptcha-response ou cf-turnstile-response.

import requests
import time
import re
import json
import os
from datetime import datetime

API_KEY = os.environ["CAPTCHAAI_API_KEY"]
BASE_URL = "https://ocr.captchaai.com"


def solve_captcha(method, params):
    params["key"] = API_KEY
    params["method"] = method

    resp = requests.get(f"{BASE_URL}/in.php", params=params)
    if not resp.text.startswith("OK|"):
        raise Exception(f"Submit failed: {resp.text}")

    task_id = resp.text.split("|")[1]

    for _ in range(60):
        time.sleep(5)
        result = requests.get(f"{BASE_URL}/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(f"Solve failed: {result.text}")

    raise TimeoutError("CAPTCHA solve timed out")


def fetch_with_captcha(url, session):
    """Fetch a page, solving CAPTCHAs if encountered."""
    resp = session.get(url)

    # Check for reCAPTCHA
    match = re.search(r'data-sitekey=["\']([A-Za-z0-9_-]+)["\']', resp.text)
    if match:
        site_key = match.group(1)
        token = solve_captcha("userrecaptcha", {
            "googlekey": site_key,
            "pageurl": url,
        })
        resp = session.post(url, data={"g-recaptcha-response": token})

    # Check for Turnstile
    match = re.search(
        r'class="cf-turnstile"[^>]*data-sitekey=["\']([^"\']+)', resp.text
    )
    if match:
        site_key = match.group(1)
        token = solve_captcha("turnstile", {
            "sitekey": site_key,
            "pageurl": url,
        })
        resp = session.post(url, data={"cf-turnstile-response": token})

    return resp


def extract_price(html, selectors):
    """Extract price from HTML using regex patterns."""
    for pattern in selectors:
        match = re.search(pattern, html)
        if match:
            price_str = match.group(1).replace(",", "")
            return float(price_str)
    return None


def monitor_prices(products):
    """Monitor prices for a list of products."""
    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 product in products:
        try:
            resp = fetch_with_captcha(product["url"], session)
            price = extract_price(resp.text, product["selectors"])

            results.append({
                "name": product["name"],
                "url": product["url"],
                "price": price,
                "timestamp": datetime.utcnow().isoformat(),
                "status": "ok",
            })
            print(f"  {product['name']}: ${price}")

        except Exception as e:
            results.append({
                "name": product["name"],
                "url": product["url"],
                "price": None,
                "timestamp": datetime.utcnow().isoformat(),
                "status": f"error: {e}",
            })
            print(f"  {product['name']}: ERROR - {e}")

    return results


# Define products to monitor
products = [
    {
        "name": "Wireless Headphones",
        "url": "https://example.com/product/headphones",
        "selectors": [
            r'class="price"[^>]*>\$?([\d,]+\.?\d*)',
            r'itemprop="price" content="([\d.]+)"',
        ],
    },
    {
        "name": "Bluetooth Speaker",
        "url": "https://example.com/product/speaker",
        "selectors": [
            r'class="price"[^>]*>\$?([\d,]+\.?\d*)',
        ],
    },
]

print("Starting price check...")
results = monitor_prices(products)

# Save results
with open("prices.json", "w") as f:
    json.dump(results, f, indent=2)

Version Node.js

La même logique se transpose avec axios et cheerio : chargez la page, lisez l'attribut data-sitekey de l'élément .g-recaptcha, puis rejouez la requête une fois le token obtenu. Le polling garde le rythme de cinq secondes de Python.

const axios = require("axios");
const cheerio = require("cheerio");

const API_KEY = process.env.CAPTCHAAI_API_KEY;

async function solveCaptcha(method, params) {
  params.key = API_KEY;
  params.method = method;

  const submit = await axios.get("https://ocr.captchaai.com/in.php", {
    params,
  });
  const taskId = String(submit.data).split("|")[1];

  for (let i = 0; i < 60; i++) {
    await new Promise((r) => setTimeout(r, 5000));
    const poll = await axios.get("https://ocr.captchaai.com/res.php", {
      params: { key: API_KEY, action: "get", id: taskId },
    });
    const text = String(poll.data);
    if (text === "CAPCHA_NOT_READY") continue;
    if (text.startsWith("OK|")) return text.split("|").slice(1).join("|");
    throw new Error(text);
  }
  throw new Error("Timeout");
}

async function monitorPrice(url) {
  const resp = await axios.get(url);
  const $ = cheerio.load(resp.data);

  // Check for reCAPTCHA
  const siteKey = $(".g-recaptcha").attr("data-sitekey");
  if (siteKey) {
    const token = await solveCaptcha("userrecaptcha", {
      googlekey: siteKey,
      pageurl: url,
    });
    // Re-fetch with token
    const formResp = await axios.post(url, { "g-recaptcha-response": token });
    return cheerio.load(formResp.data);
  }

  const price = $('[itemprop="price"]').attr("content") || $(".price").text();
  return parseFloat(price.replace(/[^0-9.]/g, ""));
}

Planifier les relevés

Un intervalle de 30 minutes suffit pour la plupart des campagnes : il capte les changements sans marteler les sites cibles. En production, une entrée cron reste la plus robuste.

# crontab -e
*/30 * * * * cd /opt/monitor && python price_monitor.py >> /var/log/prices.log 2>&1

Pour un ordonnancement piloté depuis Python, la bibliothèque schedule fait le travail sans dépendance système :

import schedule

schedule.every(30).minutes.do(lambda: monitor_prices(products))

while True:
    schedule.run_pending()
    time.sleep(60)

Côté hébergement, rapprochez vos workers des marchands ciblés pour réduire la latence :

  • un petit worker sur OVHcloud ou Scaleway suffit dans la plupart des cas ;
  • une fonction planifiée en région européenne (eu-west-3, Paris) reste au plus près des sites francophones.

Conformité RGPD et collecte responsable

Les prix affichés sont des données publiques, mais un pipeline de surveillance reste soumis à quelques garde-fous, surtout pour un public francophone :

  • limitez la collecte aux seules données tarifaires et évitez d'aspirer des données personnelles (avis nominatifs, comptes clients) — c'est la minimisation posée par le RGPD ;
  • respectez les conditions d'utilisation et le robots.txt des sites, et restez sur votre périmètre de veille autorisé ;
  • espacez les requêtes : un rythme régulier est plus fiable qu'une rafale ponctuelle.

Dépannage

Problème Cause probable Correctif
Le CAPTCHA n'est jamais détecté Le motif data-sitekey ne correspond pas au balisage réel de la page Inspectez le HTML rendu et ajustez l'expression régulière
CAPCHA_NOT_READY en boucle jusqu'au timeout La résolution dépasse la fenêtre de polling Augmentez le nombre d'itérations ou l'intervalle entre deux interrogations
Prix relevé à None Les sélecteurs ne correspondent plus après une refonte du site Mettez à jour la liste selectors du produit concerné
Blocages malgré la résolution Même IP et même User-Agent sur trop de requêtes Faites tourner les proxys résidentiels et espacez les relevés

FAQ

Quels types de CAPTCHA CaptchaAI résout-il pour la veille tarifaire ?

Les plus courants : reCAPTCHA v2 et v3, Cloudflare Turnstile et Cloudflare Challenge, GeeTest v3, plus les CAPTCHA image/OCR et en grille. En revanche, hCaptcha et FunCaptcha ne sont pas pris en charge.

La surveillance des prix est-elle conforme au RGPD ?

Les prix sont des données publiques, donc leur relevé est généralement admis. Restez néanmoins prudent : minimisez les données personnelles collectées, respectez les conditions d'utilisation des sites et vérifiez vos obligations RGPD avant toute collecte à grande échelle.

Comment réduire le coût de résolution des CAPTCHA ?

Jouez sur trois leviers :

  • ne résolvez qu'à la détection, jamais de façon systématique ;
  • mettez en cache les pages qui ne changent pas d'un relevé à l'autre ;
  • espacez vos relevés pour lisser les pics de concurrence.

Comme la facturation est par thread avec résolutions illimitées, un plan dimensionné sur votre concurrence coûte moins cher qu'une tarification à la pièce.

Vais-je être bloqué même en résolvant les CAPTCHA ?

La résolution lève le défi, mais pas les autres signaux de détection. Faites tourner proxys résidentiels et User-Agents, et répartissez les requêtes dans le temps.

Guides connexes

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