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

В этом руководстве показано, как извлекать данные Yelp с помощью Python надёжным способом. Вы получаете отрисованные страницы результатов поиска через Crawling API, разбираете каждую карточку предприятия с помощью BeautifulSoup, чтобы извлечь название, рейтинг, количество отзывов, категорию, адрес и ссылку, затем обходите страницы, чтобы охватить весь набор результатов, и экспортируете в JSON или CSV. Всё здесь ограничено публичными данными о предприятиях, а раздел о легальности ближе к концу не является шаблонным текстом, поэтому прочитайте его, прежде чем применять это к реальным объёмам данных.

Что вы построите

Небольшой Python-скрапер, который принимает поисковый запрос и местоположение, получает отрисованную страницу результатов поиска Yelp через Crawling API и извлекает структурированную запись для каждого предприятия на странице. В качестве примера используется "Italian Restaurants" в "San Francisco, CA", и для каждого объявления мы извлекаем следующие поля:

  • Название предприятия основной идентификатор, отображаемый на карточке объявления.
  • Рейтинг совокупный звёздный рейтинг предприятия.
  • Количество отзывов сколько отзывов подкрепляют этот рейтинг.
  • Категория категории бизнеса, к которым относится объявление.
  • Адрес публичный район или улица, используемые для географического анализа.
  • Ссылка URL на собственную страницу предприятия на Yelp.

Почему обычный запрос не работает на Yelp

Вы можете направить библиотеку requests на поисковый URL Yelp и в удачный день получить какой-то HTML. Но быстро возникают две проблемы. Во-первых, Yelp отрисовывает большую часть содержимого страницы результатов поиска с помощью JavaScript, поэтому необработанный HTML, который получает обычный запрос, зачастую является оболочкой, которая ещё не содержит искомых карточек предприятий. Во-вторых, Yelp следит за трафиком, характерным для скраперов: ограничивает запросы по IP, показывает CAPTCHA запросам, которые выглядят автоматизированными, и блокирует адреса дата-центров, получающие страницы в плотном, машинном паттерне. Один запрос с вашего ноутбука может пройти, но несколько сотен с одного IP уже нет.

Поэтому скрапер, который действительно выполняет работу, нуждается в двух вещах: страница, отрисованная так, как её отрисовал бы настоящий браузер, и запросы, которые читаются как запросы от реального посетителя с доверенного IP. Вы можете построить это самостоятельно с помощью парка headless-браузеров и пула ротируемых резидентских прокси, но поддержка такого стека составляет большую часть работы. Crawling API сворачивает всё это в один вызов: вы отправляете ему URL, он отрисовывает JavaScript и маршрутизирует запрос через резидентские IP на стороне сервера, обрабатывает антибот-уровень и возвращает готовый HTML для разбора.

Какой токен использовать

Crawlbase предлагает два типа токенов. Обычный токен получает статический HTML; токен JavaScript (JS) сначала отрисовывает страницу в настоящем браузере. Поскольку Yelp формирует результаты поиска на стороне клиента, используйте здесь JS-токен, чтобы карточки предприятий присутствовали в получаемом HTML. Обычный токен подходит только для целей, которые поставляют данные в первоначальном ответе.

Предварительные требования

Сначала убедитесь в наличии нескольких вещей. Ни одна из них не займёт много времени.

Базовый Python. Вы должны уметь запускать скрипт и устанавливать пакеты с помощью pip. Если селекторы для вас новы, руководство по использованию BeautifulSoup в Python подробно рассматривает сторону разбора.

Python 3.8 или новее. Проверьте с помощью python --version. Если у вас его нет, установите с python.org или через дистрибутив вроде Anaconda.

Аккаунт Crawlbase и токен. Зарегистрируйтесь, откройте панель управления и скопируйте JavaScript-токен со страницы документации аккаунта. Вы получаете до 20 000 бесплатных запросов, карта не требуется. Относитесь к токену как к паролю и не включайте его в систему контроля версий.

Настройка проекта

Создайте виртуальную среду, чтобы зависимости оставались изолированными, затем установите библиотеки, необходимые скраперу.

bash
python --version

python -m venv yelp_env
source yelp_env/bin/activate

pip install crawlbase beautifulsoup4 pandas

В Windows активируйте среду с помощью yelp_env\Scripts\activate вместо строки source. Три зависимости выполняют работу: crawlbase является официальным клиентом для Crawling API, beautifulsoup4 разбирает возвращаемый HTML, чтобы вы могли извлекать каждое поле по CSS-селектору, а pandas в конце записывает результаты в CSV.

Шаг 1: Получение отрисованной страницы поиска

