Un même plat peut afficher trois tarifs différents selon l'application qui le propose : commission prélevée sur le restaurant, frais de service, promotions du moment. Mesurer ces écarts à grande échelle suppose d'interroger DoorDash, Uber Eats, Grubhub et leurs concurrents en continu — or ces plateformes verrouillent leurs pages de tarification derrière des CAPTCHA et de la détection de trafic automatisé. Ce guide montre comment résoudre reCAPTCHA v2, reCAPTCHA v3 et Cloudflare Turnstile via l'API CaptchaAI pour récupérer menus, frais de livraison et délais estimés sans rester bloqué sur un défi.
Pourquoi les prix de livraison de nourriture varient d'une plateforme à l'autre
La divergence de prix n'est pas un bug : c'est le modèle économique du secteur. Un restaurant qui paie 30 % de commission à une plateforme et 15 % à une autre répercute souvent la différence sur ses menus. À cela s'ajoutent des frais de livraison, des frais de service et des seuils de commande minimum propres à chaque application. Pour un service de veille tarifaire ou une étude de marché restauration, c'est précisément cette matrice de prix qu'il faut reconstituer.
Les techniques décrites ici valent aussi pour les marchés francophones, où Uber Eats, Deliveroo et Just Eat se partagent la France, la Belgique et la Suisse. Un point de vigilance : dès que vous collectez et stockez des données, appliquez le principe de minimisation du RGPD — ne conservez que les champs tarifaires utiles (prix, frais, délais) et évitez de capter des données personnelles sur les restaurateurs ou les livreurs.
Les CAPTCHA rencontrés sur les plateformes de livraison
Chaque plateforme choisit son mécanisme de protection selon son architecture et son niveau de trafic automatisé attendu. Le tableau ci-dessous résume les types les plus courants et ce qu'ils protègent.
| Plateforme | Type de CAPTCHA | Déclencheur | Données protégées |
|---|---|---|---|
| DoorDash | reCAPTCHA v3 + Cloudflare | Détection de trafic automatisé | Menus, prix, frais |
| Uber Eats | Cloudflare Turnstile | Accès automatisé | Fiches restaurants, prix |
| Grubhub | reCAPTCHA v2 | Limitation de débit | Articles de menu, promotions |
| Postmates | Cloudflare Challenge | Détection de scraping | Frais de livraison, délais |
| Just Eat | reCAPTCHA v2 | Recherches répétées | Données restaurants |
| Instacart | reCAPTCHA v3 | Détection de trafic automatisé | Prix des produits d'épicerie |
CaptchaAI prend en charge chacun de ces types : reCAPTCHA v2, reCAPTCHA v3, Cloudflare Turnstile et Cloudflare Challenge. En pratique, votre code détecte le type présent sur la page, envoie le sitekey à l'API et réinjecte le token obtenu dans la requête.
Un comparateur de prix multiplateforme en Python
Le script suivant encapsule toute la logique : session HTTP avec agent utilisateur mobile, détection du CAPTCHA, résolution via CaptchaAI, puis extraction des restaurants et des menus. La méthode compare_restaurant_across_platforms recherche le même restaurant sur plusieurs applications et aligne les tarifs pour comparaison.
import requests
import time
import re
from bs4 import BeautifulSoup
import json
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("Timeout")
class FoodDeliveryComparator:
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 search_restaurants(self, platform_url, location, cuisine=None):
"""Search restaurants on a delivery platform."""
params = {"address": location}
if cuisine:
params["cuisine"] = cuisine
url = f"{platform_url}/search"
resp = self.session.get(url, params=params, timeout=30)
if self._has_captcha(resp.text):
resp = self._solve_and_retry(resp.text, url)
return self._parse_restaurants(resp.text)
def get_menu(self, restaurant_url):
"""Get menu with prices from a specific restaurant."""
resp = self.session.get(restaurant_url, timeout=30)
if self._has_captcha(resp.text):
resp = self._solve_and_retry(resp.text, restaurant_url)
return self._parse_menu(resp.text)
def compare_restaurant_across_platforms(self, restaurant_name, platforms, location):
"""Compare same restaurant's pricing across delivery platforms."""
results = []
for platform in platforms:
try:
restaurants = self.search_restaurants(
platform["url"], location,
)
# Find matching restaurant
match = None
for r in restaurants:
if restaurant_name.lower() in r["name"].lower():
match = r
break
if match and match.get("url"):
menu = self.get_menu(match["url"])
results.append({
"platform": platform["name"],
"restaurant": match["name"],
"delivery_fee": match.get("delivery_fee", ""),
"delivery_time": match.get("delivery_time", ""),
"menu_items": len(menu),
"sample_prices": menu[:5],
})
else:
results.append({
"platform": platform["name"],
"restaurant": restaurant_name,
"status": "not found",
})
except Exception as e:
results.append({
"platform": platform["name"],
"error": str(e),
})
time.sleep(5)
return results
def track_delivery_fees(self, platforms, location, output_file):
"""Track delivery fees across platforms for analysis."""
all_data = []
for platform in platforms:
try:
restaurants = self.search_restaurants(
platform["url"], location,
)
for r in restaurants[:20]: # Top 20 per platform
all_data.append({
"platform": platform["name"],
"restaurant": r["name"],
"delivery_fee": r.get("delivery_fee", ""),
"delivery_time": r.get("delivery_time", ""),
"rating": r.get("rating", ""),
})
time.sleep(5)
except Exception as e:
print(f"Error on {platform['name']}: {e}")
with open(output_file, "w") as f:
json.dump(all_data, f, indent=2)
return all_data
def _has_captcha(self, html):
return any(tag in html.lower() for tag in [
'data-sitekey', 'g-recaptcha', 'cf-turnstile',
'challenge-platform',
])
def _solve_and_retry(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})
token = solve_captcha("userrecaptcha", sitekey, url)
return self.session.post(url, data={"g-recaptcha-response": token})
def _parse_restaurants(self, html):
soup = BeautifulSoup(html, "html.parser")
restaurants = []
for card in soup.select(".restaurant-card, .store-card, .merchant"):
name_el = card.select_one(".name, .store-name, h3")
if name_el:
restaurants.append({
"name": name_el.get_text(strip=True),
"url": self._link(card),
"delivery_fee": self._text(card, ".delivery-fee, .fee"),
"delivery_time": self._text(card, ".delivery-time, .eta"),
"rating": self._text(card, ".rating, .stars"),
})
return restaurants
def _parse_menu(self, html):
soup = BeautifulSoup(html, "html.parser")
items = []
for item in soup.select(".menu-item, .item-card"):
items.append({
"name": self._text(item, ".item-name, .name"),
"price": self._text(item, ".price, .item-price"),
"description": self._text(item, ".description, .item-desc"),
})
return items
def _text(self, el, selector):
found = el.select_one(selector)
return found.get_text(strip=True) if found else ""
def _link(self, card):
a = card.select_one("a")
return a.get("href", "") if a else ""
# Usage
comparator = FoodDeliveryComparator(
proxy="http://user:pass@mobile.proxy.com:5000"
)
# Compare platforms
platforms = [
{"name": "Platform A", "url": "https://delivery-a.example.com"},
{"name": "Platform B", "url": "https://delivery-b.example.com"},
{"name": "Platform C", "url": "https://delivery-c.example.com"},
]
comparison = comparator.compare_restaurant_across_platforms(
restaurant_name="Pizza Palace",
platforms=platforms,
location="10001",
)
for result in comparison:
print(f"{result.get('platform')}: Fee={result.get('delivery_fee')} "
f"ETA={result.get('delivery_time')}")
Le paramètre location attend un code postal du marché ciblé — les tarifs et la disponibilité dépendent de la zone de livraison. Adaptez-le à chaque ville testée.
Quel proxy retenir pour chaque plateforme
Les applications de livraison sont pensées d'abord pour le mobile. Un proxy mobile (4G) associé à un agent utilisateur iPhone ou Android produit le trafic le plus crédible et réduit fortement les défis répétés. Les plateformes à protection plus légère se contentent d'un proxy résidentiel.
| Plateforme | Proxy conseillé | Raison |
|---|---|---|
| DoorDash | Mobile (4G) | Détection intensive, trafic mobile attendu |
| Uber Eats | Mobile (4G) | Plateforme pensée pour le mobile |
| Grubhub | Résidentiel | Protection standard |
| Instacart | Résidentiel | Détection modérée |
| Just Eat | Résidentiel tournant | Cloudflare standard |
Les points de données à suivre
Une fois la collecte fiabilisée, ce sont ces champs qui portent la valeur analytique. Extrayez-les de façon structurée pour alimenter vos comparaisons.
| Métrique | Valeur métier |
|---|---|
| Prix des articles de menu | Analyse de parité tarifaire et des majorations |
| Frais de livraison | Comparaison des frais par plateforme |
| Montant minimum de commande | Analyse des barrières d'accès |
| Délai de livraison estimé | Comparaison des niveaux de service |
| Promotions et remises | Veille marketing |
| Disponibilité des restaurants | Analyse de couverture |
Dépannage
| Problème | Cause | Correctif |
|---|---|---|
| Aucun restaurant retourné | Zone non desservie ou page CAPTCHA | Renseignez le code postal de livraison exact |
| Prix du menu différents de l'application | Écart de tarification entre le web et l'appli | Utilisez un agent utilisateur mobile pour obtenir les tarifs applicatifs |
| Boucle de défi Cloudflare | Empreinte incohérente | Combinez proxy mobile et agent utilisateur mobile |
| Restaurant présent sur une plateforme, absent d'une autre | Couverture différente | Marquez « non disponible » dans la comparaison |
| Frais de livraison erronés | Tarification liée à la zone | Alignez la géolocalisation du proxy sur la zone cible |
FAQ
Quels CAPTCHA CaptchaAI résout-il sur les plateformes de livraison ?
Les types les plus courants dans ce secteur : reCAPTCHA v2, reCAPTCHA v3, Cloudflare Turnstile et Cloudflare Challenge. CaptchaAI ne prend pas en charge hCaptcha ni FunCaptcha (Arkose Labs) ; si une plateforme y bascule, adaptez votre approche en conséquence.
Quel plan CaptchaAI choisir pour un comparateur multiplateforme ?
Tout dépend de votre parallélisme. Le plan BASIC ($15/mois, 5 threads) suffit pour une veille sur quelques plateformes ; le plan STANDARD ($30/mois, 15 threads) absorbe des collectes plus larges. La facturation est par thread simultané, avec des résolutions illimitées par thread.
Faut-il un proxy mobile ou résidentiel ?
Mobile pour DoorDash et Uber Eats, qui attendent un trafic issu d'applications mobiles ; résidentiel pour les plateformes à protection plus légère comme Grubhub. Faites toujours correspondre l'agent utilisateur au type de proxy.
La collecte des prix de livraison est-elle conforme au RGPD ?
La donnée tarifaire publique n'est pas une donnée personnelle, mais dès que vous stockez et croisez des informations, appliquez la minimisation : ne conservez que les champs utiles et vérifiez vos obligations RGPD avant toute exploitation.
Guides connexes
- la surveillance des stocks en distribution
- les proxys mobiles pour la résolution de CAPTCHA
- la rotation des proxys résidentiels
Comparez les prix de livraison de nourriture à grande échelle : récupérez votre clé CaptchaAI et automatisez l'analyse multiplateforme.