如何用 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 企業產品
電商與營運價格訊號、顧客抱怨、服務時間觀察競爭對手如何被評價,找出營運模式

核心其實很一致:真正的目標是結構化資料,而 Python 只是其中一種取得方式。有些讀者想保有完整程式控制權;也有人只需要一份奧斯汀水電工聯絡名單的試算表。這篇文章兩條路都會涵蓋。

Yelp Fusion API 與 Python 網頁抓取:到底該選哪個?

很多教學都直接跳過這個決策,沒有先判斷官方的 (現在改名為「Yelp Places API」)是否已經足夠。依我的經驗,先做這個評估可以省下大量時間,因為 API 很適合某些需求,但對另一些需求則完全不夠用。

Fusion API 實際能拿到什麼

Fusion API 提供結構化的商家搜尋、商家詳情、自動完成,以及評論端點。它是授權的、文件完整,也不需要任何反機器人技巧。

但真正卡關的是評論端點。以下是 Yelp 員工在 GitHub 上確認過的內容:

「Yelp API 不會回傳完整評論文字。預設只提供 3 則、每則 160 字元的評論摘錄。」 —

這不是 bug,而是設計如此。API 硬性限制最多 3 則評論摘要(Premium 版最多 7 則),而且每則都只會截到約 160 字元。沒有評論中繼資料(有幫助/有趣/很酷的投票)、沒有評論者歷史、也沒有店家回覆。而且在 2023 年 5 月之後,,從原本的 5,000 次大幅下修。入門方案價格從 起。

判斷框架

比較項目Yelp Fusion APIPython 網頁抓取Thunderbit(免寫程式)
完整評論❌ 只有 3 則摘要(每則約 160 字)✅ 透過 GraphQL 取得所有評論✅ 取得所有可見評論
速率限制300–500/日(新帳號);5,000(舊帳號)自行管理(靠代理成本)以點數計費
設定成本約 15 分鐘(API key + SDK)幾小時到幾天約 2 分鐘
商家欄位約 20 個結構化欄位不限(可解析 HTML/JSON)AI 建議欄位
防封鎖處理不適用(授權 API)必須自己做自動處理
法規風險✅ 授權⚠️ 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 會讀取頁面並建議欄位(商家名稱、評分、評論數、價格區間、分類、地址、電話、網址)
  3. 點選 Scrape — 完成

如果使用預建的 Yelp 範本,流程更簡單:打開範本,點 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、真實 headers、隨機延遲——但只要是原生 requests,還是立刻收到 403 Forbidden。

套件選擇矩陣

函式庫最適合支援 JS?可抗機器人?學習曲線速度
requests + BeautifulSoup簡單單頁抓取(對 Yelp 已失效)非常低快(直到被封)
httpx async + parsel大規模非同步抓取非常快
curl_cffi + parselYelp 專用:TLS 模擬✅ TLS/JA3/HTTP2非常快
Scrapy 2.14含分頁的完整爬取流程部分(透過 scrapy-playwright)AutoThrottle、重試 middleware中高
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 + residential proxies。如果你需要完整抓取流程與排程功能,可以再包一層 Scrapy 2.14,並使用基於 curl_cffi 的 downloader middleware。

建立 Python 環境,開始抓取 Yelp

  • 難度: 中等
  • 所需時間: 設定約 15 分鐘,做出可用爬蟲約 1–2 小時
  • 你需要準備: Python 3.10+(建議 3.12)、終端機,以及可選的 residential proxy 供應商

步驟 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 指紋發送 HTTP 請求(反機器人繞過關鍵)
  • parsel — 解析 HTML 的 CSS/XPath 選擇器(與 Scrapy 同樣引擎,但更輕量)
  • jmespath — 宣告式 JSON 查詢(處理 Yelp 內嵌 JSON 比巢狀 dict 存取更乾淨)
  • pandas — 將資料匯出為 CSV / Excel

選配但很實用:

1pip install fake-useragent  # 注意:這個 repo 在 2026 年 4 月已封存,但仍可安裝

逐步教學:如何用 Python 抓取 Yelp

這是核心教學。讓整個流程更穩定的關鍵洞見是:不要依賴 CSS 選擇器,改抓隱藏 JSON。 Yelp 會在建置時隨機化 CSS 類名(這週是 y-css-14xwok2,下週可能變 y-css-hcq7b9),所以任何綁死這些類名的爬蟲都會在幾週內失效。相較之下,內嵌 JSON payload —— application/ld+json schema 與 react-root-props —— 是穩定的。

