免责声明:本文仅讨论对公开可访问数据的合规采集。在抓取Yelp之前,请仔细阅读其服务条款。在美国,违反计算机访问限制可能涉及《计算机欺诈和滥用法》(CFAA);在欧盟,处理包含个人信息的评论数据须遵守《通用数据保护条例》(GDPR)。如果Yelp官方API能满足你的需求,应优先使用API而非网页抓取。
如果你正在构建本地商家数据集——用于市场分析、竞品监控或评论情感分析——抓取Yelp几乎是绕不开的话题。Yelp拥有超过5300万条商家信息,是全球最大的本地商业评论平台之一。但Yelp的反爬虫体系相当成熟:PerimeterX(现为HUMAN)的行为检测、TLS指纹分析、以及IP信誉评分,让简单的requests脚本在发出去几个请求后就被封禁。本文将从技术原理到可运行代码,完整讲解如何安全、稳定地采集Yelp公开商家数据。
如何抓取Yelp商家列表和评论2026:公开数据范围与Fusion API限制
首先需要明确:哪些数据是公开可访问的,哪些需要登录。Yelp的商家页面(/biz/<slug>)在未登录状态下可以访问以下信息:
- 商家基本信息:名称、地址、电话、营业时间、价格区间
- 分类与标签:如“意大利餐厅”、“咖啡店”等类别
- 评分与评论数:星级评分(1-5星)和总评论数
- 评论文本:每条评论的用户名、评分、日期、正文内容
- 照片元数据:用户上传的图片缩略图URL
Yelp提供了官方的Fusion API,但它的限制很大:商家搜索端点返回的评论最多只有3条,且不包含完整评论正文。对于需要批量采集评论数据的研究者或数据工程师来说,Fusion API的覆盖范围远远不够。这就是为什么开发者转向网页抓取——但这条路并不平坦。
Yelp的反爬虫防御体系
Yelp的反爬虫由多层机制组成,理解每一层是构建稳定抓取管道的前提。
PerimeterX / HUMAN 行为检测
Yelp使用PerimeterX(2024年更名为HUMAN)作为其主要的机器人检测引擎。PerimeterX的核心机制包括:
_px3Cookie:这是PerimeterX生成的加密Cookie,包含设备指纹和行为评分。没有有效的_px3,请求会被直接拦截或重定向到验证页面。- Sensor数据挑战:浏览器端JavaScript会收集鼠标移动、键盘输入、触摸事件等行为数据,生成一个“sensor data” payload发送给PerimeterX服务器。纯HTTP客户端(如requests)无法生成这些数据。
- IP信誉评分:PerimeterX维护IP黑名单,数据中心IP段几乎全部被标记为高风险。来自数据中心IP的请求通常在3-5个请求后触发CAPTCHA或直接403。
TLS指纹(JA3/JA4)
即使你设置了完美的User-Agent和HTTP头,TLS握手层的指纹仍然会暴露你的真实身份。Python的requests库使用urllib3底层,其TLS ClientHello与真实Chrome浏览器存在显著差异。Yelp的服务端可以检测JA3指纹,将不匹配的请求标记为自动化工具。
CAPTCHA触发条件
在以下情况下,Yelp会触发CAPTCHA挑战:
- 短时间内从同一IP发送大量请求(通常超过10-15请求/分钟)
- 使用数据中心IP段(AWS、GCP、Azure等云服务商IP)
- 缺少关键浏览器Cookie或JavaScript执行痕迹
- 访问模式异常——如直接跳转到深层页面而非从搜索结果进入
为什么轮换住宅代理是必需的
面对上述防御体系,数据中心代理几乎无法工作。住宅代理(Residential Proxy)使用真实ISP分配的IP地址,在PerimeterX的IP信誉系统中具有高信任度。以下是选择代理时的关键考量:
地理定位与本地化结果
Yelp的搜索结果和商家列表具有强烈的地理偏向性。如果你用德国IP搜索“奥斯汀咖啡店”,返回的结果可能不完整或不准确。使用带有地理定位的住宅代理可以确保你获得与本地用户一致的结果:
# 美国奥斯汀的住宅代理
http://user-country-US-city-austin:pass@gate.proxyhat.com:8080
# 美国纽约的住宅代理
http://user-country-US-city-newyork:pass@gate.proxyhat.com:8080
IP轮换策略
对于Yelp抓取,推荐两种轮换策略:
- 每请求轮换:每个HTTP请求使用不同的出口IP。适合批量抓取不同商家页面,降低单IP请求频率。
- 粘性会话(Sticky Session):在同一会话内保持相同IP,适合需要分页抓取同一商家的所有评论时维持Cookie连续性。
Python实战:通过ProxyHat抓取Yelp评论数据
以下示例展示如何通过ProxyHat住宅代理抓取Yelp商家页面的评论数据。Yelp商家页面在HTML中嵌入了__INITIAL_STATE__ JSON blob,其中包含评论的初始数据。此外,评论分页通过review_feed JSON端点异步加载。
示例1:抓取商家页面并解析嵌入的JSON
import requests
import json
import re
import time
import random
# ProxyHat住宅代理配置 - 美国奥斯汀
proxy_url = "http://user-country-US-city-austin:YOUR_PASSWORD@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/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 fetch_yelp_page(biz_slug):
url = f"https://www.yelp.com/biz/{biz_slug}"
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 = requests.get(url, headers=headers, proxies=proxies, timeout=30)
resp.raise_for_status()
# 解析嵌入的 __INITIAL_STATE__
pattern = r'<script[^>]*>window\.__INITIAL_STATE__\s*=\s*({.*?});?</script>'
match = re.search(pattern, resp.text, re.DOTALL)
if match:
state_json = match.group(1)
state = json.loads(state_json)
# 提取评论数据(结构可能随Yelp更新而变化)
reviews = state.get("biz", {}).get("reviews", [])
for review in reviews[:3]: # 仅展示前3条
print(f"用户: {review.get('user', {}).get('name', 'N/A')}")
print(f"评分: {review.get('rating', 'N/A')}")
print(f"日期: {review.get('datePublished', 'N/A')}")
print(f"评论: {review.get('comment', {}).get('text', 'N/A')[:200]}...")
print("---")
return state
else:
print("未找到 __INITIAL_STATE__")
return None
except requests.exceptions.RequestException as e:
print(f"请求失败: {e}")
return None
# 示例:抓取一个奥斯汀餐厅
fetch_yelp_page("torchys-tacos-austin")
# 请求间隔 - 保持低于10请求/分钟
time.sleep(random.uniform(8, 15))
示例2:通过review_feed端点分页抓取评论
import requests
import json
import time
import random
# 使用粘性会话 - 同一商家的分页请求保持同一IP
session_id = "yelp-torchys-001"
proxy_url = f"http://user-country-US-session-{session_id}:YOUR_PASSWORD@gate.proxyhat.com:8080"
proxies = {"http": proxy_url, "https": proxy_url}
def fetch_reviews_feed(biz_id, start=0, limit=20):
"""通过Yelp的review_feed JSON端点获取评论"""
url = f"https://www.yelp.com/biz/{biz_id}/review_feed"
params = {"rl": f"en", "sort_by": "relevance_desc", "start": start}
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
"Accept": "application/json",
"Referer": f"https://www.yelp.com/biz/{biz_id}",
"X-Requested-With": "XMLHttpRequest",
}
resp = requests.get(url, params=params, headers=headers, proxies=proxies, timeout=30)
if resp.status_code == 200:
data = resp.json()
reviews = data.get("reviews", [])
for r in reviews:
# 截断的评论对象示例
review_obj = {
"user": r.get("user", {}).get("markupDisplayName", ""),
"rating": r.get("rating", 0),
"date": r.get("localizedDate", ""),
"text": r.get("comment", {}).get("text", "")[:300],
}
print(json.dumps(review_obj, ensure_ascii=False, indent=2))
return reviews
else:
print(f"状态码: {resp.status_code}")
return []
# 分页抓取
all_reviews = []
for page in range(0, 100, 20): # 最多5页
print(f"\n=== 抓取第 {page//20 + 1} 页 ===")
reviews = fetch_reviews_feed("torchys-tacos-austin", start=page)
if not reviews:
break
all_reviews.extend(reviews)
time.sleep(random.uniform(10, 20)) # 每页间隔10-20秒
print(f"\n总共获取 {len(all_reviews)} 条评论")
Node.js SOCKS5代理示例
对于Node.js环境,可以通过SOCKS5代理进行抓取。ProxyHat的SOCKS5端点位于gate.proxyhat.com:1080。
const { SocksProxyAgent } = require('socks-proxy-agent');
const axios = require('axios');
// ProxyHat SOCKS5配置 - 美国住宅代理
const socksUrl = 'socks5://user-country-US:YOUR_PASSWORD@gate.proxyhat.com:1080';
const agent = new SocksProxyAgent(socksUrl);
async function scrapeYelpBiz(slug) {
const url = `https://www.yelp.com/biz/${slug}`;
const config = {
url,
httpsAgent: agent,
httpAgent: agent,
timeout: 30000,
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',
'Accept-Language': 'en-US,en;q=0.9',
'Accept-Encoding': 'gzip, deflate, br',
},
};
try {
const resp = await axios(config);
// 提取 __INITIAL_STATE__
const match = resp.data.match(/window\.__INITIAL_STATE__\s*=\s*({[\s\S]*?});/);
if (match) {
const state = JSON.parse(match[1]);
const biz = state.biz || {};
const bizData = {
name: biz.name || '',
rating: biz.rating || 0,
reviewCount: biz.reviewCount || 0,
categories: (biz.categories || []).map(c => c.title),
hours: biz.hours || [],
};
console.log(JSON.stringify(bizData, null, 2));
return bizData;
}
} catch (err) {
console.error('抓取失败:', err.message);
}
}
scrapeYelpBiz('torchys-tacos-austin');
分页、速率控制与会话管理最佳实践
请求速率与节奏控制
Yelp对高频请求非常敏感。推荐的速率控制策略:
- 单IP速率:不超过8-10请求/分钟
- 请求间隔:随机化8-20秒间隔,避免固定节奏被检测
- 并发控制:同一代理会话串行请求,不同代理可并行但总并发不超过50-100会话
- 退避策略:遇到429或403时,立即停止当前IP,切换新IP并等待60秒以上
粘性会话的使用场景
# 商家A的评论分页 - 使用粘性会话保持IP一致
http://user-country-US-session-bizA-page1:pass@gate.proxyhat.com:8080
# 商家B的评论分页 - 使用不同会话
http://user-country-US-session-bizB-page1:pass@gate.proxyhat.com:8080
粘性会话确保同一商家的分页请求来自同一IP,这更接近真实用户浏览行为。切换到新商家时更换会话ID,实现自然的IP轮换。
User-Agent轮换
不要只使用单一User-Agent。准备一个包含10-20个当前主流浏览器UA的列表,每次请求随机选择。但注意:同一粘性会话内应保持相同UA,否则会触发异常检测。
代理类型对比
| 代理类型 | Yelp抓取适用性 | IP信誉 | 成本 | 推荐场景 |
|---|---|---|---|---|
| 数据中心代理 | 极差 - 3-5请求即被封 | 低 | 最低(约$0.5/GB) | 不推荐 |
| 住宅代理 | 优秀 - 高信任度IP | 高 | 中等(约$3-5/GB) | Yelp抓取首选 |
| 移动代理 | 优秀 - 最高信任度 | 最高 | 最高(约$10+/GB) | 高价值目标页面 |
对于Yelp这类受PerimeterX保护的目标,住宅代理是性价比最优的选择。查看ProxyHat的定价页面了解住宅代理套餐详情,或访问代理位置页面确认覆盖的地理位置。
常见错误与边界情况
1. 忽略robots.txt
Yelp的robots.txt明确禁止部分路径的爬取。在开始抓取前,务必检查robots.txt并遵守其规则。虽然robots.txt本身不具备法律强制力,但违反它可能被用作CFAA诉讼的证据。
2. 评论中的个人数据
Yelp评论包含用户姓名、头像和位置信息。在GDPR下,这些属于个人数据。如果你计划存储或处理评论数据集:
- 对用户名进行匿名化或假名化处理
- 不存储可识别个人身份的头像URL
- 明确数据保留期限,不要无限期存储
- 如果面向欧盟用户,考虑是否需要数据处理协议
3. 结构变化导致解析失败
Yelp会不定期更新前端结构,__INITIAL_STATE__的JSON schema可能随时变化。建议:
- 使用防御性解析(
.get()配合默认值) - 监控解析失败率,超过15%时检查结构变化
- 对关键数据字段做schema验证
4. PerimeterX Cookie失效
即使使用住宅代理,_px3 Cookie也可能过期。如果请求开始返回403或重定向到验证页面,说明Cookie已失效。解决方案是使用无头浏览器(如Playwright)定期刷新Cookie,或使用ProxyHat的会话保持功能确保Cookie连续性。
伦理抓取与何时使用官方API
网页抓取是一把双刃剑。在构建Yelp数据采集管道时,请遵循以下原则:
优先使用Fusion API
如果你的需求只需要商家基本信息(名称、地址、评分、分类)和少量评论样本,Yelp Fusion API是更合规、更稳定的选择。API调用受官方授权,不会触发反爬虫机制,且数据结构稳定。Fusion API的免费层级允许5000次/天的API调用。
网页抓取的合理场景
- 需要采集超过3条的完整评论数据用于学术研究
- 需要分析评论的时间趋势和情感变化
- Fusion API未覆盖的商家属性(如照片元数据)
应避免的行为
- 不要抓取需要登录才能访问的内容(如私信、个人资料详情)
- 不要对Yelp服务器造成显著负载——保持低速率、低并发
- 不要将抓取的数据用于垃圾营销或恶意用途
- 不要绕过付费墙或访问限制
更多关于合规抓取的最佳实践,请参考ProxyHat的网页抓取用例和SERP追踪用例页面。如需技术文档,请访问ProxyHat官方文档。
关键要点总结
Yelp抓取核心要点:
- Yelp公开页面可获取商家信息、评分和评论,但Fusion API仅返回3条评论
- PerimeterX/HUMAN的行为检测和IP信誉评分是主要障碍,数据中心IP几乎不可用
- 轮换住宅代理配合US地理定位是稳定抓取的基础
- 请求速率控制在8-10请求/分钟,使用随机间隔和粘性会话
- 解析
__INITIAL_STATE__和review_feed端点获取结构化数据- 遵守TOS、CFAA和GDPR,优先使用Fusion API覆盖基础需求
- 对评论中的个人数据进行匿名化处理
准备好开始抓取了吗?前往ProxyHat定价页面选择适合你的住宅代理套餐,或注册后访问控制面板开始配置你的第一个代理会话。






