Use Cases

Comparação de preços de entrega de alimentos com resolução CAPTCHA

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

  1. 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.
  2. 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.
  3. 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.

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


Compare preços de entrega em grande escala — obtenha sua chave da CaptchaAI e automatize a análise entre plataformas.

Os comentários estão desativados para este artigo.