如何抓取Yelp商家列表和评论:2026年公开数据、PerimeterX与代理实战指南

面向开发者的Yelp公开数据抓取指南:涵盖无需登录可获取的商家资料与评论、PerimeterX反爬机制、美国住宅代理配置、Python与Node.js可运行示例,以及合规与道德边界。

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

抓取Yelp商家列表和评论是本地商业数据集构建中最常见也最容易踩坑的任务之一。Yelp拥有超过5300万条商家评论和数百万商家档案,对SEO团队、市场研究者和数据工程师极具价值。但Yelp部署了PerimeterX(现HUMAN)反机器人系统、TLS指纹检测和CAPTCHA挑战,使得裸请求几乎无法规模化。本文将从公开数据的边界讲起,逐步给出可运行的Python与Node.js示例,并说明如何用ProxyHat住宅代理实现稳定抓取。

法律与合规提示: 在抓取任何数据之前,请仔细阅读Yelp的服务条款(Terms of Service)。在美国,《计算机欺诈和滥用法》(CFAA, 18 U.S.C. § 1030)可能适用于未经授权的访问;在欧盟,GDPR对个人数据的处理有严格规定。评论中包含用户姓名等个人信息,属于个人数据范畴。本文仅讨论公开可访问、无需登录的数据获取方法,不鼓励绕过登录墙或违反平台条款。如果你需要大规模商业数据,优先考虑Yelp官方的Fusion API。

一、如何抓取Yelp商家列表和评论:公开数据的边界

无需登录可获取的数据

Yelp的商家页面(/biz/<slug>)在未登录状态下即可访问,包含以下公开信息:

  • 商家基本信息:名称、地址、电话、网址、营业时间
  • 分类与标签:如“意大利餐厅”“咖啡店”等类别
  • 评分与评论数:星级评分(1-5星)和总评论数
  • 评论文本:每页默认展示约10条评论,包含评论者昵称、评分、日期和正文
  • 图片:用户上传的照片和商家官方照片

这些数据嵌入在页面HTML中的__INITIAL_STATE__ JSON blob里,也可以通过Yelp内部的review_feed JSON端点分页获取。无需登录即可解析这些结构化数据。

Fusion API的三条评论限制

Yelp官方提供Fusion API,其中GET /v3/businesses/{id}/reviews端点最多返回3条评论。对于需要完整评论数据集的研究者来说,这个限制是致命的。Fusion API更适合获取商家基础信息(名称、地址、评分、坐标),而非评论挖掘。

数据类型公开页面抓取Fusion API
商家基础信息✅ 完整✅ 完整
评分与评论数
评论文本✅ 全部分页可获取❌ 仅3条
评论者资料✅ 昵称+头像✅ 昵称+头像
营业时间
需API Key✅ 需注册
速率限制受反爬系统约束5000次/天

二、Yelp的反机器人防御体系

理解Yelp的反爬机制是成功抓取Yelp数据的前提。Yelp使用多层防御,从IP声誉到浏览器指纹层层设防。

PerimeterX / HUMAN 反机器人

Yelp部署了PerimeterX(现品牌为HUMAN)作为核心反机器人系统。其工作原理包括:

  • _px3 Cookie:PerimeterX通过JavaScript生成_px3_px2_pxhd等cookie,包含加密的浏览器指纹和行为评分。没有有效cookie的请求会被拦截。
  • Sensor数据挑战:PerimeterX的JS SDK收集浏览器特征(canvas指纹、WebGL参数、屏幕分辨率、时区等),生成“sensor data”发送回服务器验证。纯HTTP客户端无法生成这些数据。
  • 行为分析:鼠标移动、滚动模式、点击间隔等行为信号被用于区分人类与机器人。

当PerimeterX检测到可疑流量时,会返回HTTP 403或401状态码,并展示一个JavaScript挑战页面或CAPTCHA。数据中心IP通常在3-5个请求内就会被标记。

TLS指纹检测(JA3/JA4)

Yelp还通过TLS握手指纹识别非浏览器客户端。Python的requests库和Node.js的默认https模块产生的TLS指纹与真实浏览器不同。JA3指纹哈希会被与已知机器人工具库比对。这意味着即使你设置了正确的User-Agent,TLS层面仍然会暴露身份。

CAPTCHA触发条件

以下行为会快速触发CAPTCHA或IP封禁:

  • 使用数据中心IP(AWS、GCP、Azure等云服务商IP段)
  • 短时间内发送大量请求(如每秒超过2-3个请求/同一IP)
  • 缺少_px3 cookie或cookie过期
  • User-Agent与TLS指纹不匹配
  • 访问模式过于规律(如按字母顺序遍历商家slug)