Начните с получения одной страницы результатов. Создайте URL поиска из запроса и местоположения, импортируйте класс CrawlingAPI, инициализируйте его с помощью токена и запросите URL с включённой отрисовкой JavaScript. Поиск на Yelp управляется двумя URL-параметрами: find_desc для категории бизнеса и find_loc для местоположения. Проверка статуса перед разбором делает сбои явными, а не скрытыми.

python
from urllib.parse import urlencode
from crawlbase import CrawlingAPI

api = CrawlingAPI({"token": "YOUR_CRAWLBASE_TOKEN"})

def build_url(query, location, start=0):
    base = "https://www.yelp.com/search?"
    params = {"find_desc": query, "find_loc": location, "start": start}
    return base + urlencode(params)

def crawl(page_url):
    response = api.get(page_url, {"ajax_wait": "true", "page_wait": "3000"})
    if response["headers"]["cb_status"] == "200":
        return response["body"].decode("latin1")
    print(f"Request failed: {response['headers']['cb_status']}")
    return None

if __name__ == "__main__":
    url = build_url("Italian Restaurants", "San Francisco, CA")
    html = crawl(url)
    print(html[:500] if html else "No HTML returned")

Параметры ajax_wait и page_wait указывают Crawling API отрисовать JavaScript и сделать небольшую паузу, чтобы карточки предприятий завершили загрузку до возврата HTML. Проверка статуса читает cb_status (legacy pc_status) из заголовков ответа, который является статусом Crawlbase для запроса, отдельным от вышестоящего HTTP-кода. Запустите скрипт с помощью python yelp_scraper.py, и вы должны увидеть реальную разметку результатов, а не страницу с проверкой или пустую оболочку. Это подтверждает, что путь получения данных работает, прежде чем вы напишете хоть один селектор.

Crawlbase Crawling API

Yelp отрисовывает результаты на стороне клиента и блокирует трафик, характерный для скраперов. Crawling API отрисовывает JavaScript в настоящем браузере и маршрутизирует каждый запрос через ротируемые резидентские IP на стороне сервера, обрабатывает CAPTCHA и блокировки, а затем возвращает готовый к разбору HTML, освобождая вас от необходимости самостоятельно поддерживать парк headless-браузеров и пул прокси. Сначала направьте его на публичную страницу поиска в рамках бесплатного тарифа.

Шаг 2: Разбор объявлений с помощью BeautifulSoup

Имея на руках отрисованную страницу результатов, загрузите её в BeautifulSoup и пройдитесь по карточкам результатов. Каждое предприятие находится в карточке под предсказуемым контейнером, а внутри него название, рейтинг, количество отзывов, категория, адрес и ссылка соответствуют своим собственным селекторам. Защитное чтение каждого поля с возвратом None, когда элемент отсутствует, предотвращает сбой всего прогона из-за одного отсутствующего значения.

python
from bs4 import BeautifulSoup

BASE = "https://www.yelp.com"

def text_of(node):
    return node.get_text(strip=True) if node else None

def extract_business(card):
    name = card.select_one('div[class*="businessName"] h3 > span > a')
    rating = card.select_one('div.css-volmcs + div.css-1jq1ouh > span:first-child')
    reviews = card.select_one('div.css-volmcs + div.css-1jq1ouh > span:last-child')
    category = card.select('div[class*="priceCategory"] div > p > span:first-child a')
    address = card.select_one('div[class*="priceCategory"] div > p > span:last-child')

    return {
        "name": text_of(name),
        "rating": text_of(rating),
        "review_count": text_of(reviews),
        "category": ", ".join(c.get_text(strip=True) for c in category) if category else None,
        "address": text_of(address),
        "link": BASE + name["href"] if name and name.get("href") else None,
    }

def extract_businesses(html):
    soup = BeautifulSoup(html, "html.parser")
    cards = soup.select('div[data-testid="serp-ia-card"]:not(.ABP)')
    return [extract_business(card) for card in cards]

Карточки выбираются с помощью div[data-testid="serp-ia-card"]:not(.ABP), что позволяет выбрать органические карточки результатов, пропуская рекламные варианты. Якорь businessName содержит как отображаемое название, так и относительный href на страницу этого предприятия на Yelp, поэтому название и ссылка берутся из одного элемента. Рейтинг и количество отзывов являются двумя промежутками, следующими за блоком рейтинга, а категория и адрес находятся внутри строки цены и категории. Вспомогательная функция text_of возвращает None, когда элемент отсутствует, вместо того чтобы генерировать ошибку при вызове .get_text() для пустого значения, что делает извлечение данных устойчивым, когда в карточке отсутствует какое-либо поле.

