Pour surveiller un site d'enchères sans blocage, la résolution du reCAPTCHA v2 doit vivre à l'intérieur de votre boucle de monitoring, pas à côté. Dès que votre script enchaîne les recherches ou recharge une fiche de lot toutes les quelques secondes, le défi s'affiche — et une surveillance qui s'interrompt à chaque challenge ne vous apprend rien sur l'évolution des prix.
L'enjeu n'est donc pas de résoudre un CAPTCHA isolé, mais de tenir une surveillance stable dans la durée : session persistante, rythme de requête crédible et nouvelle tentative propre juste avant la soumission du formulaire.
Où les CAPTCHA se déclenchent sur un site d'enchères
Avant d'écrire la moindre ligne, repérez les points où le défi va tomber. Ce sont presque toujours les mêmes actions qui déclenchent reCAPTCHA v2 : une recherche répétée, une fiche de lot consultée en boucle, ou un historique rechargé trop souvent depuis la même IP.
| 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 |
Résoudre reCAPTCHA v2 dans votre boucle de monitoring
La classe ci-dessous encapsule tout le cycle : détection du défi, extraction du sitekey, envoi à CaptchaAI, interrogation du résultat, puis renvoi du formulaire avec le token g-recaptcha-response. Une seule requests.Session() conserve les cookies d'une vérification à l'autre, ce qui évite de redéclencher un CAPTCHA à chaque appel.
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"
)
Alerte de prix et suivi des offres en JavaScript
Côté navigateur ou Node.js, la même logique devient une watchlist : chaque lot porte un prix plafond, et une alerte se déclenche quand l'offre s'en approche. La résolution reCAPTCHA vit dans solveAndFetch, appelée seulement si la page renvoie un g-recaptcha.
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();
Le seuil à 90 % du prix plafond (maxPrice * 0.9) vous laisse le temps de réagir avant que le lot ne dépasse votre limite ; resserrez-le à l'approche de la clôture.
Choisir la bonne fréquence de contrôle
Un moniteur d'enchères arbitre entre fraîcheur des données et discrétion : plus vous contrôlez souvent, plus vous captez tôt un changement d'offre, mais plus vous exposez votre trafic à un rythme mécanique et donc à davantage de CAPTCHA.
Les chiffres ci-dessous reposent sur des mesures observées et des retours d'utilisateurs. Les résultats varient selon l'environnement, le volume et le moment de la journée.
| 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 |
Réduire le nombre de CAPTCHA rencontrés
Le meilleur CAPTCHA reste celui que vous ne déclenchez pas : avant d'augmenter votre débit de résolution, réduisez d'abord la fréquence à laquelle les défis apparaissent.
| 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 |
Surveillance d'enchères et RGPD
Si vous surveillez des places de marché francophones — d'Interencheres à Drouot Digital — pensez à la conformité en amont. Une fiche de lot mélange souvent des données publiques (prix, montant de l'offre, temps restant) et des données personnelles (pseudo ou coordonnées d'un enchérisseur). Ne collectez que ce dont vous avez besoin pour votre suivi de prix, et n'archivez pas les identifiants d'enchérisseurs si votre objectif est l'évolution du montant.
En pratique : limitez-vous à current_bid, time_left et un horodatage, et vérifiez les conditions d'utilisation de chaque plateforme. Le RGPD n'interdit pas la veille tarifaire, mais attend une collecte proportionnée.
Dimensionner votre plan CaptchaAI
CaptchaAI facture au thread simultané, pas à la résolution : un thread traite un défi à la fois, puis passe au suivant sans limite de volume dans le mois. Un reCAPTCHA v2 se résout généralement en moins de 60 secondes avec un taux de réussite élevé, ce qui fixe le débit par thread.
Pour une watchlist contrôlée toutes les 5 à 15 minutes, le plan BASIC ($15/mois, 5 threads) traite les défis en parallèle sans file d'attente. Pour surveiller des dizaines de lots pendant une clôture serrée, où plusieurs pages déclenchent un CAPTCHA en même temps, le plan STANDARD ($30/mois, 15 threads) absorbe mieux les pics. La facturation reste en dollars US, quel que soit le type de CAPTCHA.
Dépannage
| Problème | Cause probable | Correctif |
|---|---|---|
| CAPTCHA à chaque requête | Session non persistante | Réutilisez requests.Session() |
| Token reCAPTCHA rejeté | Token expiré (durée de vie ~2 min) | Résolvez juste avant l'envoi du formulaire |
| La page montre 0 résultat | Le défi filtre la réponse sans bruit | Recherchez la présence d'éléments CAPTCHA cachés |
| IP bloquée après plusieurs contrôles | Rate limit dépassé | Faites tourner les proxys et espacez les contrôles |
FAQ
Combien de threads faut-il pour surveiller plusieurs enchères en parallèle ?
Comptez un thread par défi résolu simultanément. Une watchlist contrôlée toutes les 5 à 15 minutes tient sur le plan BASIC ($15/mois, 5 threads) ; pour une clôture serrée où de nombreux lots déclenchent un CAPTCHA en même temps, le plan STANDARD ($30/mois, 15 threads) évite la file d'attente.
reCAPTCHA v2 est-il le seul CAPTCHA sur les sites d'enchères ?
Non. La recherche et les fiches de lot affichent surtout reCAPTCHA v2, mais la navigation par catégorie déclenche parfois Cloudflare Turnstile. Prévoyez de détecter les deux familles sur un même domaine.
La surveillance d'enchères est-elle conforme au RGPD ?
Elle peut l'être si votre collecte reste proportionnée. Limitez-vous aux données de prix et d'horodatage utiles à votre suivi, évitez de stocker les identifiants d'enchérisseurs, et vérifiez les conditions d'utilisation de la plateforme. Ce guide ne constitue pas un conseil juridique.
Pourquoi mon token est-il refusé au moment de la soumission ?
Le plus souvent, il a expiré : un token reCAPTCHA v2 ne vit que deux minutes environ. Déclenchez la résolution juste avant d'envoyer le formulaire, plutôt que de résoudre à l'ouverture puis d'attendre.
Articles connexes
- Déclencher le callback reCAPTCHA v2 via l'API
- Gérer reCAPTCHA v2 et Turnstile sur un même site
- Comprendre le mécanisme de callback reCAPTCHA v2
Prochaines étapes
Pour suivre les prix et les offres sur plusieurs lots sans interruption, récupérez votre clé API CaptchaAI et intégrez la résolution reCAPTCHA dans votre logique de monitoring, plutôt que de la greffer après coup.