Yelp 汇聚了数亿条关于本地商家的众包评论,这些公开反馈是开放网络上衡量餐厅、商店或服务实际口碑最丰富的信号之一。分析师用它来追踪情感随时间的变化,运营者用它来发现反复出现的投诉,研究人员用它来对整个类别的商家进行横向比较。有用的部分就摆在每个商家页面上:星级评分、评论文本和发布日期。

本指南将向你展示如何使用 JavaScript 和 Node.js 结合 cheerio 爬取并抓取 Yelp 评论。你将构建一个可运行的小型爬虫,通过 Crawling API 获取公开的 Yelp 商家评论页面,解析每条评论的评分、文本和日期,处理评论分页,并将结果导出为 JSON 和 CSV。整个演示仅限于公开评论数据,专为聚合分析而设计,不用于分析个人评论者。在将此方案用于任何真实规模之前,请仔细阅读结尾处的法律说明。

你将构建什么

一个 Node.js 脚本,接受公开的 Yelp 商家 URL,通过 Crawling API 获取渲染后的 HTML,并为页面上的每条评论提取一条结构化记录。我们以一个餐厅页面为示例,每条评论提取以下字段:

  • 评分 评论者给出的星级评分,例如"4 out of 5"。
  • 文本 评论的公开正文,即你实际分析的内容。
  • 日期 评论发布的日期,用于排序和趋势分析。

注意列表中没有的内容。我们刻意不收集或以评论者姓名、个人主页链接、照片或位置作为键。这些属于个人数据,而本教程的全部目的在于聚合分析:情感、反复出现的主题、随时间变化的评分分布。将评论视为一个评分、一段文本和一个日期,而不是关于某个人的档案。

为什么普通请求在 Yelp 上会失败

如果你用裸 HTTP 客户端请求 Yelp 商家 URL,很少能拿到评论。两个因素对你不利。首先,Yelp 在浏览器中用 JavaScript 渲染评论列表,所以初始 HTML 几乎是空壳,直到页面脚本运行为止。其次,Yelp 积极标记自动化流量:来自数据中心 IP 的、或请求模式不像真实浏览器的流量,会在到达渲染后的评论之前遭到 CAPTCHA 挑战、限速或封锁。

因此,一个可用的 Yelp 爬虫需要在单次请求中同时具备两点:能实际渲染页面的浏览器,以及平台认为是真实访客的 IP。你可以自行组合无头浏览器加上轮换住宅代理池,但将它们拼合在一起并保持健康运行,本身就是大部分工作量所在。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 账号和 token。 注册后打开控制台,复制你的 JavaScript 请求 token。免费套餐最多提供 20,000 次请求且无需绑定信用卡。由于 Yelp 需要渲染,请使用 JavaScript token 而非普通 token。请像对待密码一样保管 token:它用于验证你的请求,因此不要放入版本控制系统。

搭建项目

创建项目文件夹,初始化,并安装爬虫所需的两个库。

bash
node --version

mkdir yelp-reviews && cd yelp-reviews
npm init -y

npm install crawlbase cheerio

两个依赖各司其职:crawlbase 是 Crawling API 的官方 Node 客户端,cheerio 使用类 jQuery 的 API 解析返回的 HTML,让你可以通过 CSS 选择器提取各个字段。本教程的旧版本使用 requestcheerio;我们保留 cheerio 和相同的选择器方式,用官方客户端替换原始 HTTP 调用,使渲染和重试都得到处理。在此文件夹中新建一个名为 yelp-scraper.js 的文件,并按以下步骤添加代码。

第 1 步:获取渲染后的评论页面

从获取完整页面开始。导入 CrawlingAPI 类,用你的 JavaScript token 初始化,并请求商家 URL。Yelp 在客户端渲染评论,因此我们传入 pageWait,让脚本在 API 抓取 HTML 之前有片刻时间填充列表。在解析前检查状态码,可以让错误显而易见而不是悄无声息。

javascript
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,你应该能在 body 开头看到真实的 Yelp 标记,而不是精简的薄壳。这在你编写任何选择器之前就确认了渲染的有效性。示例 URL 指向一个公开的餐厅页面;换成任何其他公开商家页面,同样的流程都适用。

Crawlbase Crawling API

