Как скрейпить Yelp: бизнес-листинги и отзывы в 2026 году — публичные данные, PerimeterX и прокси

Практическое руководство для разработчиков по сбору публичных бизнес-данных и отзывов Yelp с использованием резидентных прокси: обход PerimeterX, пагинация, rate-limiting и этические границы.

How to Scrape Yelp Business Listings and Reviews in 2026: Public Data, PerimeterX, and Production Patterns

Важно: Эта статья посвящена доступу исключительно к публичным данным. Перед любым скрейпингом Yelp ознакомьтесь с Условиями использования Yelp. Несанкционированный доступ к данным может нарушать Закон о компьютерном мошенничестве и злоупотреблениях (CFAA, США) и Общий регламент по защите данных (GDPR, ЕС). Информация ниже предназначена для разработчиков, работающих с общедоступными страницами в рамках применимого законодательства.

Как скрейпить Yelp в 2026 году: обзор публичных данных и антибот-защиты

Yelp остаётся одним из крупнейших агрегаторов отзывов о местных бизнесах — более 250 млн отзывов по состоянию на 2024 год, согласно отчёту Yelp About. Для разработчиков, собирающих датасеты локальных бизнесов, Yelp представляет ценный источник структурированной информации: рейтинги, категории, часы работы, координаты и тексты отзывов.

Однако скрейпинг Yelp в 2026 году — это не простой HTTP-запрос. Платформа защищена PerimeterX (ныне HUMAN Security), использует TLS-фингерпринтинг и активно блокирует дата-центровые IP-адреса уже после нескольких запросов. В этом руководстве мы разберём, как скрейпить Yelp бизнес-листинги и отзывы, используя резидентные прокси с гео-таргетингом, и какие данные доступны без авторизации.

Что доступно без входа в систему

Без аккаунта Yelp вы можете получить доступ к следующим публичным данным на странице бизнеса /biz/<slug>:

  • Название бизнеса, адрес, телефон, категория
  • Общий рейтинг (1–5 звёзд) и количество отзывов
  • Часы работы по дням недели
  • Координаты (широта, долгота)
  • Тексты отзывов с именем автора, датой, рейтингом и текстом
  • Фотографии с подписями

Для сравнения, официальный Yelp Fusion API возвращает максимум 3 отзыва на бизнес-эндпоинте /reviews и требует регистрации приложения. Веб-страница Yelp показывает значительно больше отзывов через пагинацию, что делает веб-скрейпинг привлекательным для исследователей, нуждающихся в полных наборах отзывов.

Антибот-защита Yelp: PerimeterX/HUMAN и TLS-фингерпринтинг

Yelp использует несколько уровней защиты от автоматизированного доступа:

PerimeterX (HUMAN Security)

PerimeterX — это система антибот-защиты, которая устанавливает cookie _px3 и выполняет JavaScript sensor challenge — скрипт, собирающий данные о браузере: canvas fingerprint, WebGL-параметры, тайминги событий мыши и клавиатуры, список плагинов. Если sensor data не проходит проверку, Yelp возвращает HTTP 403 или CAPTCHA-страницу.

Ключевая проблема для скрейперов: PerimeterX ведёт IP-reputation scoring. Дата-центровые IP-адреса (AWS, DigitalOcean, OVH) получают низкий reputation score и блокируются быстрее — часто после 5–15 запросов с одного дата-центрового IP. Резидентные IP-адреса от реальных интернет-провайдеров имеют значительно более высокий trust score.

TLS-фингерпринтинг (JA3/JA4)

Yelp также анализирует TLS Client Hello-пакет для определения типа клиента. Библиотеки вроде Python requests или urllib3 имеют характерные TLS-паттерны, отличающиеся от реальных браузеров. Это означает, что даже с правильными заголовками User-Agent, не-браузерный клиент может быть обнаружен по TLS-отпечатку.

