Yelp 目前累计拥有超过 ,分布在 上——但把这些数据整理成可用格式,反而比以前更难了。Yelp 在 2024–2025 年加强了反爬措施,悄悄让大多数现有的 Python 抓取教程失效。
如果你最近尝试运行 Yelp 爬虫,却频繁遇到 403 错误、空白 HTML 响应,或者六个月前还没有的 CAPTCHA,那你不是在瞎想。Yelp 现在会做 TLS/JA3 指纹识别、轮换混淆后的 CSS 类名,并对 IP 信誉进行严格评分——也就是说,几乎所有教程还在推荐的 requests + BeautifulSoup 老套路,往往第一次请求就直接阵亡。我花了好几周测试 Yelp 现有的防护体系,这篇指南会覆盖 2025 年真正可用的所有方法:官方 Fusion API(以及为什么它大概率还不够)、一套带分层防封策略的 Python 抓取流程,以及一个只需两步、无需写代码的替代方案 ,适合只想拿到数据、不想陷入调试马拉松的读者。
为什么要用 Python 抓取 Yelp(谁最能受益)
在写一行代码之前,先问一个现实问题:Yelp 数据到底有什么业务价值?它不仅仅是餐厅点评网站,更像是一个实时更新的本地商家数据库,包含结构化的联系方式、评分、分类、营业时间,以及数以亿计的用户评论。