Селекторы устаревают

Имена классов Yelp (особенно хешированные токены css-*) генерируются и меняются без предупреждения, поэтому относитесь к этим селекторам как к начальному шаблону, а не как к контракту. Когда поле возвращает None для каждого объявления, повторно проверьте живую страницу результатов в инструментах разработчика браузера и обновите селектор. Привязки атрибутов data-testid и businessName, как правило, более стабильны, чем хешированные имена классов, поэтому предпочитайте их там, где это возможно. Периодическое обслуживание селекторов является нормой для любого производственного скрапера.

Шаг 3: Собираем всё вместе

Теперь объедините получение данных и разбор в один исполняемый скрипт для одной страницы. Создайте URL, получите отрисованный HTML, передайте его в парсер и выведите структурированные записи в формате JSON.

python
import json
from urllib.parse import urlencode
from crawlbase import CrawlingAPI
from bs4 import BeautifulSoup

api = CrawlingAPI({"token": "YOUR_CRAWLBASE_TOKEN"})
BASE = "https://www.yelp.com"

def build_url(query, location, start=0):
    params = {"find_desc": query, "find_loc": location, "start": start}
    return BASE + "/search?" + urlencode(params)

def crawl(page_url):
    response = api.get(page_url, {"ajax_wait": "true", "page_wait": "3000"})
    if response["headers"]["cb_status"] == "200":
        return response["body"].decode("latin1")
    print(f"Request failed: {response['headers']['cb_status']}")
    return None

def text_of(node):
    return node.get_text(strip=True) if node else None

def extract_business(card):
    name = card.select_one('div[class*="businessName"] h3 > span > a')
    rating = card.select_one('div.css-volmcs + div.css-1jq1ouh > span:first-child')
    reviews = card.select_one('div.css-volmcs + div.css-1jq1ouh > span:last-child')
    category = card.select('div[class*="priceCategory"] div > p > span:first-child a')
    address = card.select_one('div[class*="priceCategory"] div > p > span:last-child')
    return {
        "name": text_of(name),
        "rating": text_of(rating),
        "review_count": text_of(reviews),
        "category": ", ".join(c.get_text(strip=True) for c in category) if category else None,
        "address": text_of(address),
        "link": BASE + name["href"] if name and name.get("href") else None,
    }

def extract_businesses(html):
    soup = BeautifulSoup(html, "html.parser")
    cards = soup.select('div[data-testid="serp-ia-card"]:not(.ABP)')
    return [extract_business(card) for card in cards]

def main():
    url = build_url("Italian Restaurants", "San Francisco, CA")
    html = crawl(url)
    if not html:
        return
    data = extract_businesses(html)
    print(json.dumps(data, indent=2))

if __name__ == "__main__":
    main()

Как выглядит вывод

Запустите полный скрипт с помощью python yelp_scraper.py, и вы получите чистый список структурированных записей, готовых для записи в JSON, CSV или базу данных.

json
[
  {
    "name": "Bella Trattoria",
    "rating": "4.3",
    "review_count": "(1.9k reviews)",
    "category": "Italian, Bars, Pasta Shops",
    "address": "Inner Richmond",
    "link": "https://www.yelp.com/biz/bella-trattoria-san-francisco"
  },
  {
    "name": "Sotto Mare",
    "rating": "4.3",
    "review_count": "(5.2k reviews)",
    "category": "Seafood, Italian, Bars",
    "address": "North Beach/Telegraph Hill",
    "link": "https://www.yelp.com/biz/sotto-mare-san-francisco"
  }
]

Любое поле, которое карточка не содержит, возвращается как null, что ожидаемо и именно поэтому парсер читает каждое поле защитно, а не предполагает наличие каждого ключа. Количество отзывов приходит в виде строки отображения, такой как "(1.9k reviews)"; если вам нужно чистое целое число для анализа, удалите скобки и разверните суффикс k в последующем проходе очистки.

Шаг 4: Обработка пагинации по страницам результатов

Одна страница является демонстрацией; реальная работа охватывает весь набор результатов. Yelp разбивает результаты поиска на страницы через URL-параметр start, который устанавливает смещение первого результата на странице и увеличивается с шагом десять. Таким образом, обход страниц представляет собой цикл по диапазону смещений: 0, 10, 20 и так далее. Те же функции build_url и extract_businesses переносятся без изменений, поэтому пагинация является просто внешним циклом, который делает паузу между запросами и записывает объединённый результат в JSON и CSV.

python
import json
import time
import pandas as pd