Для обхода TLS-фингерпринтинга разработчики часто используют:

  • curl-impersonate — модифицированный curl с TLS-отпечатком Chrome/Firefox
  • tls-client — Python-обёртка с настраиваемым JA3
  • Playwright/Puppeteer с stealth-плагинами для полного браузерного рендеринга

CAPTCHA после нескольких запросов

Даже при правильной настройке заголовков, после нескольких десятков запросов с одного IP-адреса Yelp может показать CAPTCHA. Это типичный паттерн для PerimeterX: первый порог — soft challenge (JavaScript), второй — hard CAPTCHA. Стратегия ротации IP-адресов критически важна для поддержания пропускной способности.

Почему резидентные прокси с US-гео необходимы для Yelp

Для скрейпинга Yelp требуются резидентные прокси по двум причинам:

  1. IP-reputation: Резидентные IP-адреса от реальных ISP (Comcast, AT&T, Verizon) имеют высокий trust score в PerimeterX, что снижает частоту блокировок.
  2. Локализация результатов: Yelp возвращает разные бизнес-листинги в зависимости от гео-расположения запроса. Для сбора данных о ресторанах в Остине, вам нужен IP из США, желательно из Техаса.

Дата-центровые прокси могут работать для единичных запросов, но не выдерживают массовый сбор данных. Мобильные прокси — отличная альтернатива с ещё более высоким trust score, но они дороже и медленнее.

Тип прокси Trust score PerimeterX Скорость Стоимость Подходит для Yelp
Дата-центровые Низкий Высокая Низкая Только единичные запросы
Резидентные Высокий Средняя Средняя Оптимальный выбор
Мобильные Очень высокий Низкая Высокая Для сложных случаев

Практическая реализация: Python с ProxyHat

Настройка прокси через ProxyHat gateway

ProxyHat предоставляет единый gateway для резидентных прокси с гибким гео-таргетингом. Формат username поддерживает флаги страны, города и сессии:

# Гео-таргетинг: США, Остин
http://user-country-US-city-austin:pass@gate.proxyhat.com:8080

# Sticky-сессия для пагинации
http://user-country-US-city-austin-session-abc123:pass@gate.proxyhat.com:8080

# Простая ротация (новый IP на каждый запрос)
http://user-country-US:pass@gate.proxyhat.com:8080

Пример 1: Сбор отзывов через review_feed и __INITIAL_STATE__

Yelp встраивает начальное состояние приложения в тег <script> с переменной __INITIAL_STATE__. Это JSON-объект, содержащий бизнес-данные и первые отзывы. Для пагинации отзывов Yelp использует внутренний endpoint /review_feed с параметром rl (review language) и sort_by.

import requests
import json
import re
import time
import random

PROXY = "http://user-country-US-city-austin-session-yelp001:pass@gate.proxyhat.com:8080"

PROXIES = {
    "http": PROXY,
    "https": PROXY,
}

HEADERS = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
    "Accept-Language": "en-US,en;q=0.9",
    "Accept-Encoding": "gzip, deflate, br",
    "Connection": "keep-alive",
    "Upgrade-Insecure-Requests": "1",
}

USER_AGENTS = [
    "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
    "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
]

