Cas d'Usage

Surveiller des sites d'enchères malgré les CAPTCHA

Les plateformes d'enchères protègent souvent leurs pages de recherche, leurs fiches produit et l'historique des offres avec reCAPTCHA v2 ou d'autres contrôles anti-bot. Dès qu'un script enchaîne les recherches, recharge trop souvent une annonce ou surveille plusieurs lots en parallèle, les challenges apparaissent. Si vous voulez suivre les hausses de prix, les changements d'offre ou la disponibilité d'un objet, il faut intégrer la résolution CAPTCHA dans le workflow de monitoring lui-même.

Le but n'est pas seulement de passer un challenge, mais de garder une surveillance stable dans la durée : session persistante, rythme de requête crédible, et réessais propres quand un token doit être résolu juste avant la soumission.

Où les CAPTCHA apparaissent sur les sites d'enchères

Action Type de CAPTCHA Déclencheur courant
Recherche d'annonces reCAPTCHA v2 Requêtes rapides et répétitives
Fiche d'un lot reCAPTCHA v2 Grand volume depuis une même IP
Historique des enchères reCAPTCHA v2 Rechargements répétés de la même page
Navigation par catégorie Cloudflare Turnstile Vitesse de navigation trop mécanique
Pages d'alerte reCAPTCHA v2 Rafraîchissements fréquents

Monitoring d'encheres avec résolution CAPTCHA

import requests
import time
import re
from datetime import datetime

class AuctionMonitor:
    def __init__(self, api_key):
        self.api_key = api_key
        self.session = requests.Session()
        self.session.headers.update({
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
        })

    def search_listings(self, auction_url, query, category=None):
        """Search auction listings, solving CAPTCHAs when triggered."""
        params = {"q": query}
        if category:
            params["category"] = category

        response = self.session.get(
            f"{auction_url}/search", params=params
        )

        if self._has_captcha(response.text):
            site_key = self._extract_site_key(response.text)
            token = self._solve_recaptcha(site_key, f"{auction_url}/search")
            response = self.session.post(
                f"{auction_url}/search",
                data={**params, "g-recaptcha-response": token}
            )

        return self._parse_listings(response.text)

    def monitor_listing(self, auction_url, listing_id):
        """Get current bid and listing details."""
        url = f"{auction_url}/item/{listing_id}"
        response = self.session.get(url)

        if self._has_captcha(response.text):
            site_key = self._extract_site_key(response.text)
            token = self._solve_recaptcha(site_key, url)
            response = self.session.post(url, data={
                "g-recaptcha-response": token
            })

        return self._parse_listing_detail(response.text)

    def track_bids(self, auction_url, listing_ids, interval=60):
        """Track bid changes across multiple listings."""
        history = {lid: [] for lid in listing_ids}

        while True:
            for listing_id in listing_ids:
                try:
                    detail = self.monitor_listing(auction_url, listing_id)
                    previous = history[listing_id]

                    if previous and detail["current_bid"] != previous[-1]["current_bid"]:
                        print(f"Bid change on {listing_id}: "
                              f"${previous[-1]['current_bid']} → ${detail['current_bid']}")

                    history[listing_id].append(detail)
                except Exception as e:
                    print(f"Error checking {listing_id}: {e}")

            time.sleep(interval)

    def _has_captcha(self, html):
        return "g-recaptcha" in html or "recaptcha" in html.lower()

    def _extract_site_key(self, html):
        match = re.search(r'data-sitekey="([^"]+)"', html)
        if match:
            return match.group(1)
        match = re.search(r"sitekey['\"]?\s*[:=]\s*['\"]([^'\"]+)", html)
        if match:
            return match.group(1)
        raise ValueError("Could not find reCAPTCHA site key")

    def _solve_recaptcha(self, site_key, page_url):
        resp = requests.post("https://ocr.captchaai.com/in.php", data={
            "key": self.api_key,
            "method": "userrecaptcha",
            "googlekey": site_key,
            "pageurl": page_url,
            "json": 1
        })
        task_id = resp.json()["request"]

        for _ in range(60):
            time.sleep(3)
            result = requests.get("https://ocr.captchaai.com/res.php", params={
                "key": self.api_key,
                "action": "get",
                "id": task_id,
                "json": 1
            })
            data = result.json()
            if data["status"] == 1:
                return data["request"]

        raise TimeoutError("reCAPTCHA solve timed out")

    def _parse_listings(self, html):
        from bs4 import BeautifulSoup
        soup = BeautifulSoup(html, "html.parser")

        def text_of(node):
            return node.get_text(strip=True) if node else None

        listings = []
        for item in soup.select(".listing-item, .auction-item"):
            link = item.select_one("a")
            listings.append({
                "title": text_of(item.select_one(".title")),
                "current_bid": text_of(item.select_one(".price, .bid")),
                "time_left": text_of(item.select_one(".time-left")),
                "url": link.get("href") if link else None,
            })
        return listings

    def _parse_listing_detail(self, html):
        from bs4 import BeautifulSoup
        soup = BeautifulSoup(html, "html.parser")

        def text_of(node):
            return node.get_text(strip=True) if node else None

        return {
            "title": text_of(soup.select_one("h1, .item-title")),
            "current_bid": text_of(soup.select_one(".current-bid, .price")),
            "bid_count": text_of(soup.select_one(".bid-count")),
            "time_left": text_of(soup.select_one(".time-remaining")),
            "checked_at": datetime.now().isoformat(),
        }

# Usage
monitor = AuctionMonitor("YOUR_API_KEY")
listings = monitor.search_listings(
    "https://auctions.example.com",
    "vintage electronics",
    category="collectibles"
)