步驟 2:抓取 Yelp 搜尋結果

Yelp 搜尋網址格式很固定: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

你應該會拿到一串 dict,內含商家名稱、網址、評分與評論數。如果回應裡缺少 react-root-props,代表你拿到的是被封鎖的空殼頁面——請更換 IP 再試一次。

Cookie: intl_splash=false 是 Yelp 國家地區跳轉頁的標準解法。沒加的話,非美國 IP 常會先看到一個看起來像軟封鎖、但其實不是的 splash 頁。

步驟 3:抓取 Yelp 商家頁

搜尋結果中的每個商家網址,都會導向一個資料更完整的細節頁。最穩定的擷取目標是 <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 字串

第 2 層:限速與隨機延遲

可預測的請求節奏很可疑。加入隨機 sleep,並在出錯時做指數退避。

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 序列化;並行請用代理)
單個 residential IP 的長期速率上限約 1 次 / 5 秒(約 12 rpm)

第 3 層:User-Agent 與 Session 輪換

使用一組真實瀏覽器的 User-Agent 池來輪替。保留 session 與 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; 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 層:代理輪換

只要有一定流量,你就需要 residential proxies。資料中心 IP 和免費代理在 Yelp 上幾乎沒用——Yelp 的 IP 信譽層會預先封鎖 AWS、GCP 和 DigitalOcean 的 IP 範圍。

供應商入門 $/GB備註
IPRoyal$1.75/GB最便宜;也是最常被引用的 Yelp 教學來源
Decodo(前 Smartproxy)$3.20–$3.50量大時 GB/美元比最佳
Bright Data$4.00(按量付費)1.5 億+ IP 池;有專門的 Yelp Proxies 頁面
Oxylabs$6.00–$8.00高階方案;1,000 萬+ IP
Aluvia(行動 SIM)$3.00真實美國電信商行動 IP,特別為 Yelp 抓取定位

輪換式 residential proxy(每次請求換 IP)最適合大量搜尋結果爬取。Sticky session(同一個 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 / header / 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 + residential proxies 真的過不去時,才升級到 headless browser——通常是 Yelp 送出 JavaScript challenge page 或 CAPTCHA 的時候。

對 95% 的商家/搜尋/評論抓取來說,curl_cffi + 隱藏 JSON + residential proxies 比瀏覽器更快、更便宜、也更穩定。但如果你真的需要瀏覽器:

工具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 Technologies 和/或其他自動化工具」的描述。

:「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 指紋模擬、residential proxies、隨機延遲、隱藏 JSON 解析,以及隨著 Yelp 防禦升級而持續維護。Thunderbit 則能讓你從「我需要 Yelp 資料」直接變成「這是我的試算表」,大約只要 30 秒,完全不用寫程式,也不用設定代理。

2025 年真正有效的防封鎖要點是:帶完整 Client Hints 的真實 headers、用 curl_cffi 模擬 TLS 指紋、隨機延遲與指數退避、residential proxy 輪換,以及最重要的——解析隱藏 JSON(application/ld+jsonreact-root-props),而不是脆弱的 CSS 選擇器。

還不確定哪條路適合你?先試試 。如果它已經滿足需求,你就省下了好幾小時;如果你需要更多控制權——完整程式化流程、客製欄位、與 CRM 的緊密整合——上面的 Python 指南就能幫上忙。若你想更深入了解抓取工具版圖,也可以看看我們整理的

試用 Thunderbit 進行 Yelp 資料擷取

常見問題

可以用 Python 免費抓取 Yelp 嗎?

可以——使用 curl_cffiparseljmespath 這類免費函式庫。不過只要規模稍微大一點(超過幾十頁),你就需要付費的 residential proxies,價格大約從 起。Thunderbit 也提供免費方案,每月可擷取 6 頁,適合快速、免寫程式的抓取需求。

Yelp 會封鎖爬蟲嗎?

會,而且非常積極。Yelp 使用 。原生 requests 第一個請求就會被封。本文介紹的分層防封鎖策略——curl_cffi 做 TLS 模擬、真實 headers、隨機延遲與 residential proxies——才是 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