def scrape_yelp_business(biz_slug):
    """Собирает бизнес-данные и отзывы со страницы /biz/<slug>"""
    url = f"https://www.yelp.com/biz/{biz_slug}"
    headers = HEADERS.copy()
    headers["User-Agent"] = random.choice(USER_AGENTS)

    try:
        resp = requests.get(url, headers=headers, proxies=PROXIES, timeout=30)
        if resp.status_code == 403:
            print(f"[!] Заблокировано PerimeterX (403) для {biz_slug}")
            return None
        if resp.status_code != 200:
            print(f"[!] HTTP {resp.status_code} для {biz_slug}")
            return None

        # Извлекаем __INITIAL_STATE__
        match = re.search(
            r"__INITIAL_STATE__=({.*?});</script>",
            resp.text,
            re.DOTALL
        )
        if not match:
            print(f"[!] __INITIAL_STATE__ не найден для {biz_slug}")
            return None

        state = json.loads(match.group(1))

        # Извлекаем бизнес-данные
        biz_data = state.get("biz", {}).get(biz_slug, {})
        business = {
            "name": biz_data.get("name", ""),
            "rating": biz_data.get("rating", 0),
            "review_count": biz_data.get("reviewCount", 0),
            "categories": [c.get("title", "") for c in biz_data.get("categories", [])],
            "address": biz_data.get("location", {}).get("displayAddress", []),
            "hours": biz_data.get("hours", []),
            "coordinates": biz_data.get("latitude"), biz_data.get("longitude"),
        }

        # Извлекаем первые отзывы
        reviews = state.get("review", {}).get("feed", {}).get("reviews", [])
        parsed_reviews = []
        for r in reviews[:3]:  # первые 3 отзыва из __INITIAL_STATE__
            parsed_reviews.append({
                "author": r.get("user", {}).get("markupDisplayName", ""),
                "rating": r.get("rating", 0),
                "date": r.get("localizedDate", ""),
                "text": r.get("comment", {}).get("text", "")[:200],  # truncated
            })

        business["reviews_sample"] = parsed_reviews
        return business

    except requests.exceptions.RequestException as e:
        print(f"[!] Ошибка запроса: {e}")
        return None

def scrape_review_feed(biz_slug, start=0, limit=20):
    """Собирает отзывы через review_feed endpoint (пагинация)"""
    url = f"https://www.yelp.com/biz/{biz_slug}/review_feed"
    params = {
        "rl": "en",
        "sort_by": "relevance_desc",
        "start": start,
        "limit": limit,
    }
    headers = HEADERS.copy()
    headers["User-Agent"] = random.choice(USER_AGENTS)
    headers["Accept"] = "application/json"
    headers["X-Requested-With"] = "XMLHttpRequest"

    try:
        resp = requests.get(
            url, headers=headers, params=params,
            proxies=PROXIES, timeout=30
        )
        if resp.status_code != 200:
            print(f"[!] review_feed вернул {resp.status_code}")
            return []

        data = resp.json()
        reviews = data.get("reviews", [])
        return [{
            "author": r.get("user", {}).get("markupDisplayName", ""),
            "rating": r.get("rating", 0),
            "date": r.get("localizedDate", ""),
            "text": r.get("comment", {}).get("text", "")[:200],
        } for r in reviews]

    except Exception as e:
        print(f"[!] Ошибка review_feed: {e}")
        return []

# Использование
if __name__ == "__main__":
    # Шаг 1: бизнес-данные + первые отзывы
    biz = scrape_yelp_business("house-of-prime-rib-san-francisco")
    if biz:
        print(json.dumps(biz, indent=2, ensure_ascii=False))

        # Шаг 2: пагинация отзывов
        all_reviews = biz.get("reviews_sample", [])
        for offset in range(20, 200, 20):
            print(f"[*] Загрузка отзывов {offset}–{offset+20}...")
            batch = scrape_review_feed("house-of-prime-rib-san-francisco", start=offset)
            if not batch:
                break
            all_reviews.extend(batch)
            time.sleep(random.uniform(2.0, 5.0))  # рандомная задержка

        print(f"\n[+] Всего собрано отзывов: {len(all_reviews)}")

В этом примере мы используем sticky-сессию (session-yelp001), чтобы все запросы в рамках пагинации шли с одного IP-адреса. Это важно: если IP меняется между запросами /biz/<slug> и /review_feed, PerimeterX может заподозрить несоответствие сессии.

Пример 2: Node.js через SOCKS5

Для Node.js можно использовать SOCKS5-агент через ProxyHat на порту 1080:

const { SocksProxyAgent } = require('socks-proxy-agent');
const axios = require('axios');

