我如何用 Python 抓取 Yelp 而不被封锁

最后更新于 April 15, 2026

Yelp 目前累计拥有超过 ,分布在 上——但把这些数据整理成可用格式,反而比以前更难了。Yelp 在 2024–2025 年加强了反爬措施,悄悄让大多数现有的 Python 抓取教程失效。

如果你最近尝试运行 Yelp 爬虫,却频繁遇到 403 错误、空白 HTML 响应,或者六个月前还没有的 CAPTCHA,那你不是在瞎想。Yelp 现在会做 TLS/JA3 指纹识别、轮换混淆后的 CSS 类名,并对 IP 信誉进行严格评分——也就是说,几乎所有教程还在推荐的 requests + BeautifulSoup 老套路,往往第一次请求就直接阵亡。我花了好几周测试 Yelp 现有的防护体系,这篇指南会覆盖 2025 年真正可用的所有方法:官方 Fusion API(以及为什么它大概率还不够)、一套带分层防封策略的 Python 抓取流程,以及一个只需两步、无需写代码的替代方案 ,适合只想拿到数据、不想陷入调试马拉松的读者。

为什么要用 Python 抓取 Yelp(谁最能受益)

在写一行代码之前,先问一个现实问题:Yelp 数据到底有什么业务价值?它不仅仅是餐厅点评网站,更像是一个实时更新的本地商家数据库,包含结构化的联系方式、评分、分类、营业时间,以及数以亿计的用户评论。

yelp_stats_bd6a43108e.png

下面这些场景最常受益,以及他们通常会提取什么数据:

使用场景关键数据字段价值所在
销售与线索开发商家名称、电话、网站、地址、分类、评分构建精准的本地中小企业潜客名单——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 APIPython 网页抓取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 现成模板:

  • —— 提取商家名称、评分、联系方式、地址、营业时间、分类
  • —— 提取评论者用户名、评论内容、评分、日期、评论者所在地

实际操作方式

  1. 在 Chrome 中打开 Yelp 搜索结果页或商家详情页
  2. 点击 中的 AI Suggest Fields,AI 会自动读取页面并推荐列名(商家名、评分、评论数、价格区间、分类、地址、电话、URL)
  3. 点击 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简单单页抓取(对 Yelp 已失效)很低快(直到被封)
httpx async + parsel大规模异步抓取很快
curl_cffi + parselYelp 专用:TLS 伪装✅ TLS/JA3/HTTP2很快
Scrapy 2.14带分页的完整抓取流程部分支持(通过 scrapy-playwright)AutoThrottle、重试中间件中高
Selenium 4.43 / Playwright 1.58JS 很重的页面、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_ASCRATING_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 的反爬机制明显变强—— 全都在上阵。很多旧教程都已经过时,因为它们写于这波加强封禁之前。

yelp_antiblock_518f0447bb.png

策略必须分层推进。每一层都能降低被封概率;叠加起来,才有持续抓取的可行性。

第 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}

容易被标记的三个错误:

  1. UA 说自己是 Chrome,但 sec-ch-ua 缺失,或者和 UA 版本对不上
  2. sec-ch-ua-platform 写的是 “Windows”,但 UA 字符串却是 macOS
  3. 同一个 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 泄漏
nodriverChrome 隐身能力一流undetected-chromedriver 的继任者;完全绕开 WebDriver 协议
patchright仍在积极维护的 Playwright 分支能通过现代检测测试
playwright-stealth比较成熟修补 navigator.webdriver,并从 UA 中去掉 HeadlessChrome

Yelp 上别用原生 Selenium,太容易被指纹识别了。

Yelp Fusion API vs. Python 抓取 vs. Thunderbit:完整对比

维度Yelp Fusion APIPython 抓取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
数据导出JSONCSV/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+jsonreact-root-props),而不是脆弱的 CSS 选择器。

还不确定哪条路更适合你?先试试 。如果它已经满足需求,你就省下了好几个小时;如果你需要更强控制力——完整程序化流程、自定义字段、深度 CRM 集成——上面的 Python 指南已经够你直接上手了。想进一步了解网页抓取工具全景,也可以看看我们整理的 或者

试试用 Thunderbit 提取 Yelp 数据

常见问题

我可以免费用 Python 抓取 Yelp 吗?

可以——使用 curl_cffiparseljmespath 这类免费库就行。但只要到了真正的量级(超过几十个页面),你就需要付费的住宅代理,价格一般从 起。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 对所有套餐都免费,包括免费版。

了解更多

目录

试试 Thunderbit

只需 2 次点击即可抓取线索和其他数据。由 AI 驱动。

获取 Thunderbit 免费试用
使用 AI 提取数据
轻松将数据转移到 Google Sheets、Airtable 或 Notion
Chrome Store Rating
PRODUCT HUNT#1 Product of the Week