Systeme d'alerte de prix en JavaScript

class AuctionTracker {
  constructor(apiKey) {
    this.apiKey = apiKey;
    this.watchList = new Map();
  }

  addWatch(listingId, url, maxPrice) {
    this.watchList.set(listingId, { url, maxPrice, history: [] });
  }

  async checkAll() {
    const alerts = [];

    for (const [id, watch] of this.watchList) {
      try {
        const detail = await this.fetchListing(watch.url);
        watch.history.push(detail);

        const price = parseFloat(detail.currentBid.replace(/[^0-9.]/g, ''));
        if (price >= watch.maxPrice * 0.9) {
          alerts.push({
            listing: id,
            price,
            threshold: watch.maxPrice,
            message: `Price approaching limit: $${price} / $${watch.maxPrice}`
          });
        }
      } catch (error) {
        alerts.push({ listing: id, error: error.message });
      }
    }

    return alerts;
  }

  async fetchListing(url) {
    const response = await fetch(url);
    const html = await response.text();

    if (html.includes('g-recaptcha')) {
      return this.solveAndFetch(url, html);
    }

    return this.parseDetail(html);
  }

  async solveAndFetch(url, html) {
    const siteKeyMatch = html.match(/data-sitekey="([^"]+)"/);
    if (!siteKeyMatch) throw new Error('No reCAPTCHA site key found');

    const submitResp = await fetch('https://ocr.captchaai.com/in.php', {
      method: 'POST',
      body: new URLSearchParams({
        key: this.apiKey,
        method: 'userrecaptcha',
        googlekey: siteKeyMatch[1],
        pageurl: url,
        json: '1'
      })
    });

    const { request: taskId } = await submitResp.json();

    for (let i = 0; i < 60; i++) {
      await new Promise(r => setTimeout(r, 3000));
      const result = await fetch(
        `https://ocr.captchaai.com/res.php?key=${this.apiKey}&action=get&id=${taskId}&json=1`
      );
      const data = await result.json();
      if (data.status === 1) {
        // Resubmit with token
        const response = await fetch(url, {
          method: 'POST',
          body: new URLSearchParams({ 'g-recaptcha-response': data.request })
        });
        return this.parseDetail(await response.text());
      }
    }

    throw new Error('reCAPTCHA solve timed out');
  }

  parseDetail(html) {
    // Parse auction listing details from HTML
    return {
      currentBid: html.match(/current.?bid[^>]*>([^<]+)/i)?.[1]?.trim(),
      bidCount: html.match(/(\d+)\s*bids?/i)?.[1],
      timeLeft: html.match(/time.?(?:left|remaining)[^>]*>([^<]+)/i)?.[1]?.trim(),
      checkedAt: new Date().toISOString()
    };
  }
}

// Usage
const tracker = new AuctionTracker('YOUR_API_KEY');
tracker.addWatch('item-123', 'https://auctions.example.com/item/123', 500);
tracker.addWatch('item-456', 'https://auctions.example.com/item/456', 200);
const alerts = await tracker.checkAll();

Stratégie de surveillance

Fréquence de contrôle Cas d'usage Taux de CAPTCHA attendu
Toutes les 30 secondes Fin d'enchère très sensible Élevé, avec proxys quasi obligatoires
Toutes les 5 minutes Suivi actif Modéré
Toutes les 15 minutes Watchlist classique Faible
Toutes les heures Veille longue durée Minimal

Comment réduire la fréquence des CAPTCHA

Technique Effet
Réutiliser les cookies de session Conserve un état plus cohérent
Faire tourner des proxys résidentiels Répartit les requêtes sur plusieurs IP
Randomiser les intervalles Évite un rythme trop prévisible
Utiliser des comptes authentifiés Réduit parfois le niveau de suspicion

Dépannage

Problème Cause probable Correctif
CAPTCHA à chaque requête Session non persistante Réutilisez requests.Session()
Token reCAPTCHA rejeté Token expiré Résoudre juste avant l'envoi du formulaire
La page montre 0 résultat Le challenge filtre la réponse sans bruit Recherchez la présence d'éléments CAPTCHA cachés
IP bloquée après plusieurs checks Rate limit dépassé Faites tourner les proxys et espacez les contrôles

FAQ

À quelle vitesse peut-on vérifier des annonces ?

CaptchaAI résout souvent un reCAPTCHA v2 en quelques dizaines de secondes. Pour les ventes sensibles au temps, il faut surtout optimiser la session, la rotation proxy et le moment où vous déclenchant la résolution.

Les sites d'enchères détectent-ils facilement le monitoring ?

Ils détectent surtout les motifs de trafic trop réguliers ou trop agressifs. La résolution CAPTCHA ne suffit pas à elle seule : il faut aussi gérer les intervalles, les sessions et la diversité du trafic.

Peut-on suivre une enchère quasi en temps réel ?

Oui, mais les dernières minutes sont aussi la zone la plus sensible. Dans ce cas, une session déjà authentifiée, des contrôles limités et une résolution CAPTCHA déclenchée au bon moment sont plus efficaces qu'un polling brutal.

Articles connexes

  • Comment résoudre le callback reCAPTCHA v2 avec l'API
  • Gérer reCAPTCHA v2 et Turnstile sur le même site
  • Mécanisme de callback reCAPTCHA v2

Prochaines étapes

Si vous devez suivre des prix ou des offres sur plusieurs lots, récupérez votre clé API CaptchaAI et intégrez la résolution reCAPTCHA dans votre logique de monitoring plutôt que comme un correctif ajouté après coup.

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