const SOCKS5_PROXY = 'socks5://user-country-US-city-austin-session-node01:pass@gate.proxyhat.com:1080';
const agent = new SocksProxyAgent(SOCKS5_PROXY);

const USER_AGENTS = [
  'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
  'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
];

async function scrapeYelpBusiness(bizSlug) {
  const url = `https://www.yelp.com/biz/${bizSlug}`;
  const ua = USER_AGENTS[Math.floor(Math.random() * USER_AGENTS.length)];

  try {
    const resp = await axios.get(url, {
      httpAgent: agent,
      httpsAgent: agent,
      timeout: 30000,
      headers: {
        'User-Agent': ua,
        'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
        'Accept-Language': 'en-US,en;q=0.9',
        'Accept-Encoding': 'gzip, deflate, br',
        'Connection': 'keep-alive',
      },
    });

    if (resp.status === 403) {
      console.error(`[!] Заблокировано PerimeterX (403)`);
      return null;
    }

    // Извлекаем __INITIAL_STATE__
    const match = resp.data.match(/__INITIAL_STATE__=({.*?});<\/script>/s);
    if (!match) {
      console.error('[!] __INITIAL_STATE__ не найден');
      return null;
    }

    const state = JSON.parse(match[1]);
    const bizData = state?.biz?.[bizSlug] || {};
    const reviews = state?.review?.feed?.reviews || [];

    return {
      name: bizData.name || '',
      rating: bizData.rating || 0,
      reviewCount: bizData.reviewCount || 0,
      categories: (bizData.categories || []).map(c => c.title || ''),
      reviewsSample: reviews.slice(0, 3).map(r => ({
        author: r?.user?.markupDisplayName || '',
        rating: r?.rating || 0,
        text: (r?.comment?.text || '').substring(0, 200),
      })),
    };
  } catch (err) {
    console.error(`[!] Ошибка: ${err.message}`);
    return null;
  }
}

// Использование
(async () => {
  const result = await scrapeYelpBusiness('house-of-prime-rib-san-francisco');
  console.log(JSON.stringify(result, null, 2));
})();

Пагинация, rate-limit pacing и sticky-сессии

Пагинация отзывов

Yelp загружает отзывы асинхронно через /review_feed. Параметр start управляет смещением, limit — количеством (обычно 20). Максимальная глубина пагинации составляет около 1000 отзывов на бизнес — после этого Yelp может перестать возвращать данные.

Rate-limit pacing

PerimeterX отслеживает частоту запросов с одного IP. Рекомендуемые темпы:

  • 1 запрос каждые 3–5 секунд с одного sticky-IP для безопасного режима
  • Не более 100 запросов с одной сессии перед ротацией
  • Случайные задержки (jitter) — избегайте равномерных интервалов, которые легко детектируются
# Паттерн безопасного rate-limiting
import random
import time

def safe_delay():
    """Случайная задержка 2–6 секунд между запросами"""
    delay = random.uniform(2.0, 6.0)
    time.sleep(delay)

def rotate_session():
    """Генерирует новый session ID для ротации IP"""
    import uuid
    return f"yelp-{uuid.uuid4().hex[:8]}"

# Ротация сессии каждые 50 запросов
request_count = 0
for biz_slug in business_slugs:
    if request_count % 50 == 0:
        session_id = rotate_session()
        proxy = f"http://user-country-US-city-austin-session-{session_id}:pass@gate.proxyhat.com:8080"
    scrape_yelp_business(biz_slug)
    safe_delay()
    request_count += 1

Sticky-сессии для пагинации

При сборе всех отзывов для одного бизнеса используйте одну sticky-сессию. Меняйте сессию только при переходе к следующему бизнесу. Это имитирует поведение реального пользователя, просматривающего отзывы одного заведения.

# Sticky-сессия: один бизнес = одна сессия
session_id = f"biz-{biz_slug}-001"
proxy = f"http://user-country-US-city-austin-session-{session_id}:pass@gate.proxyhat.com:8080"