三、为什么需要轮换住宅代理与美国地理位置定位

要绕过PerimeterX的IP声誉评分,住宅代理是必需的。住宅IP来自真实的ISP分配,在PerimeterX的IP声誉数据库中评分远高于数据中心IP。数据中心IP几乎一开就被拦截,而住宅IP可以维持数十到数百个请求才需要轮换。

美国地理位置定位对Yelp尤其重要,因为Yelp的核心市场在美国,且搜索结果具有本地化特征。使用-country-US-city-austin这样的地理位置参数可以:

  • 获取与本地用户一致的搜索结果和商家排序
  • 降低IP与请求目标之间的地理异常(如从德国IP访问Austin本地餐厅页面更可疑)
  • 提高PerimeterX的行为评分——本地IP访问本地商家是“正常”模式

查看ProxyHat支持的全球位置,请访问代理位置页面

四、实战代码示例

示例1:Python requests 抓取Yelp评论(通过ProxyHat HTTP代理)

以下示例使用ProxyHat住宅代理,通过HTTP网关gate.proxyhat.com:8080访问Yelp商家页面,解析嵌入的__INITIAL_STATE__ JSON blob提取评论数据。

import requests
import re
import json
import time
import random

# ProxyHat 住宅代理配置
# 使用美国地理位置 + 粘性会话保持cookie连续性
PROXY_URL = "http://user-country-US-city-austin-session-yelp01:pass@gate.proxyhat.com:8080"

proxies = {
    "http": PROXY_URL,
    "https": PROXY_URL,
}

# 轮换的浏览器 User-Agent 列表
USER_AGENTS = [
    "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
    "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36",
]

def fetch_yelp_reviews(biz_slug, max_pages=5):
    """抓取Yelp商家页面的评论数据"""
    all_reviews = []
    session = requests.Session()

    for page in range(0, max_pages):
        # Yelp 评论分页参数: start=page*10
        url = f"https://www.yelp.com/biz/{biz_slug}?start={page * 10}"

        headers = {
            "User-Agent": random.choice(USER_AGENTS),
            "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",
        }

        try:
            resp = session.get(url, headers=headers, proxies=proxies, timeout=30)

            if resp.status_code == 403:
                print(f"[!] 被PerimeterX拦截 (page {page}),切换会话...")
                # 轮换会话ID以获取新IP
                new_session_id = f"yelp{random.randint(1000, 9999)}"
                PROXY_URL_NEW = f"http://user-country-US-city-austin-session-{new_session_id}:pass@gate.proxyhat.com:8080"
                session.proxies.update({"http": PROXY_URL_NEW, "https": PROXY_URL_NEW})
                time.sleep(random.uniform(5, 10))
                continue

            # 解析 __INITIAL_STATE__ JSON blob
            match = re.search(r'<script>window\.__INITIAL_STATE__=({.*?});</script>', resp.text, re.DOTALL)
            if not match:
                # 尝试另一种嵌入模式
                match = re.search(r'__INITIAL_STATE__\s*=\s*({.*?});', resp.text, re.DOTALL)

            if match:
                state_json = match.group(1)
                state = json.loads(state_json)

                # 导航到评论数据路径(结构可能随Yelp更新变化)
                biz_data = state.get("bizDetailsPage", {}).get("reviewListPage", {})
                reviews = biz_data.get("reviews", [])

                for review in reviews:
                    review_obj = {
                        "review_id": review.get("id", ""),
                        "rating": review.get("rating", None),
                        "text": review.get("comment", {}).get("text", "")[:200],  # 截断
                        "date": review.get("localizedDate", ""),
                        "author": review.get("author", {}).get("displayName", ""),
                    }
                    all_reviews.append(review_obj)

                print(f"[+] Page {page}: 获取 {len(reviews)} 条评论")
            else:
                print(f"[-] Page {page}: 未找到 __INITIAL_STATE__")

        except requests.exceptions.RequestException as e:
            print(f"[ERROR] {e}")

        # 速率控制:每次请求间隔 3-7 秒
        time.sleep(random.uniform(3, 7))

    return all_reviews

# 运行示例
if __name__ == "__main__":
    reviews = fetch_yelp_reviews("tartine-bakery-san-francisco", max_pages=3)
    for r in reviews[:3]:
        print(json.dumps(r, indent=2, ensure_ascii=False))