下面这些场景最常受益,以及他们通常会提取什么数据:
| 使用场景 | 关键数据字段 | 价值所在 |
|---|---|---|
| 销售与线索开发 | 商家名称、电话、网站、地址、分类、评分 | 构建精准的本地中小企业潜客名单——5 个 Yelp 用户中有 4 个到站时已经准备好购买 |
| 竞品情报 | 评论、星级评分、评论数量、情绪倾向 | 监控竞争对手口碑、发现服务缺口、跟踪趋势变化 |
| 市场研究与 NLP | 完整评论文本、日期、评论者元数据 | 情感分析、主题建模——Yelp 评论是学术研究中 最常用的 NLP 语料之一 |
| 房地产与选址 | 商家密度、业态组合、区域内评论质量 | 连锁门店和零售选址——Yelp 甚至将 Location Intelligence 作为专门的 B2B 授权产品来出售 |
| 电商与运营 | 价格信号、客户投诉、服务时间 | 追踪竞争对手评价方式,识别运营模式 |
核心逻辑其实很简单:真正的目标是结构化数据,而 Python 只是实现这一目标的一种方式。有些读者需要完全的程序化控制;也有人只想要一份奥斯汀水管工联系方式表格。这两条路径在本文里都会讲到。
Yelp Fusion API vs. Python 网页抓取:该选哪个?
大多数教程都会跳过这个关键决策,直接上代码,而不去评估官方的 (现已更名为 “Yelp Places API”)是否已经足够。按我的经验,先做这一步判断能省下不少时间,因为 API 对某些需求很好用,但对另一些需求则完全不够。
Fusion API 到底能给你什么
Fusion API 提供结构化的商家搜索、商家详情、自动补全,以及一个评论接口。它是官方授权的、文档完善的,而且不需要和反爬机制斗智斗勇。
但问题出在评论接口。Yelp 官方人员在 GitHub 上已经明确说明:
“Yelp API 不会返回完整评论文本。默认只提供 3 条 160 个字符的评论摘要。” —
这不是 bug,而是设计如此。API 最多只给 3 条评论摘要(Premium 为 7 条),每条都被截断到大约 160 个字符。没有评论元数据(有用/有趣/酷的投票)、没有评论者历史、没有商家回复。而且 ,相比之前的 5,000 次缩水不少。入门价格从 起。
决策框架
| 因素 | Yelp Fusion API | Python 网页抓取 | Thunderbit(无代码) |
|---|---|---|---|
| 完整评论 | ❌ 仅 3 条摘要(每条约 160 字符) | ✅ 通过 GraphQL 获取全部评论 | ✅ 获取所有可见评论 |
| 速率限制 | 新客户 300–500/天;老客户 5,000/天 | 自行管理(代理成本另算) | 按点数计费 |
| 上手时间 | 约 15 分钟(API Key + SDK) | 数小时到数天 | 约 2 分钟 |
| 商家字段 | 约 20 个结构化字段 | 不限(可解析 HTML/JSON) | AI 推荐字段 |
| 反爬处理 | 不适用(授权接口) | 需自行实现 | 自动处理 |
| 法律风险 | ✅ 授权接口 | ⚠️ ToS 灰色地带 | ⚠️ 与抓取相同 |
| 成本 | 最低每月 29 美元 | 免费(+ 代理成本 $0.75–$4/GB) | 提供免费方案 |
| 维护成本 | 低(API 比较稳定) | 高(选择器易失效、反爬升级) | 低(AI 自动适配) |
适合用 Fusion API 的情况: 你只需要基础商家信息、少量查询,或者需要一个官方授权的集成,并且每家商家 3 条评论摘要就够了。
适合用 Python 抓取的情况: 你需要完整评论文本、某一家商家的全部评论、评论元数据、单次搜索超过 240 条结果,或者预算低于每月 29 美元。
适合用 Thunderbit 的情况: 你想快速拿到数据,不想写代码也不想维护代码。下面的无代码部分会详细讲。
无代码捷径:用 Thunderbit 抓取 Yelp(无需 Python)
在深入 Python 之前,先给想直接拿数据的读者一个最快路径。大多数竞品文章默认你已经会 Python,但我在 Thunderbit 的工作中发现,搜索“scrape Yelp”的用户里,很大一部分其实是销售、运营经理和小企业主,他们只是想要一张本地商家表格,而不是上一堂 TLS 指纹识别课。
已经内置了 Yelp 现成模板:
- —— 提取商家名称、评分、联系方式、地址、营业时间、分类
- —— 提取评论者用户名、评论内容、评分、日期、评论者所在地
实际操作方式
- 在 Chrome 中打开 Yelp 搜索结果页或商家详情页
- 点击 中的 AI Suggest Fields,AI 会自动读取页面并推荐列名(商家名、评分、评论数、价格区间、分类、地址、电话、URL)
- 点击 Scrape —— 完成
如果直接用内置模板,操作更简单:打开模板,点击 Scrape 即可。
子页面抓取 会自动完成数据补全流程——从 Yelp 搜索结果页开始,开启子页面抓取后,Thunderbit 会逐个访问商家页面,提取营业时间、完整评论、网站、图片和设施信息,无需额外配置。
分页默认自动处理——无论是点击翻页还是滚动加载,开箱即用。(想了解原理,可参考我们的 。)
所有套餐都支持免费导出——Excel、Google Sheets、Airtable、Notion、CSV、JSON,一应俱全。无需 pandas,也不用写 CSV 导出代码。
时间对比
| 时间环节 | Python 爬虫 | Thunderbit |
|---|---|---|
| 首次运行 | 数小时到数天(写选择器、处理分页、代理、重试逻辑) | 使用现成 Yelp 模板约 30 秒 |
| Yelp 页面结构变化时 | 手动重写选择器 | 重新点一次 AI Suggest Fields,自动适配 |
| IP 被封时 | 排查、轮换代理池、重新测试 | 云端模式自动处理 IP 轮换 |
| 导出到 Google Sheets | 需要写 OAuth + pandas 连接代码 | 一键导出,免费 |
如果你先试 Thunderbit,发现它已经满足需求,那本文后半部分就可以不用看了。如果你需要更强的程序控制、自定义字段,或者每月数据量超过几千条——继续往下看。
抓取 Yelp 该选哪些 Python 库
“我该用 Scrapy、BS4+requests,还是 Selenium?”这是 r/webscraping 里谈到 Yelp 时最常见的问题之一。但很多教程只是挑自己喜欢的库,然后直接往下讲,完全不解释原因。下面给你一个诚实的对比。
2025 现实:requests + BeautifulSoup 对 Yelp 已经失效
几乎所有经典 Yelp 教程推荐的技术栈——pip install requests beautifulsoup4——在 2025 年会在第一次请求时就被封。不是第 50 次,是第一次。
原因是:Python 的 requests 库带出的 TLS/JA3 指纹,和任何真实浏览器都对不上。Yelp 的反爬层会在 TLS 握手阶段就把它标记掉,甚至在 User-Agent 头还没来得及被读取之前就已经拦截了。我反复测试过——新的 IP、真实头部、随机延迟——结果还是被 requests 直接返回 403 Forbidden。
库选择矩阵
| 库 | 最适合 | 支持 JS? | 反爬能力? | 学习门槛 | 速度 |
|---|---|---|---|---|---|
requests + BeautifulSoup | ❌ | ❌ | 很低 | 快(直到被封) | |
httpx async + parsel | 大规模异步抓取 | ❌ | ❌ | 低 | 很快 |
curl_cffi + parsel | Yelp 专用:TLS 伪装 | ❌ | ✅ TLS/JA3/HTTP2 | 低 | 很快 |
Scrapy 2.14 | 带分页的完整抓取流程 | 部分支持(通过 scrapy-playwright) | AutoThrottle、重试中间件 | 中高 | 快 |
Selenium 4.43 / Playwright 1.58 | JS 很重的页面、CAPTCHA 绕过 | ✅ | 部分支持 | 中等 | 慢(约 10–30 页/分钟) |
| Thunderbit | 非程序员、快速提取 | ✅(浏览器) | 内置(云端模式) | 很低 | 快 |
curl_cffi 的关键突破
真正改变我 Yelp 抓取工作流的库是 ——它是 curl-impersonate 的 Python 绑定。它发出的 TLS/JA3 + HTTP/2 指纹与真实 Chrome 完全一致,而且 API 可以直接替代 requests:
1from curl_cffi import requests
2r = requests.get(
3 "https://www.yelp.com/biz/some-restaurant",
4 impersonate="chrome131",
5)
6print(r.status_code, len(r.text))
这一处改动——from curl_cffi import requests 再加上 impersonate="chrome131"——就能绕过 Yelp 最大的一层反爬机制,而不需要启动浏览器。按我的测试,它和直接 403 与稳定返回 200 之间的差别非常明显。
我对 2025 年 Yelp 抓取最推荐的技术栈: curl_cffi + parsel + jmespath + 住宅代理。如果你需要完整抓取流程和定时任务,可以把它包进 Scrapy 2.14,并使用基于 curl_cffi 的 downloader middleware。
搭建 Python 环境来抓取 Yelp
- 难度: 中级
- 所需时间: 环境搭建约 15 分钟,做出可用爬虫约 1–2 小时
- 你需要准备: Python 3.10+(推荐 3.12)、终端,以及可选的住宅代理服务商
第 1 步:创建虚拟环境并安装依赖
1python3.12 -m venv .venv
2source .venv/bin/activate # Windows 上:.venv\Scripts\activate
3pip install "curl_cffi>=0.11" "parsel>=1.9" "jmespath>=1.0" pandas
各个包的作用:
curl_cffi—— 用 Chrome 的 TLS 指纹发起请求(也就是反爬绕过的关键)parsel—— 用 CSS/XPath 解析 HTML(Scrapy 同款引擎,但更轻量)jmespath—— 声明式 JSON 查询(比嵌套字典访问更适合 Yelp 内嵌 JSON)pandas—— 导出 CSV/Excel
可选但很有用:
1pip install fake-useragent # 注意:该仓库已于 2026 年 4 月归档,但目前仍可安装
分步骤:如何用 Python 抓取 Yelp
下面是核心教程。让整个方案更稳的关键思路是:不要依赖 CSS 选择器,优先提取隐藏 JSON。 Yelp 会在构建时随机化 CSS 类名(这周可能是 y-css-14xwok2,下周就变成 y-css-hcq7b9),所以任何绑定这些类名的爬虫,往往几周内就会失效。稳定的反而是页面里嵌入的 JSON 负载——application/ld+json schema 和 react-root-props。
第 2 步:抓取 Yelp 搜索结果页
Yelp 搜索 URL 的结构比较固定:https://www.yelp.com/search?find_desc={term}&find_loc={location}。搜索结果数据会作为 JSON 嵌在 <script data-id="react-root-props"> 标签里,而不是藏在一堆 CSS 类名里。
1import re, json, jmespath
2from curl_cffi import requests
3from parsel import Selector
4HEADERS = {
5 "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
6 "AppleWebKit/537.36 (KHTML, like Gecko) "
7 "Chrome/124.0.0.0 Safari/537.36",
8 "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,"
9 "image/avif,image/webp,image/apng,*/*;q=0.8",
10 "accept-language": "en-US,en;q=0.9",
11 "accept-encoding": "gzip, deflate, br",
12 "cookie": "intl_splash=false",
13}
14def scrape_search(term: str, location: str, max_pages: int = 3):
15 results = []
16 for page in range(max_pages):
17 url = (f"https://www.yelp.com/search?"
18 f"find_desc={term}&find_loc={location}&start={page * 10}")
19 r = requests.get(url, headers=HEADERS, impersonate="chrome131")
20 if r.status_code != 200:
21 print(f"第 {page} 页被拦截:{r.status_code}")
22 break
23 sel = Selector(text=r.text)
24 script = sel.xpath(
25 "//script[@data-id='react-root-props']/text()"
26 ).get() or ""
27 m = re.search(r"react_root_props\s*=\s*(\{.*?\});", script, re.S)
28 if not m:
29 print(f"第 {page} 页没有找到 react-root-props —— 可能是软拦截")
30 break
31 data = json.loads(m.group(1))
32 businesses = jmespath.search(
33 "legacyProps.searchAppProps.searchPageProps"
34 ".mainContentComponentsListProps"
35 "[?searchResultBusiness].searchResultBusiness.{"
36 "name: name, url: businessUrl, rating: rating, "
37 "reviews: reviewCount, phone: phone, "
38 "neighborhoods: neighborhoods}",
39 data,
40 ) or []
41 results.extend(businesses)
42 import time, random
43 time.sleep(random.uniform(3, 7))
44 return results
你应该能得到一个包含商家名称、URL、评分和评论数的字典列表。如果响应中没有 react-root-props,说明你拿到的是一个空壳拦截页——换 IP 再试。
Cookie: intl_splash=false 是一个常见的绕过 Yelp 国家提示页的做法。没有这个头时,非美国 IP 可能会先跳到一个看起来像软封锁但其实不是的页面。
第 3 步:抓取 Yelp 商家页面
搜索结果中的每个商家 URL 都会进入更丰富的详情页。最稳定的提取目标是 <script type="application/ld+json"> 区块——这里包含 Yelp 为 SEO 保留的结构化 schema.org 数据,而且通常不会被混淆。
1def scrape_business(biz_url: str) -> dict:
2 url = f"https://www.yelp.com{biz_url}" if biz_url.startswith("/") else biz_url
3 r = requests.get(url, headers=HEADERS, impersonate="chrome131")
4 if r.status_code != 200:
5 return {"url": url, "error": r.status_code}
6 sel = Selector(text=r.text)
7 biz_id = sel.css('meta[name="yelp-biz-id"]::attr(content)').get()
8 for raw in sel.css('script[type="application/ld+json"]::text').getall():
9 try:
10 data = json.loads(raw)
11 except json.JSONDecodeError:
12 continue
13 for node in (data if isinstance(data, list) else [data]):
14 if node.get("@type") in (
15 "Restaurant", "LocalBusiness", "FoodEstablishment",
16 "HealthAndBeautyBusiness", "HomeAndConstructionBusiness",
17 ):
18 return {
19 "biz_id": biz_id,
20 "name": node.get("name"),
21 "rating": (node.get("aggregateRating") or {}).get("ratingValue"),
22 "review_count": (node.get("aggregateRating") or {}).get("reviewCount"),
23 "address": node.get("address"),
24 "telephone": node.get("telephone"),
25 "price_range": node.get("priceRange"),
26 "hours": node.get("openingHours"),
27 "url": url,
28 }
29 return {"biz_id": biz_id, "url": url}
meta[name="yelp-biz-id"] 的值是经过编码的商家 ID,后面抓评论接口时会用到。先把它取出来,下一步会用。
第 4 步:带分页抓取 Yelp 评论
这就是 Fusion API 最弱、而抓取最强的地方。Yelp 的内部 GraphQL 批处理接口会返回完整评论文本、评论者信息、日期、评分和投票数——这些都是 API 不提供的。
接口是 https://www.yelp.com/gql/batch,它对 GetBusinessReviewFeed 操作使用一个固定的 documentId。分页则通过 base64 编码的 cursor 来控制。
1import base64
2GQL_URL = "https://www.yelp.com/gql/batch"
3DOC_ID = "ef51f33d1b0eccc958dddbf6cde15739c48b34637a00ebe316441031d4bf7681"
4def fetch_reviews(enc_biz_id: str, num_pages: int = 5):
5 all_reviews = []
6 for page in range(num_pages):
7 offset = page * 10
8 cursor = base64.b64encode(
9 json.dumps({"version": 1, "offset": offset}).encode()
10 ).decode()
11 payload = [{
12 "operationName": "GetBusinessReviewFeed",
13 "variables": {
14 "encBizId": enc_biz_id,
15 "reviewsPerPage": 10,
16 "after": cursor,
17 "sortBy": "DATE_DESC",
18 "language": "en",
19 },
20 "extensions": {
21 "operationType": "query",
22 "documentId": DOC_ID,
23 },
24 }]
25 r = requests.post(
26 GQL_URL,
27 json=payload,
28 headers={
29 **HEADERS,
30 "content-type": "application/json",
31 "x-apollo-operation-name": "GetBusinessReviewFeed",
32 "apollographql-client-name": "yelp-main-frontend",
33 },
34 impersonate="chrome131",
35 )
36 if r.status_code != 200:
37 print(f"在 offset {offset} 处抓取评论失败:{r.status_code}")
38 break
39 data = r.json()
40 # 按响应结构提取评论
41 try:
42 reviews = data[0]["data"]["business"]["reviews"]["edges"]
43 for edge in reviews:
44 node = edge.get("node", {})
45 all_reviews.append({
46 "reviewer": node.get("author", {}).get("displayName"),
47 "rating": node.get("rating"),
48 "date": node.get("localizedDate"),
49 "text": node.get("text", {}).get("full"),
50 })
51 except (KeyError, IndexError, TypeError):
52 break
53 import time, random
54 time.sleep(random.uniform(3, 7))
55 return all_reviews
每一页会返回 10 条评论。通过修改 base64 cursor 里的 offset 就可以继续翻页。sortBy 参数支持 DATE_DESC(最新优先)、RATING_ASC、RATING_DESC 等选项。
第 5 步:导出抓到的 Yelp 数据
1import pandas as pd
2# 假设你已经收集好了 businesses 和 reviews
3df_businesses = pd.DataFrame(businesses)
4df_businesses.to_csv("yelp_businesses.csv", index=False)
5df_reviews = pd.DataFrame(all_reviews)
6df_reviews.to_csv("yelp_reviews.csv", index=False)
7# 或者保存为 JSON,方便后续处理
8import json
9with open("yelp_data.json", "w") as f:
10 json.dump({"businesses": businesses, "reviews": all_reviews}, f, indent=2)
如果你走的是无代码路线,Thunderbit 可以把同样的数据直接导出到 Excel、Google Sheets、Airtable 或 Notion——不需要 pandas,也不需要自己写文件导出代码。
反封锁实战手册:如何抓取 Yelp 而不被封
这一节基本就是整篇文章存在的原因。自 2024 年底以来,Yelp 的反爬机制明显变强—— 全都在上阵。很多旧教程都已经过时,因为它们写于这波加强封禁之前。