# Все запросы пагинации идут через один IP
for offset in range(0, 500, 20):
    reviews = scrape_review_feed(biz_slug, start=offset)
    safe_delay()

Ротация User-Agent

PerimeterX анализирует заголовки запроса. Использование одного User-Agent для всех запросов создаёт детектируемый паттерн. Ротация между 5–10 актуальными браузерными User-Agent снижает риск:

  • Используйте только актуальные версии Chrome, Firefox, Safari
  • Согласовывайте User-Agent с другими заголовками (Sec-CH-UA для Chrome)
  • Избегайте редких или устаревших User-Agent

Типичные ошибки и edge cases

1. Игнорирование cookie _px3

PerimeterX устанавливает cookie _px3 при первом визите. Если ваш скрейпер не сохраняет и не отправляет этот cookie в последующих запросах, PerimeterX будет повторно инициировать sensor challenge, что быстро приводит к блокировке. Используйте requests.Session() для автоматического управления cookie.

2. Слишком высокая частота запросов

Даже с резидентными прокси, запросы без задержек вызовут блокировку. PerimeterX анализирует не только IP, но и поведенческие паттерны. Равномерные интервалы в 1 секунду так же подозрительны, как и отсутствие задержек.

3. Несоответствие гео и бизнеса

Если вы скрейпите бизнесы в Сан-Франциско через IP из Нью-Йорка, Yelp может вернуть другой набор результатов или показать предупреждение. Используйте гео-таргетинг по городу: city-san-francisco для бизнесов в SF.

4. Игнорирование robots.txt

Проверьте robots.txt Yelp перед скрейпингом. Хотя не все пути закрыты, уважение к директивам robots.txt — это хорошая практика и часть этического скрейпинга.

Настройка ProxyHat для скрейпинга Yelp

ProxyHat предоставляет резидентные прокси с гибким гео-таргетингом по странам и городам. Для скрейпинга Yelp рекомендуется:

  1. Используйте резидентные прокси с US-гео: user-country-US-city-san-francisco
  2. Включайте sticky-сессии для пагинации: session-{id}
  3. Ротируйте сессию каждые 50–100 запросов
  4. Используйте SOCKS5 (порт 1080) для Node.js-приложений

Подробная документация доступна на docs.proxyhat.com. Цены на резидентные прокси — на странице тарифов ProxyHat. Полный список доступных локаций — на странице локаций ProxyHat.

Дополнительные материалы по веб-скрейпингу доступны в нашем руководстве по веб-скрейпингу и руководстве по SERP-трекингу.

Этический скрейпинг: когда использовать официальные API

Скрейпинг публичных данных — это серая зона. Перед началом сбора данных с Yelp задайте себе следующие вопросы:

  • Доступны ли данные без входа? Если данные требуют авторизации, не пытайтесь обойти это — это нарушает ToS и может подпадать под CFAA.
  • Содержат ли данные персональную информацию? Имена авторов отзывов, их фотографии и профили — это персональные данные по GDPR. Избегайте сбора таких данных или анонимизируйте их.
  • Покрывает ли Fusion API ваши нужды? Если вам нужны базовые бизнес-данные и до 3 отзывов на бизнес, Fusion API — более надёжный и легальный путь.
  • Уважаете ли вы robots.txt? Директивы robots.txt — это не юридическое требование, но их соблюдение — стандарт индустрии.

Рекомендация: Если Yelp Fusion API покрывает ваши потребности (бизнес-поиск, базовая информация, 3 отзыва), используйте его. Это надёжнее, быстрее и юридически безопаснее. Веб-скрейпинг оправдан, когда вам нужны полные наборы отзывов для исследовательских целей, анализ тональности или обучение ML-моделей — но только с публичных страниц и в рамках применимого законодательства.

