Yelp хранит сотни миллионов пользовательских отзывов о местных компаниях, и эти публичные данные представляют собой один из самых ценных сигналов в открытом интернете о том, как воспринимается тот или иной ресторан, магазин или услуга. Аналитики используют их для отслеживания настроений за период, операторы бизнеса читают их, чтобы выявить повторяющиеся жалобы, а исследователи сравнивают целые категории заведений. Полезная информация находится прямо на странице каждого бизнеса: звёздный рейтинг, текст отзыва и дата его публикации.
В этом руководстве показано, как crawl and scrape Yelp reviews с помощью JavaScript и Node.js с использованием cheerio. Вы создадите небольшой рабочий скрапер, который получает публичную страницу отзывов о бизнесе через Crawling API, парсит рейтинг, текст и дату каждого отзыва, обрабатывает пагинацию и экспортирует результат в JSON и CSV. Всё руководство ограничено публичными данными об отзывах и ориентировано на агрегированный анализ, а не на профилирование отдельных рецензентов. Прочитайте раздел о законности в конце, прежде чем направлять скрапер на реальные объёмы данных.
Что вы создадите
Скрипт Node.js, который принимает публичный URL бизнеса на Yelp, получает отрисованный HTML через Crawling API и извлекает структурированную запись для каждого отзыва на странице. В качестве текущего примера используется страница ресторана, и для каждого отзыва извлекаются следующие поля:
- Rating звёздный рейтинг рецензента, например "4 out of 5".
- Text публичный текст отзыва, та часть, которую вы фактически анализируете.
- Date дата публикации отзыва, используемая для сортировки и анализа трендов.
Обратите внимание на то, чего нет в этом списке. Мы намеренно не собираем и не привязываемся к имени рецензента, ссылке на профиль, фотографии или местоположению. Это персональные данные, и весь смысл данного руководства состоит в агрегированном анализе: тональность, повторяющиеся темы, распределение рейтингов по времени. Рассматривайте отзыв как рейтинг, блок текста и дату, но не как досье на конкретного человека.
Почему обычный запрос не работает на Yelp
Если вы запросите URL бизнеса на Yelp с помощью обычного HTTP-клиента, вы редко получите список отзывов. Против вас работают два фактора. Во-первых, Yelp формирует список отзывов в браузере с помощью JavaScript, поэтому исходный HTML представляет собой практически пустую оболочку, пока не выполнятся скрипты страницы. Во-вторых, Yelp агрессивно помечает автоматизированный трафик: IP-адреса дата-центров и шаблоны запросов, не похожие на реальный браузер, подвергаются проверке CAPTCHA, ограничению частоты запросов или блокировке ещё до того, как они получают доступ к отрисованным отзывам.
Поэтому рабочий скрапер Yelp должен совмещать два требования в одном запросе: браузер, который реально отрисовывает страницу, и IP-адрес, который платформа воспринимает как настоящего пользователя. Вы можете собрать всё это самостоятельно, используя headless-браузер и пул ротируемых резидентских прокси, но объединение и поддержка этих компонентов и составляет основную часть работы. Crawling API объединяет оба в один вызов: вы передаёте ему URL, он отрисовывает страницу за надёжным IP-адресом и возвращает готовый HTML для парсинга с помощью cheerio. Yelp использует JavaScript-рендеринг, поэтому мы запрашиваем его с включённым рендерингом.
Всё нижеследующее устроено так, чтобы результат был полезен в массовом виде и бесполезен как профиль отдельного человека. Мы извлекаем рейтинг, текст и дату, а личность рецензента оставляем на странице. Если для вашего анализа действительно необходимо взвешивание по рецензенту, хешируйте или псевдонимизируйте идентификатор при сборе данных, чтобы сохранённая запись никогда не связывала текст с именем конкретного человека.
Предварительные требования
Перед написанием кода вам понадобится несколько вещей. Ни одна из них не займёт много времени.
Базовые знания JavaScript и Node.js. Вы должны уметь писать и запускать скрипты Node и устанавливать пакеты с помощью npm. Если вы новичок в Node, руководство по созданию веб-скрапера с Node.js охватывает основы, которые предполагает данный учебник.
Node.js версии 16 или новее. Проверьте свою версию командой node --version. Если у вас её нет, установите с официального сайта Node.js или через менеджер версий, например nvm.
Аккаунт и токен Crawlbase. Зарегистрируйтесь, откройте панель управления и скопируйте свой токен для JavaScript-запросов. Бесплатный тариф предоставляет до 20 000 запросов без привязки карты. Поскольку Yelp требует рендеринга, используйте JavaScript-токен, а не обычный. Относитесь к токену как к паролю: он аутентифицирует ваши запросы, поэтому не добавляйте его в систему контроля версий.
Настройка проекта
Создайте папку проекта, инициализируйте её и установите две библиотеки, необходимые скраперу.
node --version mkdir yelp-reviews && cd yelp-reviews npm init -y npm install crawlbase cheerio
Две зависимости выполняют основную работу: crawlbase является официальным клиентом Node для Crawling API, а cheerio парсит возвращаемый HTML с помощью API в стиле jQuery, позволяя извлекать отдельные поля по CSS-селекторам. В устаревшей версии этого руководства использовались request и cheerio; мы сохраняем cheerio и тот же подход с селекторами, заменяя прямой HTTP-вызов официальным клиентом, который берёт на себя рендеринг и повторные попытки. Создайте файл yelp-scraper.js в этой папке и добавьте код из шагов ниже.
Шаг 1: Получение отрисованной страницы отзывов
Начните с получения готовой страницы. Импортируйте класс CrawlingAPI, инициализируйте его вашим JavaScript-токеном и запросите URL бизнеса. Yelp отрисовывает отзывы на стороне клиента, поэтому передайте pageWait, чтобы дать скриптам время заполнить список до того, как API захватит HTML. Проверка кода состояния перед парсингом делает ошибки явными, а не скрытыми.
const { CrawlingAPI } = require('crawlbase'); const api = new CrawlingAPI({ token: 'YOUR_CRAWLBASE_TOKEN' }); const yelpPageURL = 'https://www.yelp.com/biz/sushi-yasaka-new-york'; api .get(yelpPageURL, { pageWait: 3000 }) .then((response) => { if (response.statusCode === 200) { console.log(response.body.slice(0, 500)); } }) .catch((error) => console.error('API request error:', error));
Запустите скрипт командой node yelp-scraper.js, и вы должны увидеть настоящую разметку Yelp в начале тела ответа, а не пустую оболочку. Это подтверждает, что рендеринг работает ещё до написания первого селектора. Пример URL указывает на одну публичную страницу ресторана; замените её на любую другую публичную страницу бизнеса, и тот же поток применяется.
Первый запрос только что вернул полностью отрисованную страницу Yelp без headless-браузера и прокси на вашей стороне. Crawling API запускает страницу в настоящем браузере, ждёт заполнения списка отзывов, ротирует резидентские IP-адреса на стороне сервера и решает CAPTCHA, которые Yelp подбрасывает скраперам, так что вы получаете готовый HTML из одного вызова. Сначала направьте его на одну публичную страницу бизнеса в бесплатном тарифе.
Шаг 2: Парсинг каждого отзыва с cheerio
Получив отрисованный HTML, загрузите его в cheerio и обойдите блоки отзывов. Yelp размещает каждый отзыв в повторяющемся контейнере, поэтому вы выбираете все отзывы, а затем читаете рейтинг, текст и дату из каждого. Устаревшие селекторы для этой страницы были .review.review--with-sidebar для контейнера отзыва и .review-content p для текста тела; мы оставляем их и добавляем рейтинг и дату. Защитное чтение каждого поля не позволяет одному пропущенному значению остановить выполнение.
const cheerio = require('cheerio'); function parseReviews(html) { const $ = cheerio.load(html); const reviews = []; // Legacy container selector, kept faithfully const blocks = $('.review.review--with-sidebar'); blocks.each((index, element) => { const block = $(element); // Public review text const text = block.find('.review-content p').text().trim(); // Rating: read it from the star widget's aria/title text const ratingLabel = block.find('[role="img"][aria-label*="star"]').attr('aria-label') || block.find('.i-stars').attr('title') || ''; const ratingMatch = ratingLabel.match(/([\d.]+)\s*star/i); const rating = ratingMatch ? parseFloat(ratingMatch[1]) : null; // Date the review was posted const date = block .find('.review-content .rating-qualifier, span[class*="date"]') .first() .text() .trim(); if (text) { reviews.push({ rating, date, text }); } }); return reviews; }
Несколько деталей обеспечивают точность. Текст отзыва берётся из .review-content p, точно как в оригинальном руководстве. Рейтинг на Yelp не является обычным текстом: он находится в элементе виджета звёзд, чей aria-label или title содержит что-то вроде "4 star rating", поэтому небольшое регулярное выражение извлекает число. Дата располагается в том же блоке review-content. Каждая запись содержит только { rating, date, text } без имени рецензента, это намеренно. Если у отзыва нет текста тела, мы пропускаем его, а не сохраняем пустую строку.
Имена классов Yelp меняются без предупреждения, и точные селекторы рейтинга и даты, приведённые выше, могут потребовать обновления на живой странице. Относитесь к ним как к стартовому шаблону, а не как к контракту. Когда поле возвращается пустым, повторно осмотрите живую страницу в DevTools браузера и обновите селектор. Если хотите освежить навыки построения устойчивых селекторов, см. руководство по crawling JavaScript websites. Периодическое обслуживание селекторов является нормой для любого рабочего скрапера.
Шаг 3: Обработка пагинации отзывов
Одна страница показывает только первую порцию отзывов. Yelp разбивает остальные по страницам с помощью параметра запроса start, который увеличивается шагами (обычно по 10 отзывов на страницу): ?start=10 это вторая страница, ?start=20 третья, и так далее. Чтобы собрать все отзывы о бизнесе, вы проходите по этим смещениям до тех пор, пока страница не вернёт ни одного блока отзыва, получая и парсируя каждую с уже написанными функциями.
async function crawlPage(pageUrl) { const response = await api.get(pageUrl, { pageWait: 3000 }); if (response.statusCode === 200) return response.body; console.error(`Request failed: ${response.statusCode}`); return null; } async function crawlAllReviews(businessUrl, maxPages = 5) { const all = []; for (let page = 0; page < maxPages; page++) { const start = page * 10; const url = `${businessUrl}?start=${start}`; const html = await crawlPage(url); if (!html) break; const pageReviews = parseReviews(html); if (pageReviews.length === 0) break; // no more reviews all.push(...pageReviews); // Pace requests so we stay a polite visitor await new Promise((r) => setTimeout(r, 2000)); } return all; }
Цикл останавливается на первой пустой странице, при ответе не 200, или когда достигает maxPages, поэтому он никогда не зависает. Двухсекундная пауза между запросами поддерживает умеренную частоту запросов, что на сложной цели вроде Yelp важнее скорости. Держите maxPages небольшим во время разработки и увеличивайте только после того, как результат парсинга выглядит правильно.
Шаг 4: Сборка полного скрипта с экспортом JSON и CSV
Теперь объедините получение, парсинг и пагинацию в один работоспособный скрипт, а затем запишите агрегированные записи на диск в форматах JSON и CSV. CSV содержит только рейтинг, дату и текст, то есть три поля, необходимые для анализа тональности и тем.
const fs = require('fs'); const { CrawlingAPI } = require('crawlbase'); const cheerio = require('cheerio'); const api = new CrawlingAPI({ token: 'YOUR_CRAWLBASE_TOKEN' }); function toCsv(rows) { const headers = ['rating', 'date', 'text']; const escape = (value) => `"${String(value ?? '').replace(/"/g, '""')}"`; const lines = [headers.join(',')]; for (const row of rows) { lines.push(headers.map((h) => escape(row[h])).join(',')); } return lines.join('\n'); } async function main() { const businessUrl = 'https://www.yelp.com/biz/sushi-yasaka-new-york'; const reviews = await crawlAllReviews(businessUrl, 5); if (reviews.length === 0) { console.log('No reviews parsed; check your selectors.'); return; } fs.writeFileSync('reviews.json', JSON.stringify(reviews, null, 2)); fs.writeFileSync('reviews.csv', toCsv(reviews)); console.log(`Saved ${reviews.length} reviews to JSON and CSV`); } main();
Вставьте функции parseReviews, crawlPage и crawlAllReviews из предыдущих шагов в тот же файл, чтобы main могла их вызвать. Запустите командой node yelp-scraper.js и получите два файла: reviews.json с полными структурированными записями и reviews.csv, готовый для открытия в таблице или передачи в аналитический конвейер. Вспомогательная функция toCsv заключает каждое поле в кавычки и удваивает все встроенные кавычки, что важно здесь, поскольку текст отзывов длинный и часто содержит запятые и переносы строк.
Как выглядит результат
Файл JSON содержит один объект на каждый отзыв с рейтингом, датой и текстом. Идентификатор рецензента отсутствует, что именно то, что нужно для агрегированного анализа.
[ { "rating": 5, "date": "3/14/2024", "text": "Consistently fresh fish and quick service at lunch. The omakase set is great value for the quality." }, { "rating": 3, "date": "2/2/2024", "text": "Good food but the wait was long on a weekend. Worth it if you can get there early." } ]
CSV отражает те же строки с заголовком, так что он сразу открывается в Excel, Google Sheets или любом конвейере данных, принимающем файлы с разделителями.
rating,date,text "5","3/14/2024","Consistently fresh fish and quick service at lunch. The omakase set is great value for the quality." "3","2/2/2024","Good food but the wait was long on a weekend. Worth it if you can get there early."
Превращение отзывов в агрегированные выводы
Смысл сбора отзывов в том, что вы узнаёте в массовом виде, а не из отдельной записи. После того как данные находятся в JSON или CSV, вы можете рассчитать распределение рейтингов, отслеживать средний рейтинг по месяцам с помощью поля даты и пропустить текст отзывов через анализ тональности или кластеризацию тем, чтобы выявить повторяющиеся сюжеты: время ожидания, ценообразование, сервис. Ни одна из этих задач не требует имени рецензента, и исключение имён из конвейера одновременно чище и безопаснее.
Если вы планируете передавать текст в модель, сначала нормализуйте его: удалите шаблонные фразы, сожмите пробелы и определитесь с обработкой отзывов не на английском языке. Руководство по структурированию и очистке данных, полученных с помощью скрапинга, для AI и ML проходит через этот шаг. Для более широкого шаблона сбора и сравнения отзывов из разных источников руководство по скрапингу отзывов покупателей охватывает тот же принцип "агрегирование прежде всего" для других платформ отзывов.
Оставаться незаблокированным
Даже при обработанном рендеринге Yelp отслеживает трафик, похожий на скрапинг. Несколько привычек поддерживают работоспособность запуска, и они применимы к любой сложной коммерческой цели.
- Соблюдайте темп запросов. Поддерживайте задержку между постраничными запросами, как это делает скрипт. Распределение запросов во времени является наиболее значимым фактором для соблюдения лимитов Yelp.
- Полагайтесь на ротацию. Пул резидентских IP-адресов распределяет запросы по множеству адресов реальных пользователей, так что ни один из них не превышает лимит и не вызывает CAPTCHA. Crawling API делает это за вас; если вы используете собственный стек, именно здесь нужно сделать всё правильно.
- Читайте коды состояния. Запуск, который начинает возвращать проверки или ответы не 200, сообщает вам, что текущей частоты или уровня IP уже недостаточно. Воспринимайте это как сигнал снизить скорость, а не как шум, который нужно игнорировать.
Более полное руководство см. в статье о том, как скрапить сайты, не попадая в блок.
Законно ли скрапить отзывы Yelp?
Допустимость скрапинга Yelp зависит от условий использования сервиса, вашей юрисдикции и того, что вы делаете с данными. Условия использования Yelp явно запрещают копирование или скрапинг контента с сайта вручную или с помощью ботов, расширений или программного обеспечения, поэтому автоматизированный сбор данных может противоречить этим условиям независимо от того, насколько осторожен ваш инструментарий. Ни один из приведённых здесь кодов не меняет этого: он просто заставляет техническую часть работать. Прочтите Условия использования Yelp и его robots.txt и рассматривайте оба документа как границу того, что вы собираете. Наиболее чистый путь для любого реального объёма или коммерческого использования, это официальный Yelp Fusion API, который предоставляет данные о бизнесе и отзывах на чётких, санкционированных условиях.
Текст отзывов и звёздные рейтинги, отображаемые на странице бизнеса, являются публичными, но люди, написавшие их, не являются анонимными с точки зрения законодательства о конфиденциальности. Имена рецензентов, ссылки на профили, фотографии и местоположения являются персональными данными. Согласно GDPR и CCPA, обработка таких данных требует правового основания, а физические лица сохраняют права на них. Именно поэтому данный учебник намеренно собирает только рейтинг, текст и дату, и именно поэтому мы рекомендуем не хранить, не индексировать и не публиковать повторно отзывы человека, связанные с его личностью. Сохраняйте анализ агрегированным: тональность, темы, тренды рейтингов, но никогда не создавайте профиль отдельного рецензента.
Несколько правил, которых стоит придерживаться. Собирайте только публичный контент отзывов, скрапите в медленном и уважительном темпе и поддерживайте объём запросов достаточно низким, чтобы не нагружать серверы Yelp. Не трогайте ничего за логином. Не перераспределяйте защищённый авторским правом контент Yelp, включая медиаматериалы отзывов, как свой собственный. Если вы работаете в юрисдикции GDPR или CCPA или собираете данные о людях в такой юрисдикции, и ваш сценарий использования затрагивает персональные данные, проконсультируйтесь с юристом перед тем, как продолжить. В случае сомнений правильный путь, это Yelp Fusion API или соглашение об обмене данными, а не более хитрый скрапер.
Ключевые выводы
- Yelp отрисовывает отзывы на стороне клиента и жёстко блокирует. Обычный запрос возвращает пустую оболочку или CAPTCHA, поэтому необходимо отрисовать страницу за надёжным IP-адресом, прежде чем парсить её.
- Crawling API делает и то и другое в одном вызове. Он отрисовывает страницу с JavaScript-токеном, ждёт загрузки списка отзывов, ротирует резидентские IP-адреса и решает CAPTCHA, возвращая готовый HTML для парсинга с cheerio.
-
Парсите только рейтинг, текст и дату. Используйте устаревшие селекторы
.review.review--with-sidebarи.review-content p, добавьте рейтинг виджета звёзд и дату, и обходите смещениеstartдля пагинации. - Сохраняйте агрегированность и конфиденциальность. Не включайте имена рецензентов в запись; анализируйте тональность, темы и тренды рейтингов, но никогда не создавайте профиль отдельного человека и не публикуйте отзыв, связанный с человеком.
- Предпочитайте официальный API для больших объёмов. Соблюдайте ToS и robots.txt Yelp, учитывайте GDPR и CCPA при работе с персональными данными и используйте Yelp Fusion API или соглашение об обмене данными для коммерческого или крупномасштабного использования.
Часто задаваемые вопросы
Можно ли скрапить отзывы Yelp законно?
Это зависит от Условий использования Yelp, вашей юрисдикции и вашего сценария использования данных. Условия Yelp ограничивают скрапинг его контента, поэтому автоматизированный сбор может противоречить им. Текст отзывов и рейтинги являются публичными, но личность рецензента представляет собой персональные данные, защищённые законами вроде GDPR и CCPA. Собирайте только публичный контент отзывов, сохраняйте агрегированность, скрапите медленно и предпочитайте официальный Yelp Fusion API для любого реального объёма или коммерческого использования.
Почему обычный запрос возвращает неполные данные с Yelp?
Потому что Yelp отрисовывает список отзывов на стороне клиента с помощью JavaScript и проверяет автоматизированный трафик с помощью CAPTCHA. Сырой HTTP-запрос с IP-адреса дата-центра обычно возвращает пустую оболочку или страницу блокировки, а не отзывы. Чтобы получить полную страницу, необходимо отрисовать её за надёжным IP-адресом и дать скриптам время заполнить список, что и берёт на себя Crawling API.
Как получить все отзывы, а не только первую страницу?
Yelp разбивает отзывы по страницам с помощью параметра запроса start, который увеличивается шагами примерно по десять, так что ?start=10 это вторая страница, ?start=20 третья, и так далее. Проходите по этим смещениям, получайте и парсируйте каждую страницу и останавливайтесь, когда страница не вернёт блоков отзывов или вы достигнете своего лимита страниц. Именно это делает функция crawlAllReviews в этом руководстве, с паузой между запросами.
Следует ли хранить имена рецензентов?
Нет. Имена рецензентов, ссылки на профили и фотографии являются персональными данными, а данный учебник предназначен для агрегированного анализа, а не профилирования. Храните только рейтинг, текст и дату. Если ваш анализ действительно требует различать рецензентов, псевдонимизируйте или хешируйте идентификатор при сборе данных, чтобы сохранённая запись никогда не связывала текст с именем конкретного человека, и соблюдайте применимые обязательства GDPR или CCPA.
Рейтинг или дата возвращаются пустыми. Что изменилось?
Скорее всего, разметка Yelp. Имена классов и структура виджета звёзд меняются без предупреждения, поэтому ранее работавшие селекторы могут сломаться. Повторно осмотрите живой блок отзыва в DevTools браузера, убедитесь, где теперь находятся aria-label или title рейтинга и дата, и обновите селекторы. Периодическое обслуживание селекторов является нормой для любого рабочего скрапера.
Есть ли официальный способ получить данные об отзывах Yelp?
Да. Yelp Fusion API это санкционированная программа Yelp для доступа к информации о бизнесе и ограниченному набору данных об отзывах на чётких условиях. Для коммерческих проектов, больших объёмов или случаев, когда вам нужна гарантированная структура и права на использование, Fusion API или прямое соглашение об обмене данными является правильным инструментом, а не скрапер.
Обходите любой сайт в масштабе, без борьбы с инфраструктурой.
Crawlbase берёт на себя прокси, отпечатки и CAPTCHA, чтобы ваша команда выпускала конвейеры данных вместо поддержки обвязки краулинга. 1 000 запросов бесплатно, без карты.
