Yelpビジネスデータのスクレイピング:公開データへのアクセス方法
法的注意事項: 本記事は公開データへのアクセスに限定した技術解説です。Yelpの利用規約を遵守し、米国ではCFAA(コンピュータ不正利用法)、EUではGDPRが適用される点にご留意ください。ログインが必要なコンテンツのスクレイピングは推奨しません。本ガイドは公開ページからアクセス可能な情報のみを対象とします。
ローカルビジネスのデータセット構築において、Yelpビジネスデータのスクレイピングは多くの開発者が直面する課題です。店舗名、住所、評価、カテゴリ、営業時間、レビュー本文はすべて公開ページ(/biz/<slug>)から取得可能ですが、YelpはPerimeterX(現HUMAN)による強力なアンチボット防御を導入しており、データセンタープロキシではわずか5〜10回のリクエストでCAPTCHAが表示されます。本ガイドでは、ローテーション型レジデンシャルプロキシを用いたYelpレビューのスクレイピング手法を、実装コードとともに解説します。
ログイン不要で取得できる公開データとFusion APIの制限
Yelpのビジネスページ(https://www.yelp.com/biz/<slug>)にログインせずにアクセスすると、以下のデータが公開されています:
- 店舗名、住所、電話番号
- 総合評価(1〜5の星)とレビュー総数
- カテゴリ(例:レストラン、美容院)
- 営業時間
- レビュー本文、投稿者名、投稿日、個別評価
- 写真のURL
一方、Yelpが公式提供するFusion APIには重要な制限があります。ビジネス詳細エンドポイント(/businesses/{id})は最大3件のレビューしか返しません。数千件のレビューを持つ店舗の完全なデータセットを構築するには、公開ページのスクレイピングが事実上唯一の手段となります。ただし、Fusion APIは認証済みの安定的なアクセスを提供するため、3件のレビューで要件を満たせる場合はAPIの利用を強く推奨します。
| 取得方法 | レビュー取得数 | ブロックリスク | コスト | 適用シーン |
|---|---|---|---|---|
| Fusion API | 最大3件 | 低 | 無料(制限あり) | 少量の基本データ取得 |
| 直接アクセス(プロキシなし) | 全件 | 極高 | 無料 | 非推奨 |
| データセンタープロキシ | 全件 | 高 | 低 | テスト用途のみ |
| レジデンシャルプロキシ | 全件 | 中〜低 | 中 | 本番運用に推奨 |
Yelpのアンチボット防御:PerimeterX/HUMANの仕組み
YelpはHUMAN Security(旧PerimeterX)のボット検知プラットフォームを採用しています。このシステムは複数のシグナルを組み合わせて自動化リクエストを検出します:
_px3 Cookieとセンサーチャレンジ
PerimeterXはブラウザ側でJavaScriptを実行し、_px3というCookieを生成します。このCookieにはデバイスフィンガープリント、マウス移動パターン、タイミングデータなどがエンコードされており、正規ブラウザからのリクエストであることを証明します。単純なHTTPリクエストではこのCookieを生成できず、PerimeterXは即座にリクエストをブロックします。
TLSフィンガープリンティング
PerimeterXはTLSハンドシェイクのパターンも分析します。Pythonのrequestsライブラリが使用するTLSスタックは、ChromeやFirefoxのそれとは異なるシグネチャを持ちます。JA3/JA4フィンガープリントと呼ばれるこの技術により、ヘッダを偽装してもTLSレベルで自動化ツールが識別されます。curl_cffiやtls-clientなどのライブラリでブラウザのTLS指紋を模倣することで、この検出を回避できます。
CAPTCHAとIPレピュテーション
データセンタープロキシからのリクエストは、IPレピュテーションデータベースで「データセンター」としてフラグ付けされます。PerimeterXはデータセンターIPからのリクエストに対して、わずか数回のアクセスでCAPTCHAチャレンジをトリガーします。一方、レジデンシャルIPは一般家庭のISPから割り当てられたIPアドレスであり、IPレピュテーションスコアが高いため、より多くのリクエストが許容されます。
なぜローテーション型レジデンシャルプロキシが必要か
Yelpのスクレイピングにおいてレジデンシャルプロキシが必須となる理由は3つあります:
1. PerimeterXのIPレピュテーション回避: データセンターIPは即座にフラグ付けされますが、レジデンシャルIPは実在するISPからの接続として扱われます。ローテーション型プロキシは各リクエストでIPを変更するため、単一IPからの異常なリクエスト量として検出されるリスクを分散できます。
2. 地域別ビジネス検索の実行: Yelpの検索結果は地理位置に依存します。例えば、オースティンのレストランを検索するには、テキサス州のIPアドレスからのリクエストが必要です。ProxyHatでは-country-US-city-austinパラメータで地理ターゲティングが可能です。
3. スティッキーセッションでの継続性: ページネーションをまたぐスクレイピングでは、同一IPでセッションを維持することが重要です。-session-abc123フラグを使用すると、指定したセッションIDで同一IPを維持しつつ、別セッションでは新IPに切り替えられます。
ProxyHatのロケーション一覧では、対応する国・都市の組み合わせを確認できます。また、プランページでレジデンシャルプロキシの料金体系を確認できます。
Pythonでの実装:ProxyHat経由でレビューデータを取得
以下の例では、ProxyHatのレジデンシャルプロキシを経由してYelpビジネスページにアクセスし、ページ内に埋め込まれた__INITIAL_STATE__JSONブロブからレビューデータを抽出します。このブロブはYelpがReactアプリの初期状態としてページソースに埋め込むもので、APIエンドポイントに追加リクエストを送ることなくレビューデータにアクセスできます。
import requests
import json
import re
import time
import random
# ProxyHat residential proxy with US geo-targeting
proxy_url = "http://user-country-US-city-austin:pass@gate.proxyhat.com:8080"
proxies = {"http": proxy_url, "https": proxy_url}
# Rotate user-agents to reduce fingerprint risk
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",
]
headers = {
"User-Agent": random.choice(user_agents),
"Accept-Language": "en-US,en;q=0.9",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
}
url = "https://www.yelp.com/biz/some-restaurant-austin"
response = requests.get(url, proxies=proxies, headers=headers, timeout=30)
if response.status_code == 200:
# Extract __INITIAL_STATE__ blob from page source
match = re.search(
r'__INITIAL_STATE__\s*=\s*({.*?});\s*</script>',
response.text,
re.DOTALL
)
if match:
state = json.loads(match.group(1))
# Navigate to review objects (structure may vary)
review_data = state.get("review", {})
reviews = review_data.get("reviews", []) if isinstance(review_data, dict) else review_data
for review in reviews[:5]:
print({
"author": review.get("author", ""),
"rating": review.get("rating", ""),
"date": review.get("date", ""),
"text": (review.get("text", "") or "")[:200],
})
else:
print("__INITIAL_STATE__ not found in page source")
elif response.status_code == 403:
print("Blocked by PerimeterX — try rotating session ID")
else:
print(f"Unexpected status: {response.status_code}")
# Rate-limit pacing: 20 requests/minute max
time.sleep(3)
この例では、gate.proxyhat.com:8080をHTTPプロキシとして使用し、ユーザー名に-country-US-city-austinを指定してオースティンのレジデンシャルIPを取得しています。リクエストごとにUser-Agentをローテーションし、3秒間のインターバルでレートを1分あたり約20リクエストに制限しています。
Yelpのページ構造は頻繁に変更されるため、__INITIAL_STATE__のJSONスキーマをハードコードせず、動的にキーを探索する実装が推奨されます。詳細なプロキシ設定についてはProxyHatドキュメントを参照してください。
Node.jsでの実装:SOCKS5プロキシ経由
次に、Node.jsからSOCKS5プロキシ(ポート1080)を使用してYelpにアクセスする例を示します。SOCKS5はHTTPプロキシよりも低レベルで動作し、TLSハンドシェイクをプロキシに透過させるため、TLSフィンガープリンティングのリスクを低減できます。
const { SocksProxyAgent } = require('socks-proxy-agent');
const fetch = require('node-fetch');
// ProxyHat SOCKS5 with sticky session for pagination continuity
const proxyUrl = 'socks5://user-country-US-session-yelp001:pass@gate.proxyhat.com:1080';
const agent = new SocksProxyAgent(proxyUrl);
const headers = {
'User-Agent': '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',
'Accept-Language': 'en-US,en;q=0.9',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
};
async function scrapeYelpBusiness(slug) {
const url = `https://www.yelp.com/biz/${slug}`;
const res = await fetch(url, { agent, headers, timeout: 30000 });
if (res.status === 403) {
console.error('Blocked — rotate session ID and retry');
return null;
}
const html = await res.text();
const match = html.match(/__INITIAL_STATE__\s*=\s*({.*?});\s*<\/script>/s);
if (!match) {
console.error('__INITIAL_STATE__ not found');
return null;
}
const state = JSON.parse(match[1]);
const reviews = state.review?.reviews || state.review || [];
return reviews.slice(0, 10).map(r => ({
author: r.author || '',
rating: r.rating || '',
date: r.date || '',
text: (r.text || '').substring(0, 200),
}));
}
// Usage
scrapeYelpBusiness('some-restaurant-austin')
.then(reviews => console.log(JSON.stringify(reviews, null, 2)))
.catch(err => console.error('Error:', err.message));
この例では-session-yelp001を指定してスティッキーセッションを確立しています。同一セッションIDを使用し続ける限り同じIPが割り当てられるため、ページネーション時のIP切り替えによるPerimeterXのセッション切断を防げます。ページネーションを完了したら、セッションIDを変更して新しいIPにローテーションします。
curlでの基本確認
スクレイピングを始める前に、curlでプロキシ接続とレスポンスステータスを素早く確認できます:
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/some-restaurant-austin
ステータスコード200が返ればプロキシ経由でアクセス成功、403が返ればPerimeterXにブロックされているため、セッションIDを変更するかレートを下げて再試行してください。
ページネーション、レート制限、スティッキーセッションの設計
ページネーション戦略
Yelpのビジネスページはデフォルトで数件のレビューを表示し、追加レビューは/biz/{slug}?start=20のようなクエリパラメータでページネーションされます。各ページのstart値は20件刻みで増加します。スクレイピング時は、同一セッションID(同一IP)でページネーションを完走し、次のビジネスページに移る前にセッションIDを変更してIPをローテーションします。
レート制限のペーシング
PerimeterXのレート検知を回避するため、以下のペーシング戦略を推奨します:
- 1IPあたりのリクエストを1分間に20リクエスト以下に制限
- リクエスト間に2〜5秒のランダムなスリープを挿入
- ビジネスページごとに新しいセッションIDを発行
- 403レスポンスを受けたら指数バックオフで再試行(5秒→10秒→20秒)
User-Agentローテーション
単一のUser-Agent文字列を継続使用すると、PerimeterXがパターンを学習してブロックする可能性があります。最新のChrome、Firefox、SafariのUser-Agent文字列を10種類以上準備し、リクエストごとにランダムに選択します。ただし、TLSフィンガープリントとUser-Agentの整合性も重要です。ChromeのUser-Agentを送信しながらPythonのデフォルトTLSスタックを使用すると、不整合として検出される場合があります。curl_cffiライブラリを使用すると、ChromeのTLS指紋を模倣しつつリクエストを送信できます。
スティッキーセッションの活用
ProxyHatの-session-{id}フラグは、同一IDで同一IPを維持します。これにより、PerimeterXのセッションベース検知を回避しつつ、ビジネスページ全体のレビューを連続取得できます。セッションIDの命名規則として-session-yelp-{business_id}のような形式を用いると、ビジネスごとに独立したIPを使用できます。
プロキシを活用したスクレイピングのより幅広いユースケースについては、Webスクレイピングのユースケースを参照してください。SERPデータの収集にも同様の手法が適用できます(SERPトラッキングのページも併せてご覧ください)。
倫理的スクレイピングと公式APIの活用
スクレイピングを実装する前に、以下の倫理的・法的要件を確認してください:
robots.txtの尊重
Yelpのrobots.txtを確認し、スクレイピングが許可されたパスのみにアクセスしてください。 robots.txtでDisallow指定されたパスへのアクセスは避けるべきです。
個人情報の取り扱い
レビューには投稿者の氏名、プロフィール写真、投稿日が含まれます。EUではGDPRが、カリフォルニア州ではCCPAが適用されます。これらのデータを保存・処理する場合、データ最小化の原則に従い、分析に必要な最小限のフィールドのみを保持してください。個人を特定できる情報(PII)を含むデータセットを第三者に共有・販売することは法的リスクが高く、推奨されません。
ログイン壁のコンテンツは避ける
Yelpにはログインが必要なページ(友人のレビュー、プライベートメッセージなど)が存在します。これらのコンテンツは公開データではなく、スクレイピングはCFAA違反のリスクがあります。本ガイドで扱うのはログイン不要の公開ビジネスページのみです。
公式APIを優先する
Fusion APIが要件を満たす場合は、常にAPIを優先してください。APIは安定的で法的リスクが低く、メンテナンスコストも最小限です。3件のレビューと基本ビジネス情報で要件を満たせるプロジェクト(例:競合分析の初期調査、店舗一覧の構築)では、APIの利用が最適な選択です。スクレイピングは、APIでは取得できない大量のレビュー本文や、APIに含まれないフィールドが必要な場合に限定すべきです。
重要なポイント
Key Takeaways:
- Yelpの公開ビジネスページからはログイン不要でレビュー本文、評価、カテゴリ、営業時間を取得可能。Fusion APIは最大3件のレビューしか返さない。
- PerimeterX/HUMANは_px3 Cookie、TLSフィンガープリンティング、IPレピュテーションを組み合わせてボットを検出。データセンターIPは5〜10回でブロック。
- ローテーション型レジデンシャルプロキシ(US geo-targeting)がYelpスクレイピングに必須。
-country-US-city-austinで地域別ビジネス結果を取得。- ページネーション時は
-session-{id}で同一IPを維持し、ビジネスページごとにセッションIDを変更してIPをローテーション。- レートは1分間に20リクエスト以下、リクエスト間に2〜5秒のランダムスリープ、User-Agentのローテーションでフィンガープリントリスクを低減。
- 常にrobots.txtを尊重し、個人情報はデータ最小化の原則で扱い、ログイン壁のコンテンツにはアクセスしない。Fusion APIで要件を満たせる場合はAPIを優先。
ProxyHatのレジデンシャルプロキシを活用すれば、Yelpビジネスデータのスクレイピングを本番環境で安定して実行できます。まずはプランを確認し、ドキュメントで接続設定の詳細をご覧ください。






