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.