第一次请求就返回了一个完整渲染的 Yelp 页面,而你无需在自己这边运行无头浏览器或代理。Crawling API 在真实浏览器中运行页面,等待评论列表填充,在服务端轮换住宅 IP,并处理 Yelp 对爬虫的 CAPTCHA,让你从一次调用中就能获得已渲染的 HTML。先从免费套餐开始,将其指向单个公开商家页面。

第 2 步:用 cheerio 解析每条评论

拿到渲染后的 HTML 后,将其加载到 cheerio 并遍历评论块。Yelp 将每条评论布局在一个重复的容器中,因此你选择每条评论,然后从中读取评分、文本和日期。本教程旧版本的选择器 .review.review--with-sidebar 用于评论容器,.review-content p 用于正文;我们保留这些选择器,并增加评分和日期。防御性地读取每个字段,避免单个缺失值导致运行崩溃。

javascript
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-labeltitle 读作类似"4 star rating"的内容,因此用一个小正则表达式提取数字。日期也位于同一个 review-content 块中。每条记录只包含 { rating, date, text },没有评论者姓名,这是有意为之。如果某条评论没有正文,我们跳过它而不是存储空行。

选择器会漂移

Yelp 的类名会不经通知地变动,上述确切的评分和日期选择器可能需要在实时页面上更新。将它们视为起始模板,而非固定契约。当某个字段返回为空时,在浏览器开发者工具中重新检查实时页面并更新选择器。如果你想复习构建健壮选择器的方法,可以参阅爬取 JavaScript 网站指南。定期维护选择器是任何生产级爬虫的正常操作。

第 3 步:处理评论分页

单个页面只显示第一批评论。Yelp 使用 start 查询参数分页显示其余评论,步长为若干条(通常每页 10 条),因此 ?start=10 是第二页,?start=20 是第三页,以此类推。要收集一个商家的所有评论,你需要遍历这些偏移量,直到某个页面没有返回任何评论块为止,每次都用你已经写好的函数获取和解析。

javascript
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 只包含评分、日期和文本这三个驱动情感和主题分析的字段。

javascript
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();

将前面步骤中的 parseReviewscrawlPagecrawlAllReviews 函数粘贴到同一个文件中,以便 main 可以调用它们。运行 node yelp-scraper.js,你将得到两个文件:reviews.json 包含完整的结构化记录,reviews.csv 可以直接在电子表格中打开或输入到分析管道。toCsv 辅助函数对每个字段加引号并将嵌入的引号加倍,这在评论文本较长且频繁包含逗号和换行符时尤为重要。

输出结果示例

JSON 文件每条评论包含一个对象,包括评分、日期和文本。没有评论者身份信息,这正是聚合工作所需要的。

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 或任何读取分隔符文件的数据管道中打开。

csv
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 轮换。 住宅 IP 池将请求分散到多个真实用户地址,使单个地址不会触发限制或 CAPTCHA。Crawling API 为你处理了这一点;如果你自行搭建,这是最需要做好的环节。
  • 关注状态码。 如果运行开始返回挑战或非 200 响应,说明当前速率或 IP 套餐已不够用。将此视为退出的信号,而非可忽略的噪音。

完整的操作手册,请参阅如何在不被封锁的情况下抓取网站

抓取 Yelp 评论合法吗?

抓取 Yelp 是否被允许,取决于 Yelp 的服务条款、你所在的司法管辖区,以及你对数据的使用方式。Yelp 的服务条款明确限制从网站复制或抓取内容,无论是手动还是通过机器人、扩展程序或软件,因此无论你的工具多么谨慎,自动化采集都可能违反这些条款。本文中的任何代码都不会改变这一点;它们只是让技术层面的工作变得可行。请阅读 Yelp 的服务条款和 robots.txt,并将两者视为你采集内容的边界。对于任何真实规模或商业用途,最清晰的路径是官方的 Yelp Fusion API,它在明确的条款下提供商家和评论数据。

商家页面上显示的评论文本和星级评分是公开的,但撰写它们的人在隐私法律层面并非匿名。评论者姓名、个人主页链接、照片和位置属于个人数据。根据 GDPR 和 CCPA,处理这些数据需要合法依据,个人对其数据保有权利。这正是为什么本教程刻意只收集评分、文本和日期,也是为什么我们建议你不要存储、以此为键或将某人的评论与其身份绑定后再发布。保持分析的聚合性:情感、主题和评分趋势,而非个人评论者的档案。