def scrape_all_pages(query, location, max_pages=5):
    all_rows = []
    for page in range(max_pages):
        start = page * 10
        url = build_url(query, location, start)
        html = crawl(url)
        if not html:
            print(f"Stopping at offset {start}: no HTML")
            break
        rows = extract_businesses(html)
        if not rows:
            print(f"No results at offset {start}; reached the end")
            break
        all_rows.extend(rows)
        print(f"Offset {start}: {len(rows)} businesses")
        time.sleep(2)
    return all_rows

if __name__ == "__main__":
    rows = scrape_all_pages("Italian Restaurants", "San Francisco, CA", max_pages=5)

    with open("yelp_businesses.json", "w") as f:
        json.dump(rows, f, indent=2)

    pd.DataFrame(rows).to_csv("yelp_businesses.csv", index=False)
    print(f"Saved {len(rows)} businesses to JSON and CSV")

Две детали делают этот цикл пригодным для производственного использования. Он останавливается досрочно, когда страница не возвращает предприятий, чтобы вы не тратили запросы впустую после последней реальной страницы, и делает паузу на две секунды между запросами, чтобы прогон не поступал как один плотный поток. Шаг экспорта записывает оба формата из одного и того же списка словарей: json.dump для структурированного файла и pandas для CSV, который открывается прямо в таблице. Настройте max_pages и паузу под свои объёмы; чем медленнее вы идёте, тем меньше внимания привлекаете.

Оставаться незаблокированным

Даже при том, что Crawling API берёт на себя отрисовку, ротацию IP и антибот-уровень, несколько привычек поддерживают здоровье прогона, и они применимы к любой цели в каталоге местного бизнеса.

  • Делайте паузы между запросами. Задержка выше не является украшением. Плотный цикл является самым быстрым способом получить ограничение скорости; распределение запросов читается гораздо больше похожим на обычный трафик.
  • Опирайтесь на ротацию. Пул резидентских IP распределяет запросы по многим адресам реальных пользователей, чтобы ни один из них не превысил лимит скорости. Crawling API делает это за вас; если вы строите собственный стек, именно эту часть нужно сделать правильно.
  • Следите за кодами статуса. Прогон, который начинает возвращать проверки или ошибки, сообщает вам, что текущая скорость слишком агрессивна. Воспринимайте это как сигнал к снижению скорости, а не как шум, который нужно игнорировать.

Более широкую инструкцию смотрите в статье о том, как скрапить сайты без блокировки. Если ваша цель состоит в более широкой категории каталогов местного бизнеса, а не в Yelp конкретно, руководство по скрапингу объявлений местного бизнеса и пошаговое руководство по Yellow Pages охватывают смежные источники с тем же подходом. А если вам нужен текст отзывов за каждым предприятием, а не сводка результатов поиска, смотрите статью о том, как сканировать отзывы Yelp, принимая во внимание примечания о конфиденциальности ниже.

Законно ли скрапить Yelp?

Разрешён ли скрапинг Yelp, зависит от условий использования сайта, вашей юрисдикции и того, что вы делаете с данными. Ни один из приведённых здесь кодов не меняет этого; он только обеспечивает техническую работу. Прочитайте Условия использования Yelp и его robots.txt и относитесь к обоим как к границе того, что вы собираете и с какой скоростью. Условия Yelp ограничивают автоматизированный доступ, поэтому для чего-либо сверх небольшого исследования правильным путём является официальный канал Yelp: Yelp Fusion API предлагает данные о предприятиях и поиске на условиях, поддерживаемых Yelp, что является более безопасным и устойчивым вариантом, чем скрапинг внешнего интерфейса.

Если вы всё же собираете данные с публичных страниц, ограничьте объём только публичными данными о предприятиях: название предприятия, совокупный рейтинг, количество отзывов, категория, публичный район или адрес и ссылка на объявление. Сами отзывы заслуживают более чёткой границы. Совокупные рейтинги и количество отзывов являются фактами на уровне предприятия, но текст отдельного отзыва и имя написавшего его человека являются персональными данными. Относитесь к идентификаторам рецензентов как к персональным данным: не создавайте профили отдельных лиц, не публикуйте отзыв человека, привязанный к его имени, и применяйте обязательства по GDPR или CCPA там, где в поле зрения попадают персональные данные.

Не менее важно то, чего этот подход не охватывает. Он не затрагивает ничего за логином и не обходит аутентификацию или какой-либо контроль доступа для достижения закрытого контента; это выходит за рамки данного руководства и противоречит условиям сайта. Уважайте заявленные ограничения скорости Yelp, поддерживайте разумный объём запросов, чтобы не перегружать его серверы, и если вы планируете хранить, обогащать или коммерчески использовать данные Yelp, предпочтите Fusion API и проверьте применимые к вам правила, а не считайте, что «публичный» означает «без ограничений».

