Um scraper de comparação de preços de entrega costuma sobreviver à primeira busca e travar na segunda: DoorDash, Uber Eats e Grubhub reconhecem o padrão de requisições repetidas e respondem com reCAPTCHA v2, reCAPTCHA v3 ou Cloudflare Turnstile antes de liberar o cardápio ou a tela de checkout. Sem resolver esse desafio, ferramentas de comparação de preço, pesquisa de mercado e analytics de restaurante ficam paradas na primeira página.
Este guia mostra onde cada plataforma coloca o desafio (reCAPTCHA v2, reCAPTCHA v3 e Cloudflare Turnstile são os mais comuns no setor), traz um script Python pronto para resolver o CAPTCHA e continuar a coleta, e recomenda proxy e User-Agent por plataforma para manter cardápio, taxa de entrega e promoções atualizados em várias plataformas ao mesmo tempo.
Quais dados vale a pena rastrear antes de montar o pipeline
Antes de escrever qualquer linha de scraping, vale decidir o que a comparação realmente precisa capturar — isso evita reprocessar tudo depois:
- Preço dos itens do cardápio — paridade de preço e análise de markup entre plataformas.
- Taxa de entrega — comparação direta de custo por plataforma.
- Pedido mínimo — indica barreira de acesso ao cardápio completo.
- Prazo estimado de entrega — indicador de nível de serviço.
- Promoções e descontos ativos — inteligência de marketing e sazonalidade.
- Disponibilidade do restaurante por região — mostra cobertura real, não só preço.
Cada ponto vive em uma parte diferente do HTML — o mapa abaixo mostra onde o CAPTCHA costuma bloquear a coleta em cada plataforma.
Onde o CAPTCHA aparece na comparação de preços de apps de entrega
| Plataforma | Tipo de CAPTCHA | Gatilho | Dados protegidos |
|---|---|---|---|
| DoorDash | reCAPTCHA v3 + Cloudflare | Detecção de bots | Cardápios, preços, taxas |
| Uber Eats | Cloudflare Turnstile | Acesso automatizado | Listagens de restaurantes, preços |
| Grubhub | reCAPTCHA v2 | Limite de requisições | Itens do cardápio, promoções |
| Postmates | Cloudflare Challenge | Detecção de scraping | Taxas de entrega, prazos estimados |
| Just Eat | reCAPTCHA v2 | Buscas repetidas | Dados do restaurante |
| Instacart | reCAPTCHA v3 | Detecção de bots | Preços de mercado |
Script Python para comparar preços entre plataformas de entrega
O comparador abaixo faz a busca por endereço em cada plataforma, detecta o CAPTCHA pelo HTML da resposta, resolve via API da CaptchaAI e reenvia a requisição original com o token — sem precisar reescrever o fluxo de busca a cada vez que uma plataforma muda o desafio.
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')}")
O ponto central é _solve_and_retry: lê a sitekey no HTML, decide entre turnstile e userrecaptcha pela marcação da página e reenvia a requisição já com o token no campo certo (cf-turnstile-response ou g-recaptcha-response). Em produção, troque o time.sleep(5) fixo por backoff exponencial e registre a taxa de CAPTCHA por plataforma — esse número indica se vale investir em mais threads.
Proxy e User-Agent: o que muda por plataforma
| Plataforma | Proxy recomendado | Por quê |
|---|---|---|
| DoorDash | Móvel (4G) | Detecção agressiva de bots, espera tráfego de app |
| Uber Eats | Móvel (4G) | Plataforma mobile-first |
| Grubhub | Padrão | Proteção comum, sem exigir sinal mobile |
| Instacart | Padrão | Detecção moderada de bots |
| Just Eat | Rotação regular | Cloudflare padrão |
Os apps de entrega priorizam o tráfego mobile: um proxy com IP de operadora e User-Agent de iPhone ou Android tende a produzir os resultados mais estáveis, principalmente no Uber Eats e no DoorDash.
Como validar o proxy antes de rodar em produção
- Meça o RTT a partir de uma região próxima ao workload, como AWS
sa-east-1(São Paulo), em vez de assumir a latência de um proxy nos EUA. - Fixe o timeout do polling com base nessa medição — 5 s entre consultas costuma bastar para reCAPTCHA v2/v3, e o Turnstile responde ainda mais rápido.
- Rode uma amostra de 10-20 requisições por plataforma e registre a taxa de CAPTCHA por combinação de proxy e User-Agent antes do lote completo.
Erros comuns e como corrigir
| Problema | Causa | Correção |
|---|---|---|
| Lista de restaurantes vazia | Endereço fora da área de cobertura ou página de CAPTCHA | Confirme o CEP do endereço de entrega usado na busca |
| Preço do cardápio diferente do app | Preço distinto entre versão web e app | Use User-Agent mobile para captar o preço equivalente ao app |
| Loop de desafio do Cloudflare | Sinal de navegador inconsistente com o proxy | Alinhe o tipo de proxy ao User-Agent (mobile com mobile) |
| Restaurante aparece em uma plataforma e não em outra | Cobertura de entrega diferente por plataforma | Marque como "indisponível" na comparação em vez de erro |
| Taxa de entrega incorreta | Preço depende da localização exata do endereço | Alinhe a geolocalização do proxy ao endereço de destino |
Se o pipeline grava endereço de entrega, histórico de pedido ou qualquer dado vinculado a uma conta de usuário, trate isso como dado pessoal para efeitos da LGPD: limite a retenção ao que a análise realmente precisa e mantenha o acesso restrito à equipe de dados.
Perguntas frequentes
Que tipos de CAPTCHA aparecem com mais frequência nessa comparação?
reCAPTCHA v2 e v3 dominam em DoorDash, Grubhub e Instacart, enquanto o Uber Eats usa principalmente Cloudflare Turnstile. Vale detectar o tipo pelo HTML da página antes de escolher o method da chamada à API.
Por que o mesmo prato custa diferente em cada app de entrega?
Porque a comissão cobrada pela plataforma varia — normalmente entre 15% e 30% — e o restaurante repassa parte disso ao preço do cardápio. Taxa de entrega e taxa de serviço também mudam por plataforma, então a comparação real precisa somar tudo, não só o preço do item.
Coletar preços públicos de apps de entrega para benchmarking tem risco legal?
Trate como qualquer coleta de dado público: use ambiente próprio, respeite os termos de uso de cada plataforma e, se armazenar endereço vinculado a um usuário, siga a LGPD.
É melhor coletar via app mobile ou versão web?
Mobile. Essas são plataformas mobile-first, e o preço exibido no site desktop às vezes diverge do preço real do app. Um proxy com IP de operadora e User-Agent de iPhone ou Android reduz esse desvio.
Como evitar o loop de desafio do Cloudflare Turnstile no Uber Eats?
Geralmente indica sinal de navegador inconsistente com o proxy — IP de datacenter com User-Agent mobile, por exemplo. Alinhe os dois e resolva o Turnstile assim que o desafio aparecer, sem reenviar a mesma requisição várias vezes.
Guias relacionados
- Monitoramento de estoque no varejo com CAPTCHA
- Proxies móveis e taxa de sucesso maior no CAPTCHA
- Proxies com rotação residencial para CAPTCHA
Compare preços de entrega em grande escala — obtenha sua chave da CaptchaAI e automatize a análise entre plataformas.