Key Takeaways

  • Публичные данные Yelp: бизнес-профили, рейтинги, категории, часы работы и тексты отзывов доступны без входа на /biz/<slug>. Fusion API ограничен 3 отзывами на бизнес.
  • Антибот-защита: PerimeterX/HUMAN использует cookie _px3, JavaScript sensor challenge и TLS-фингерпринтинг. Дата-центровые IP блокируются после 5–15 запросов.
  • Резидентные прокси с US-гео: необходимы для обхода IP-reputation scoring и получения локализованных бизнес-результатов. Используйте country-US-city-austin для гео-таргетинга.
  • Sticky-сессии: используйте одну сессию для всех запросов пагинации одного бизнеса, ротируйте каждые 50–100 запросов.
  • Rate-limit pacing: случайные задержки 2–6 секунд, ротация User-Agent, не более 100 запросов на сессию.
  • Этика и закон: уважайте ToS Yelp, robots.txt и GDPR. Избегайте данных, требующих авторизации. Рассмотрите Fusion API, если он покрывает ваши нужды.

FAQ

Что такое скрейпинг Yelp бизнес-листингов и отзывов?

Скрейпинг Yelp — это автоматизированный сбор публично доступных данных с веб-страниц Yelp: названий бизнесов, рейтингов, категорий, часов работы и текстов отзывов. В отличие от официального Fusion API, который возвращает максимум 3 отзыва на бизнес, веб-скрейпинг позволяет собирать полные наборы отзывов через пагинацию endpoint /review_feed. Данные встраиваются в страницу в виде JSON-объекта __INITIAL_STATE__.

Почему для скрейпинга Yelp важны прокси?

Yelp защищён PerimeterX (HUMAN Security), который блокирует дата-центровые IP-адреса после 5–15 запросов из-за низкого IP-reputation score. Резидентные прокси с IP-адресами реальных интернет-провайдеров имеют высокий trust score и значительно реже блокируются. Кроме того, гео-таргетинг прокси по США и конкретным городам необходим для получения локализованных бизнес-результатов, так как Yelp возвращает разные данные в зависимости от гео-расположения запроса.

Какой тип прокси лучше всего подходит для скрейпинга Yelp?

Резидентные прокси с гео-таргетингом по США — оптимальный выбор для скрейпинга Yelp. Они обеспечивают высокий IP-reputation score в PerimeterX, поддерживают гео-локализацию по городам (например, city-san-francisco) и позволяют использовать sticky-сессии для пагинации отзывов. Мобильные прокси имеют ещё более высокий trust score, но они дороже и медленнее. Дата-центровые прокси не рекомендуются — они блокируются практически сразу.

Как избежать блокировок при скрейпинге Yelp?

Используйте резидентные прокси с US-гео и sticky-сессиями, поддерживайте случайные задержки 2–6 секунд между запросами, ротируйте User-Agent между 5–10 актуальными браузерными строками, сохраняйте cookie _px3 через requests.Session(), и ротируйте сессию каждые 50–100 запросов. Избегайте равномерных интервалов между запросами — PerimeterX детектирует паттерны. При получении HTTP 403 сделайте паузу 10–15 минут и смените сессию.

Можно ли скрейпить Yelp легально?

Скрейпинг публично доступных данных (без входа в систему) с Yelp технически возможен, но юридическая ситуация зависит от юрисдикции. В США дело hiQ v. LinkedIn установило прецедент, что скрейпинг публичных данных может не нарушать CFAA, однако условия использования Yelp могут запрещать автоматизированный доступ. В ЕС сбор персональных данных (имена авторов отзывов) регулируется GDPR. Рекомендуется использовать официальный Fusion API, где это возможно, и консультироваться с юристом перед масштабным сбором данных.

Готовы начать?

Доступ к более чем 50 млн резидентных IP в 148+ странах с AI-фильтрацией.

Смотреть ценыРезидентные прокси
← Вернуться в Блог