Итоги

Ключевые выводы

  • Yelp является структурированным каталогом. Каждый результат поиска является карточкой с названием, рейтингом, количеством отзывов, категорией, публичным адресом и ссылкой, управляемой URL-параметрами find_desc и find_loc.
  • Обычный запрос дважды даёт сбой. Yelp отрисовывает результаты на стороне клиента и блокирует трафик, характерный для скраперов; Crawling API отрисовывает JavaScript, маршрутизирует через резидентские IP и возвращает готовый к разбору HTML в одном вызове.
  • BeautifulSoup выполняет извлечение. Сопоставьте название, рейтинг, количество отзывов, категорию, адрес и ссылку с текущими селекторами, читайте каждое поле защитно и ожидайте, что хешированные имена классов css-* будут меняться.
  • Пагинация является циклом по смещению start. Шаг десять, повторное использование того же парсера, досрочная остановка на пустой странице, пауза между запросами и экспорт в JSON и CSV.
  • Держитесь публичных данных о предприятиях. Уважайте ToS и robots.txt, относитесь к идентификаторам рецензентов как к персональным данным, никогда не прикасайтесь к контенту за логином и предпочитайте официальный Yelp Fusion API для всего, что выходит за рамки небольшого исследования.

Часто задаваемые вопросы

Какой токен мне нужен для Yelp: обычный или JS?

JavaScript-токен. Yelp формирует карточки результатов поиска на стороне клиента, поэтому получение с обычным токеном часто возвращает HTML-оболочку без предприятий в ней. JS-токен сначала отрисовывает страницу в настоящем браузере, что и обеспечивает наличие карточек в HTML, который вы разбираете. Сочетайте его с параметрами ajax_wait и page_wait, чтобы содержимое успело загрузиться до возврата ответа.

Как обрабатывать пагинацию на Yelp?

Yelp предоставляет смещение результатов через URL-параметр start, который увеличивается с шагом десять, поэтому вы перебираете диапазон смещений (0, 10, 20 и так далее), создаёте URL для каждой страницы и запускаете тот же парсер для каждой. Остановитесь, когда страница вернёт ноль предприятий, что означает конец набора результатов, и делайте паузу на пару секунд между запросами, чтобы прогон не поступал как один поток.

Мои селекторы возвращают None. Что изменилось?

Почти наверняка разметка Yelp. Хешированные имена классов, такие как css-volmcs и css-1jq1ouh, генерируются и меняются без предупреждения, поэтому селекторы, работавшие в прошлом месяце, могут сломаться. Повторно проверьте живую страницу результатов в инструментах разработчика браузера и обновите их, отдавая предпочтение более стабильным привязкам data-testid и businessName там, где это возможно. Периодическое обслуживание селекторов является нормой для любого производственного скрапера.

Законно ли скрапить отзывы Yelp?

Совокупные рейтинги и количество отзывов являются фактами на уровне предприятия, которые вы можете использовать для анализа, но текст отдельного отзыва и имя рецензента являются персональными данными. Не создавайте профили отдельных лиц и не публикуйте отзыв человека, привязанный к его личности, и применяйте GDPR или CCPA там, где задействованы персональные данные. Для данных об отзывах в любом масштабе Yelp Fusion API является путём, который поддерживает Yelp, и это более безопасный выбор, чем скрапинг публичных страниц.

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

Поддерживайте низкую скорость запросов на IP, делайте паузы между запросами и маршрутизируйте через ротируемые резидентские IP, чтобы ни один адрес не превысил лимит скорости. Crawling API управляет отрисовкой, ротацией и антибот-уровнем за вас; если вы строите собственный стек, именно в эту часть стоит инвестировать. Следите за кодами статуса и снижайте скорость в момент, когда начинаете видеть проверки.

Могу ли я экспортировать собранные данные в Excel?

Да. Скрапер создаёт список словарей, который pandas превращает в таблицу в двух строках: pd.DataFrame(rows).to_excel("yelp_businesses.xlsx", index=False). Поскольку все записи имеют одинаковые ключи, столбцы выстраиваются чисто, и та же структура экспортируется так же легко в CSV, который уже записывает скрипт, или в таблицу базы данных.

Начать создавать

Обходите любой сайт в масштабе, без борьбы с инфраструктурой.

Crawlbase берёт на себя прокси, отпечатки и CAPTCHA, чтобы ваша команда выпускала конвейеры данных вместо поддержки обвязки краулинга. 1 000 запросов бесплатно, без карты.

Самообслуживание · Звонок отдела продаж не требуется · Доступны корпоративные объёмы краулинга