上面的代码解析了一个截断的评论对象,输出类似:

{
  "review_id": "abc123xyz",
  "rating": 5,
  "text": "Best morning bun of my life. The croissant was flaky and buttery...",
  "date": "2025-11-14",
  "author": "Emily K."
}

示例2:Node.js 通过SOCKS5代理抓取Yelp商家信息

以下示例使用ProxyHat的SOCKS5端口1080,通过socks-proxy-agent库在Node.js中建立代理连接。

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

// ProxyHat SOCKS5 代理配置
const proxyUrl = "socks5://user-country-US-city-austin-session-node01:pass@gate.proxyhat.com:1080";
const agent = new SocksProxyAgent(proxyUrl);

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

function fetchYelpBiz(slug) {
  return new Promise((resolve, reject) => {
    const url = `https://www.yelp.com/biz/${slug}`;

    const options = {
      hostname: 'www.yelp.com',
      path: `/biz/${slug}`,
      method: 'GET',
      agent: agent,
      headers: {
        'User-Agent': USER_AGENTS[Math.floor(Math.random() * USER_AGENTS.length)],
        'Accept': 'text/html,application/xhtml+xml',
        'Accept-Language': 'en-US,en;q=0.9',
        'Accept-Encoding': 'gzip, deflate, br',
      },
    };

    const req = https.request(options, (res) => {
      let data = '';
      res.on('data', (chunk) => data += chunk);
      res.on('end', () => {
        if (res.statusCode === 403) {
          reject(new Error('被PerimeterX拦截 (HTTP 403)'));
          return;
        }

        // 提取 __INITIAL_STATE__
        const match = data.match(/__INITIAL_STATE__\s*=\s*({.*?});/s);
        if (match) {
          const state = JSON.parse(match[1]);
          const biz = state?.bizDetailsPage?.businessDetails?.business || {};
          resolve({
            name: biz.name || '',
            rating: biz.rating || null,
            review_count: biz.reviewCount || 0,
            phone: biz.phone || '',
            address: biz.location?.displayAddress?.join(', ') || '',
            categories: (biz.categories || []).map(c => c.title),
            hours: biz.hours || [],
          });
        } else {
          reject(new Error('未找到 __INITIAL_STATE__'));
        }
      });
    });

    req.on('error', reject);
    req.setTimeout(30000, () => req.destroy());
    req.end();
  });
}

// 运行
fetchYelpBiz('tartine-bakery-san-francisco')
  .then(data => console.log(JSON.stringify(data, null, 2)))
  .catch(err => console.error('Error:', err.message));

示例3:curl 快速测试代理连通性

# 测试ProxyHat住宅代理是否能访问Yelp
curl -x http://user-country-US-city-austin:pass@gate.proxyhat.com:8080 \
  -H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" \
  -H "Accept-Language: en-US,en;q=0.9" \
  -s -o /dev/null -w "%{http_code} %{time_total}s\n" \
  https://www.yelp.com/biz/tartine-bakery-san-francisco

# 预期输出: 200 1.2s(成功)
# 如果输出 403,说明该IP已被PerimeterX标记,需要轮换session

五、分页、速率控制与会话策略

分页策略

Yelp商家页面的评论分页通过URL参数?start=N实现,其中N是起始评论索引(每页10条)。例如:

  • ?start=0 — 第1页(评论1-10)
  • ?start=10 — 第2页(评论11-20)
  • ?start=20 — 第3页(评论21-30)

大部分商家页面最多显示约100-200条评论(即10-20页),但热门商家可能有更多。Yelp可能会在深层分页时触发额外的反爬检查。

速率控制与请求间隔

合理的请求间隔是避免封禁的关键。推荐的速率模式:

  • 单IP请求间隔:3-7秒随机延迟,避免固定间隔被检测
  • 单会话最大请求数:50-100个请求后轮换session ID
  • 并发控制:不超过5个并发连接/同一出口IP
  • 每日总量:单商家不超过500个请求,分散在24小时内

粘性会话(Sticky Session)

PerimeterX的_px3 cookie与IP绑定。如果每个请求都换IP,cookie会失效,反而触发更多验证。正确做法是使用粘性会话保持同一IP一段时间:

# 粘性会话:同一session ID保持同一出口IP
http://user-country-US-session-yelp-biz001:pass@gate.proxyhat.com:8080

# 需要轮换时,更改session ID即可获得新IP
http://user-country-US-session-yelp-biz002:pass@gate.proxyhat.com:8080