几条值得坚守的原则。只收集公开的评论内容,以慢速和尊重的频率抓取,将请求量保持在不给 Yelp 服务器造成压力的水平。远离任何需要登录的内容。不要将 Yelp 的受版权保护内容(包括评论媒体)作为自己的内容再发布。如果你在 GDPR 或 CCPA 司法管辖区运营或收集该辖区内人员的数据,且你的用途涉及个人数据,请在继续之前寻求法律建议。如有疑问,Yelp Fusion API 或数据协议是正确的路径,而非更聪明的爬虫。

回顾

核心要点

  • Yelp 在客户端渲染评论并严格封锁爬虫。 普通请求返回空壳或 CAPTCHA,因此必须先在可信 IP 后面渲染页面再进行解析。
  • Crawling API 一次调用完成两件事。 它使用 JavaScript token 渲染页面,等待评论列表加载,轮换住宅 IP,并处理 CAPTCHA,返回你用 cheerio 解析的已渲染 HTML。
  • 只解析评分、文本和日期。 保留旧版 .review.review--with-sidebar.review-content p 选择器,增加星级评分和日期,并遍历 start 偏移量实现分页。
  • 保持聚合性和隐私安全。 将评论者姓名排除在记录之外;分析情感、主题和评分趋势,永远不要建立个人档案或将评论与具名人员绑定后再发布。
  • 大规模使用时优先选择官方 API。 遵守 Yelp 的服务条款和 robots.txt,在涉及个人数据时注意 GDPR 和 CCPA,商业或大规模用途请使用 Yelp Fusion API 或数据协议。

常见问题

抓取 Yelp 评论合法吗?

这取决于 Yelp 的服务条款、你所在的司法管辖区和你的用途。Yelp 的条款限制对其内容的抓取,因此自动化采集可能违反条款。评论文本和评分是公开的,但评论者身份属于 GDPR 和 CCPA 等法律所覆盖的个人数据。只收集公开评论内容,保持聚合性,慢速抓取,对于任何真实规模或商业用途请优先使用官方 Yelp Fusion API。

为什么普通请求从 Yelp 返回的数据不完整?

因为 Yelp 使用 JavaScript 在客户端渲染评论列表,并使用 CAPTCHA 挑战自动化流量。来自数据中心 IP 的原始 HTTP 请求通常返回空壳或封锁页面,而不是评论。要获得完整的页面,必须在可信 IP 后面渲染它,并给脚本时间填充列表,这正是 Crawling API 为你处理的事情。

如何获取所有评论而不仅仅是第一页?

Yelp 使用 start 查询参数分页评论,步长约为十条,因此 ?start=10 是第二页,?start=20 是第三页,以此类推。在这些偏移量上循环,获取并解析每个页面,当某页没有返回评论块或达到页面上限时停止。本指南中的 crawlAllReviews 函数正是这样做的,并在获取之间加入了暂停。

我应该存储评论者姓名吗?

不应该。评论者姓名、个人主页链接和照片属于个人数据,本教程专为聚合分析而设计,而非个人分析。只存储评分、文本和日期。如果你的分析确实需要区分评论者,请在采集时将标识符假名化或哈希,使存储的记录永远不会将文本追溯到具名人员,并遵守所有适用的 GDPR 或 CCPA 义务。

我的评分或日期返回为空。是什么改变了?

几乎可以肯定是 Yelp 修改了标记语言。它的类名和星级组件的结构会不经通知地改变,之前有效的选择器可能就此失效。在浏览器开发者工具中重新检查实时评论块,确认评分 aria-labeltitle 以及日期现在的位置,并更新选择器。定期维护选择器是任何生产级爬虫的正常操作。

有没有获取 Yelp 评论数据的官方途径?

有。Yelp Fusion API 是 Yelp 的官方程序,在明确的条款下提供商家信息和有限的评论数据访问。对于商业项目、大规模需求,或需要有保证结构和使用权的场景,Fusion API 或直接数据协议才是正确的工具,而非爬虫。

开始构建

大规模爬取任何站点,无需与基础设施对抗。

Crawlbase 负责处理代理、指纹和 CAPTCHA,让你的团队专注于交付数据流水线,而非维护爬取管道。1,000 次请求免费,无需信用卡。

自助开通 · 无需销售通话 · 提供企业级爬取量