策略必须分层推进。每一层都能降低被封概率;叠加起来,才有持续抓取的可行性。
第 1 层:尽量真实的请求头
Python requests 默认会发出 User-Agent: python-requests/2.x,这会立刻被拦。但就算把 User-Agent 改得像真浏览器,也还不够。Yelp 会检查完整的 头部是否一致。
1FULL_HEADERS = {
2 "authority": "www.yelp.com",
3 "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
4 "AppleWebKit/537.36 (KHTML, like Gecko) "
5 "Chrome/124.0.0.0 Safari/537.36",
6 "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,"
7 "image/avif,image/webp,image/apng,*/*;q=0.8",
8 "accept-language": "en-US,en;q=0.9",
9 "accept-encoding": "gzip, deflate, br",
10 "sec-ch-ua": '"Chromium";v="124", "Google Chrome";v="124", "Not-A.Brand";v="99"',
11 "sec-ch-ua-mobile": "?0",
12 "sec-ch-ua-platform": '"Windows"',
13 "sec-fetch-dest": "document",
14 "sec-fetch-mode": "navigate",
15 "sec-fetch-site": "same-origin",
16 "sec-fetch-user": "?1",
17 "upgrade-insecure-requests": "1",
18 "referer": "https://www.yelp.com/",
19 "cookie": "intl_splash=false",
20}
容易被标记的三个错误:
- UA 说自己是 Chrome,但
sec-ch-ua缺失,或者和 UA 版本对不上 sec-ch-ua-platform写的是 “Windows”,但 UA 字符串却是 macOS- 同一个 IP 在成千上万次请求里都使用完全一样的 UA——最好维护一个包含 10–20 条近期期的 Chrome/Firefox/Safari UA 池并轮换使用
第 2 层:限速和随机延迟
可预测的请求节奏会很可疑。给请求加入随机睡眠,并在出错时做指数退避。
1import random, time
2def polite_get(client_get, url, attempt=0):
3 r = client_get(url, headers=FULL_HEADERS, impersonate="chrome131")
4 if r.status_code in (403, 429, 503):
5 if attempt >= 4:
6 raise RuntimeError(f"在 {url} 上重试 {attempt + 1} 次后仍被封")
7 backoff = 2 ** (attempt + 1) + random.random()
8 print(f" 返回 {r.status_code},退避 {backoff:.1f} 秒(第 {attempt + 1} 次尝试)")
9 time.sleep(backoff)
10 return polite_get(client_get, url, attempt + 1)
11 time.sleep(random.uniform(3, 7))
12 return r
| 参数 | 建议值 |
|---|---|
| 请求间随机睡眠 | random.uniform(3, 7) 秒 |
| 遇到 429/403/503 时退避 | 2 → 4 → 8 → 16 秒,最多 5 次 |
| 每个 IP 的并发数 | 1(单 IP 串行;并行请用代理) |
| 单个住宅 IP 的持续速率上限 | 约 1 请求 / 5 秒(约 12 rpm) |
第 3 层:User-Agent 和会话轮换
使用真实浏览器的 User-Agent 池进行轮换。同时保留会话和 cookie,让行为更像真实用户浏览。Yelp 会基于 cookie 做检测,所以每次请求都新建一个 session 反而很可疑。
1UA_POOL = [
2 "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/124.0.0.0 Safari/537.36",
3 "Mozilla/5.0 (Macintosh; Intel Mac OS X 14_4_1) AppleWebKit/537.36 Chrome/124.0.0.0 Safari/537.36",
4 "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:125.0) Gecko/20100101 Firefox/125.0",
5 "Mozilla/5.0 (Macintosh; Intel Mac OS X 14_4_1; rv:125.0) Gecko/20100101 Firefox/125.0",
6 "Mozilla/5.0 (Macintosh; Intel Mac OS X 14_4_1) AppleWebKit/605.1.15 Safari/17.4.1",
7 # 再补 5-10 条近期字符串
8]
第 4 层:代理轮换
只要到了真实量级,就必须用住宅代理。数据中心代理和免费代理在 Yelp 上基本没用——Yelp 的 IP 信誉层会预先封掉 AWS、GCP 和 DigitalOcean 这些 IP 段。
| 服务商 | 起步 $/GB | 说明 |
|---|---|---|
| IPRoyal | $1.75/GB | 最便宜;运行最常被引用的 Yelp 教程 |
| Decodo(原 Smartproxy) | $3.20–$3.50 | 大规模使用时性价比高 |
| Bright Data | $4.00(按量计费) | 1.5 亿+ IP 池;专门有 Yelp Proxies 页面 |
| Oxylabs | $6.00–$8.00 | 高端方案;1000 万+ IP |
| Aluvia(手机 SIM) | $3.00 | 真实美国运营商移动 IP,专门面向 Yelp 场景 定位清晰 |
轮换住宅代理(每次请求换一个新 IP)最适合高并发的搜索页抓取。粘性会话(一个 IP 保持 10 分钟)更适合在商家页 → 评论页 → 分页这个链路中持续保留 cookie。
第 5 层:识别并处理封锁
不是所有封锁都长得一样。Yelp 常常返回一个通用的“页面不可用”壳,而不是直接给 CAPTCHA,所以天真的爬虫会误以为拿到了页面,实际上只是空响应。
1BLOCK_MARKERS = (
2 "captcha", "px-captcha", "page not available",
3 "access denied", "unusual traffic",
4)
5def is_blocked(resp):
6 if resp.status_code in (401, 403, 429, 503):
7 return True
8 body = resp.text.lower()
9 if any(m in body for m in BLOCK_MARKERS):
10 return True
11 # 如果是搜索页或商家页,但缺少 react-root-props,
12 # 说明 Yelp 返回的是被裁剪过的封锁页
13 if "react-root-props" not in body and "/biz/" in str(resp.url):
14 return True
15 return False
| 信号 | 含义 |
|---|---|
| HTTP 403 | 硬封锁——IP/头部/TLS 已被判定异常 |
| HTTP 429 | 触发限流——通常可通过退避恢复 |
| HTTP 503 | 通用封锁或服务降载 |
跳转到 /error 或页面正文出现 “page not available” | 软封锁 |
| 为空,只剩 | JS 挑战页 |
正文中出现 captcha / g-recaptcha / px-captcha | 升级封锁——需要 CAPTCHA |
列表页缺少 react-root-props | 裁剪后的封锁响应 |
第 6 层:更稳的解析方式——优先隐藏 JSON,而不是 CSS 选择器
再强调一次:Yelp 会在构建时随机化 CSS 类名。把爬虫绑定到 h3.y-css-14xwok2 这种选择器上,等 Yelp 下次重新部署为 h3.y-css-hcq7b9 时,几周内就会崩掉。
真正不会轻易变化的内容有:
<script type="application/ld+json">—— schema.org 结构化数据(名称、地址、电话、评分、营业时间)<script data-id="react-root-props">—— 以 JSON 形式保存的完整搜索结果数据https://www.yelp.com/gql/batch—— 带稳定documentId的 GraphQL 评论接口
如果你还在解析 CSS 类名,那就是在沙地上盖房子。应该改为解析 JSON。
第 7 层:隐身浏览器兜底方案
只有当 curl_cffi + 住宅代理还是过不去时,才升级到无头浏览器——通常是 Yelp 给了一个 JavaScript 挑战页或 CAPTCHA。
对 95% 的商家/搜索/评论抓取任务来说,curl_cffi + 隐藏 JSON + 住宅代理,比浏览器更快、更省钱、也更稳定。但如果确实需要浏览器,可以考虑:
| 工具 | 2025 状态 | 说明 |
|---|---|---|
| rebrowser-playwright | 推荐起点 | 对 Playwright 做了补丁,可修复 CDP 泄漏 |
| nodriver | Chrome 隐身能力一流 | undetected-chromedriver 的继任者;完全绕开 WebDriver 协议 |
| patchright | 仍在积极维护的 Playwright 分支 | 能通过现代检测测试 |
| playwright-stealth | 比较成熟 | 修补 navigator.webdriver,并从 UA 中去掉 HeadlessChrome |
Yelp 上别用原生 Selenium,太容易被指纹识别了。
Yelp Fusion API vs. Python 抓取 vs. Thunderbit:完整对比
| 维度 | Yelp Fusion API | Python 抓取 | Thunderbit |
|---|---|---|---|
| 完整评论文本 | ❌ 3 条摘要 × 每条约 160 字符 | ✅ 无限量(GraphQL) | ✅ 内置评论模板 |
| 评论元数据(投票、商家回复) | ❌ | ✅ | ✅ 通过 AI 推荐字段 |
| 图片 | ❌(Base 方案为 0) | ✅ 不限 | ✅ |
| 单次搜索最大结果数 | 240(2024 前曾是 1,000) | 不限(分页) | 不限 |
| 每日速率限制 | 新客户 300–500 / 天,老客户 5,000 / 天 | 只受代理预算限制 | 按点数计费(Pro 每月 3,000) |
| 上手成本 | 约 15 分钟 | 数小时到数天 | 约 2 分钟 |
| 反爬处理 | 不适用 | 你自己负责 | 已处理(云端模式) |
| 法律风险 | 低(官方授权) | 中等(ToS 灰色地带) | 中等(与抓取相同) |
| 起步成本 | 每月 29 美元 | 约 $0.75–$4/GB 代理费 + 开发时间 | 免费方案 |
| 重度使用成本 | $643+/月 | 每月 $50–$500 代理费 + 开发时间 | 每月 $38–$49 |
| 数据导出 | JSON | CSV/JSON(需自行编写) | Excel / Sheets / Airtable / Notion — 免费 |
| 维护成本 | 低 | 高(选择器失效、反爬升级) | 低(AI 自动适配) |
抓取 Yelp 的法律与伦理建议
我不是律师,这也不是法律意见。但过去两年里,法律环境变化已经大到足以让你在投入 Yelp 抓取项目之前,先了解最基本的规则。
Yelp 的服务条款怎么说: 明确禁止使用“任何机器人、蜘蛛程序……或其他自动化设备”去“访问、检索、复制、抓取或索引服务的任何部分”。同时还新增了关于“AI 技术和/或其他自动化工具”的措辞。
:"Yelp 不允许对网站进行任何抓取。"
robots.txt 怎么说: Yelp 的 里有通配规则 User-agent: * / Disallow: /,并且明确屏蔽了 GPTBot、ClaudeBot、PerplexityBot、CCBot 和 Meta-ExternalAgent。只有 Googlebot、Bingbot 以及少数社交媒体爬虫被放行。
真正重要的法律先例: 在 (美国加州北区,2024 年 1 月)一案中,法院裁定抓取公开可见、未登录的数据并不违反 Meta 的服务条款。关键区别在于:未登录的公开数据 vs. 登录后数据。 案建立了一个原则:抓取公开数据大概率不违反 CFAA,但 hiQ 仍在州法侵权主张(动产侵入、挪用)上败诉,并被判赔 50 万美元。
实操建议:
- 只抓取公开可见、未登录页面
- 控制请求频率(本文中的延迟设置也顺带承担了伦理限速的作用)
- 不要把带实名用户的原始评论文本拿去转售——尊重评论者隐私
- 遵守当地数据保护法规(CCPA、GDPR)
- 不要登录后再抓取——这会越过授权边界
- 把商家信息(名称/地址/电话/评分)视为公开事实数据;把评论文本视为更敏感的数据
请就你的具体情况咨询法律专业人士。
总结
三条路径,一个目标。
Yelp Fusion API 是官方授权、维护成本低的选项——但它最多只能给你 3 条评论摘要,而且起步就要每月 29 美元。Python 抓取则让你能完全掌控 Yelp 上的每个数据点,但也意味着要投入真正的工程成本:用 curl_cffi 做 TLS 指纹伪装、使用住宅代理、随机延迟、解析隐藏 JSON,以及随着 Yelp 防护升级持续维护。Thunderbit 则能把“我需要 Yelp 数据”直接变成“这是我的表格”,大约 30 秒搞定,不用写代码,也不用配置代理。
2025 年真正有效的防封核心是:带完整 Client Hints 的真实请求头、用 curl_cffi 伪装 TLS 指纹、随机延迟加指数退避、住宅代理轮换,以及——最重要的——优先解析隐藏 JSON(application/ld+json 和 react-root-props),而不是脆弱的 CSS 选择器。
还不确定哪条路更适合你?先试试 。如果它已经满足需求,你就省下了好几个小时;如果你需要更强控制力——完整程序化流程、自定义字段、深度 CRM 集成——上面的 Python 指南已经够你直接上手了。想进一步了解网页抓取工具全景,也可以看看我们整理的 或者 。
常见问题
我可以免费用 Python 抓取 Yelp 吗?
可以——使用 curl_cffi、parsel 和 jmespath 这类免费库就行。但只要到了真正的量级(超过几十个页面),你就需要付费的住宅代理,价格一般从 起。Thunderbit 也提供免费方案,每月可抓 6 页,适合快速无代码提取。
Yelp 会封爬虫吗?
会,而且非常积极。Yelp 会使用 。原生 requests 基本第一次就会被封。本文中的分层防封策略——curl_cffi 做 TLS 伪装、真实请求头、随机延迟和住宅代理——才是 2025 年真正有效的方法。
Yelp Fusion API 比抓取更好吗?
要看你的需求。API 是官方授权的,风险低,但它只会返回 ,搜索结果上限 240 条,而且起步就要每月 29 美元。如果你需要完整评论文本、评论元数据,或者每天要抓几百条以上的数据,那抓取可能才是唯一选择。
我该怎么用 Python 抓取 Yelp 评论?
先用 curl_cffi 并设置 impersonate="chrome131" 请求商家页面,从 <meta name="yelp-biz-id"> 里提取编码后的商家 ID,再向 https://www.yelp.com/gql/batch 发送 GetBusinessReviewFeed 请求,并通过 base64 编码的 after cursor 做分页。上面的教程部分已经给出了完整步骤代码。你也可以参考 作为实现参考。
不写代码也能抓 Yelp 吗?
可以—— 已经内置了 和 模板。打开 Yelp 页面,点击 AI Suggest Fields,再点击 Scrape 就行。导出到 Google Sheets、Excel、Airtable 和 Notion 对所有套餐都免费,包括免费版。
了解更多