更多代理配置选项请参考ProxyHat官方文档

轮换User-Agent

仅轮换User-Agent不足以绕过TLS指纹检测,但它是降低被检测概率的必要措施之一。建议:

  • 使用真实浏览器的完整UA字符串(包含Chrome版本号)
  • 保持UA与Accept-Language等header一致
  • 不要使用过时的UA(如Chrome 80版本号在2026年已不现实)
  • 每个session使用一个固定的UA,而非每个请求都换

如需更高级的指纹管理,考虑使用Playwright或Puppeteer等无头浏览器方案,它们能生成真实的TLS指纹和PerimeterX sensor数据,但代价是更高的资源消耗和更慢的速度。

六、常见错误与边缘情况

错误1:使用数据中心代理

数据中心IP在PerimeterX的IP声誉数据库中评分极低。实测中,AWS us-east-1的IP在Yelp上平均3-5个请求后即被403拦截。解决方案:使用ProxyHat住宅代理。

错误2:忽略__INITIAL_STATE__结构变化

Yelp会不定期更新前端架构,__INITIAL_STATE__的JSON结构可能变化。建议:

  • 使用防御性解析(.get() + 默认值)
  • 定期检查解析逻辑是否仍然有效
  • 记录解析失败的页面以便排查

错误3:固定请求间隔

固定3秒间隔的请求模式很容易被PerimeterX的行为分析识别。使用random.uniform(3, 7)等随机间隔,甚至偶尔插入更长的“阅读时间”模拟人类行为。

错误4:忽略robots.txt

Yelp的robots.txt对部分路径有Disallow规则。虽然robots.txt主要约束搜索引擎爬虫,但尊重它也是合规抓取的良好实践。在构建爬虫前先检查robots.txt的内容。

七、道德抓取与何时使用官方API

Yelp评论抓取器的使用需要谨慎考虑道德和法律边界:

  • 个人数据保护:评论包含用户昵称、头像和位置信息,属于GDPR定义的个人数据。如果你在欧盟运营或处理欧盟用户数据,需要 lawful basis(合法依据)。考虑匿名化处理——移除用户ID和昵称,仅保留评分和评论文本用于情感分析。
  • 避免登录墙内容:Yelp部分内容需要登录才能查看(如某些用户的完整评论历史)。不要尝试绕过登录墙,这明确违反ToS且可能触发CFAA。
  • 请求频率:不要对Yelp服务器造成过大负担。合理的请求速率不仅是为了避免封禁,也是对平台基础设施的尊重。
  • 数据用途:抓取的数据应用于研究、分析或竞争情报,不要用于垃圾信息发送、虚假评论或任何欺诈活动。

何时应该使用Fusion API而非抓取

  • 只需要商家基础信息(名称、地址、评分、坐标)→ Fusion API足够
  • 需要可靠的结构化数据不需要评论全文 → Fusion API更稳定
  • 商业用途需要合规保障 → Fusion API有明确的授权和SLA
  • 每日请求量低于5000次 → Fusion API的免费配额可能够用

只有当你需要完整的评论数据集(超过3条评论)且Fusion API无法满足时,才考虑抓取公开页面。即便如此,也应优先考虑与Yelp协商数据合作授权。

关键要点

  • Yelp商家页面(/biz/<slug>)的公开数据可通过解析__INITIAL_STATE__ JSON blob获取,无需登录。
  • Fusion API的评论端点仅返回3条评论,无法满足完整评论数据集需求。
  • PerimeterX/HUMAN反机器人系统通过_px3 cookie、sensor数据和IP声誉评分拦截非人类流量。数据中心IP在3-5个请求内即被封禁。
  • 使用ProxyHat住宅代理配合-country-US-city-austin地理位置定位和粘性-session-参数,是稳定抓取Yelp的关键。
  • 请求间隔应随机化(3-7秒),每50-100个请求轮换session,并保持UA与TLS指纹一致。
  • 抓取评论涉及个人数据,需遵守GDPR和CFAA。优先使用Fusion API获取基础数据,仅在需要完整评论时抓取公开页面。

准备好开始抓取了吗?查看ProxyHat定价方案选择适合你的住宅代理套餐,或访问网页抓取用例页面了解更多实战场景。如果你需要追踪Yelp商家在搜索结果中的排名,也可以参考我们的SERP追踪用例

准备开始了吗?

通过AI过滤访问148多个国家的5000多万个住宅IP。

查看价格住宅代理
← 返回博客