Платформы доставки еды защищают свои данные о ценах с помощью CAPTCHA и обнаружения ботов. Службам сравнения цен, исследователям рынка и инструментам ресторанной аналитики необходим автоматический доступ для сравнения цен меню, стоимости доставки и рекламных акций на DoorDash, Uber Eats, Grubhub и других платформах.
CAPTCHA на платформах доставки
| Платформа | Тип капчи | Курок | Защищенные данные |
|---|---|---|---|
| ДверьДэш | reCAPTCHA v3 + Cloudflare | Обнаружение ботов | Меню, цены, сборы |
| Убер Ест | Cloudflare Turnstile | Автоматизированный доступ | Список ресторанов, цены |
| Грубхаб | reCAPTCHA v2 | Ограничение скорости | Пункты меню, акции |
| Почтальоны | страница Cloudflare-защиты в staging | Обнаружение парсинга | Стоимость доставки, расчетное время прибытия |
| Просто ешь | reCAPTCHA v2 | Повторные поиски | Данные ресторана |
| Инстакарт | reCAPTCHA v3 | Обнаружение ботов | Цены на продукты |
Многоплатформенный компаратор цен
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')}")
Рекомендации по прокси
| Платформа | подходящий прокси | Почему |
|---|---|---|
| ДверьДэш | Мобильная связь (4G) | Обнаружение тяжелых ботов, ожидается мобильная версия |
| Убер Ест | Мобильная связь (4G) | Мобильная платформа |
| Грубхаб | Жилой | Стандартная защита |
| Инстакарт | Жилой | Умеренное обнаружение ботов |
| Просто ешь | Вращающийся жилой дом | Стандартный Cloudflare |
Приложения доставки ориентированы на мобильные устройства — авторизованный сетевой выход с мобильными пользовательскими агентами дают наилучшие результаты.
Точки данных для отслеживания
| Метрика | Ценность бизнеса |
|---|---|
| Цены на позиции меню | Анализ паритета цен и наценки |
| Стоимость доставки | Сравнение комиссий платформы |
| Минимальная сумма заказа | Анализ барьеров доступа |
| Ориентировочное время доставки | Сравнение уровня обслуживания |
| Акции/discounts | Маркетинговая разведка |
| Наличие ресторана | Анализ покрытия |
Поиск неисправностей
| Проблема | Причина | Исправить |
|---|---|---|
| Пустые результаты ресторана | Местоположение не обслуживается или страница CAPTCHA | Установите правильный почтовый адрес доставки |
| Цены в меню отличаются от приложения | Разница в ценах на веб-сайт и на приложение | Используйте мобильный UA, чтобы получить цены, эквивалентные приложениям |
| Цикл испытаний Cloudflare | Несовпадение сигналы браузера | Используйте авторизованный сетевой выход + мобильный UA |
| Ресторан найден на одной платформе, но не найден на другой | Различное покрытие | Отметить как «недоступно» для сравнения |
| Неправильная стоимость доставки | Цены в зависимости от местоположения | Сопоставьте геолокацию прокси с целевым местоположением |
Часто задаваемые вопросы
Почему цены на разных платформах доставки различаются?
Рестораны устанавливают разные цены на каждую платформу, чтобы учесть разную комиссию (15–30%). Стоимость доставки и плата за обслуживание также различаются в зависимости от платформы.
Должен ли я использовать мобильный или настольный компьютер для очистки приложений доставки?
Мобильные устройства — это платформы, ориентированные прежде всего на мобильные устройства. авторизованный сетевой выход-сервер с пользовательским агентом iPhone/Android производит наиболее аутентичный трафик.
Как часто мне следует сравнивать цены?
Еженедельно для общего анализа рынка. Ежедневно во время рекламных периодов или соревновательных исследовательских спринтов.
Связанные руководства
- Мониторинг розничных товарных запасов
- авторизованный сетевой выход для CAPTCHA
- Ротация авторизованный сетевой выход
- Сравните цены на доставку еды в масштабе —получите ключ CaptchaAIи автоматизируйте кросс-платформенный анализ.*