2026年版 TripAdvisorのレビューをスクレイピングする方法:プロキシ活用ガイド

TripAdvisorのホテル・レストランレビューを合法的に収集するための開発者向けガイド。Cloudflare/DataDome対策、GraphQLエンドポイントの活用、curl_cffiによるTLS偽装、ProxyHatローテーションプロキシの実装例を解説。

How to Scrape TripAdvisor Reviews in 2026: A Developer's Guide

2026年にTripAdvisorのレビューをスクレイピングする方法:概要と法的注意点

本記事は公開データへの合法的なアクセスのみを対象としています。 Tripadvisor LLCの利用規約(Terms of Service)を遵守し、米国ではComputer Fraud and Abuse Act(CFAA)、EUではGDPRに従う必要があります。レビュー投稿者の個人データ(氏名、アバター、一意のユーザーID等)を収集・保存することは、GDPRの「個人データ」に該当する可能性があります。本ガイドは公開レビューテキストと集計データの取得に限定し、ログイン壁の背後にあるデータや個別ユーザー追跡を推奨しません。詳細はTripAdvisor利用規約およびGDPR公式サイトを参照してください。

2026年において、TripAdvisorのレビューをスクレイピングする方法は、旅行インテリジェンスやレビュー分析の開発者にとって重要なテーマです。ホテルやレストランの口コミデータは、価格戦略、競合分析、感情分析の基盤となります。しかし、TripAdvisorは強力なアンチボットシステムを備えており、単純なHTTPリクエストではブロックされます。本記事では、curl_cffiとローテーション型レジデンシャルプロキシを用いた実践的なアプローチを解説します。

TripAdvisorの公開データとは何か

TripAdvisorで公開されているデータには、以下が含まれます:

  • リスティングページ:ホテル・レストランの基本情報(名称、住所、カテゴリ、全体評価)
  • レビューテキスト/Hotel_Review および /Restaurant_Review URL上の個別レビュー(タイトル、評価、本文、投稿日)
  • 評価分布:5段階評価の内訳(5星〜1星の各割合)
  • 写真のメタデータ:アップロード日、キャプション(画像ファイルそのものではなくメタデータのみ)

レビューの遅延読み込み(Lazy Loading)

TripAdvisorはレビューをページ読み込み時に全件表示しません。初期ページには最初の10件程度のレビューが含まれ、以降はJavaScriptによる動的読み込みで追加されます。具体的には、ページ下部の「More」ボタンクリック時に、バックグラウンドでGraphQL APIが呼び出され、次のレビューページがJSON形式で返されます。このため、BeautifulSoup等のHTMLパーサーだけでは全レビューを取得できず、GraphQLエンドポイントへの直接リクエストが効率的なアプローチとなります。

TripAdvisorのアンチボットスタック:なぜデータセンタープロキシでは不十分か

TripAdvisorは複数層の防御システムを組み合わせており、以下の技術が連携してボットトラフィックを検出します。

Cloudflare + DataDomeの二重防御

TripAdvisorは、CloudflareのWAF/DDoS防御に加え、DataDomeのボット検知エンジンを使用しています。DataDomeは、ブラウザのJavaScript実行環境、マウス動作パターン、TLSフィンガープリントを組み合わせて、人間とボットを判別します。単純なrequestsライブラリのリクエストは、TLSハンドシェイクの段階で「Python-requests」のシグネチャとして検出され、即座に403またはチャレンジページが返されます。

TLS / HTTP2フィンガープリンティング

TLSフィンガープリンティング(JA3/JA4)は、ClientHelloパケットの暗号スイート順序、拡張機能の構成をハッシュ化し、クライアントを識別します。Pythonの標準requestsライブラリは、ChromeやFirefoxとは異なるJA3ハッシュを生成するため、ヘッダーを偽装してもTLSレベルで検出されます。HTTP/2のSETTINGSフレーム順序も同様にフィンガープリントの対象となります。

データセンタープロキシがブロックされる理由

データセンタープロキシ(AWS、DigitalOcean、OVH等のIPレンジ)は、ASNデータベースで「hosting provider」として分類されています。DataDomeはこの情報を参照し、データセンターIPからのリクエストをデフォルトで疑わしいと判定します。User-AgentやAccept-Languageヘッダーを本物のブラウザと完全に一致させても、IPのASN情報だけでブロックされるケースが多発します。これが、レジデンシャルプロキシが必須となる根本的な理由です。

プロキシタイプTripAdvisor成功率(概算)適用場面
データセンタープロキシ5%〜15%テスト用途のみ(実用不可)
モバイルプロキシ70%〜85%高頻度収集、キャリアIP
レジデンシャルプロキシ85%〜95%本番運用推奨

GraphQLエンドポイント /data/graphql/ids の活用

TripAdvisorのレビューページは、内部的に /data/graphql/ids というGraphQLエンドポイントを呼び出しています。このエンドポイントにPOSTリクエストを送ることで、HTMLをパースするよりも構造化されたデータを直接取得できます。

リクエスト構成

  • メソッド:POST
  • URLhttps://www.tripadvisor.com/data/graphql/ids
  • ヘッダーContent-Type: application/jsonX-Requested-By: XXXXX(ページのHTML内に埋め込まれたトークン)
  • ボディ:GraphQLクエリ(reviewList、location情報等のクエリ)

X-Requested-By トークンは、リスティングページのHTML内に <meta name="X-Requested-By" content="..."> またはインラインスクリプトとして含まれています。まずリスティングページを取得し、このトークンを抽出してからGraphQLリクエストを行う2段階のアプローチが必要です。

ジオターゲティングの必要性

TripAdvisorはユーザーのIP地理情報に基づいて、表示するリスティングやレビューのローカライズを行います。例えば、ロンドンのホテルレビューを正確に取得するには、英国のIPアドレスからアクセスする必要があります。ProxyHatのジオターゲティング機能(-country-GB-city-london)を使えば、ローカライズされたコンテンツにアクセスできます。

Python実装例:curl_cffi + ProxyHatでGraphQLリクエスト

以下の例では、curl_cffiライブラリを使用してChromeのTLSフィンガープリントを再現し、ProxyHatのレジデンシャルプロキシ経由でTripAdvisorのGraphQLエンドポイントにリクエストを送ります。

import re
import json
from curl_cffi import requests

# ProxyHat レジデンシャルプロキシ(ロンドンIP)
PROXY_URL = "http://user-country-GB-city-london-session-sess01:pass@gate.proxyhat.com:8080"

# ステップ1: リスティングページを取得し、X-Requested-Byトークンを抽出
listing_url = "https://www.tripadvisor.com/Hotel_Review-g186338-d123456-Reviews-Example_Hotel-London_England.html"

resp = requests.get(
    listing_url,
    impersonate="chrome",
    proxies={"http": PROXY_URL, "https": PROXY_URL},
    timeout=30,
)

# トークンをHTMLから抽出
token_match = re.search(r'X-Requested-By["\']?\s*(?:content|value)?["\']?\s*[=:]["\']?([\w-]+)', resp.text)
if not token_match:
    raise Exception("X-Requested-By token not found")
requested_by = token_match.group(1)

# ステップ2: GraphQLエンドポイントへPOST
graphql_url = "https://www.tripadvisor.com/data/graphql/ids"

graphql_query = {
    "query": """
    query ReviewList($locationId: Int!, $offset: Int, $limit: Int) {
      reviewList(locationId: $locationId, offset: $offset, limit: $limit) {
        reviews {
          title
          rating
          text
          publishedDate
        }
      }
    }""",
    "variables": {
        "locationId": 123456,
        "offset": 0,
        "limit": 10
    }
}

headers = {
    "Content-Type": "application/json",
    "X-Requested-By": requested_by,
    "Referer": listing_url,
    "Accept": "application/json",
}

resp2 = requests.post(
    graphql_url,
    json=graphql_query,
    impersonate="chrome",
    headers=headers,
    proxies={"http": PROXY_URL, "https": PROXY_URL},
    timeout=30,
)

data = resp2.json()
for review in data.get("data", {}).get("reviewList", {}).get("reviews", []):
    print(f"タイトル: {review.get('title')}")
    print(f"評価: {review.get('rating')}/5")
    text = review.get('text', '')
    print(f"本文(先頭100文字): {text[:100]}...")
    print(f"投稿日: {review.get('publishedDate')}")
    print("---")

このコードは、まずリスティングページを取得して X-Requested-By トークンを抽出し、そのトークンを使ってGraphQLエンドポイントにレビュー一覧クエリをPOSTします。impersonate="chrome" により、curl_cffiがChrome 120のTLSハンドシェイクを再現し、JA3/JA4フィンガープリントが本物のブラウザと一致します。

Node.jsでの実装例

const { curl } = require('curl-impersonate');

const PROXY_URL = 'http://user-country-GB-city-london-session-sess01:pass@gate.proxyhat.com:8080';

async function scrapeReviews() {
  // ステップ1: リスティングページ取得
  const listingResp = await curl('https://www.tripadvisor.com/Hotel_Review-g186338-d123456-Reviews-Example_Hotel-London_England.html', {
    impersonate: 'chrome120',
    proxy: PROXY_URL,
    timeout: 30000,
  });

  const tokenMatch = listingResp.body.match(/X-Requested-By["']?\s*(?:content|value)?["']?\s*[=:"]["']?([\w-]+)/);
  const requestedBy = tokenMatch ? tokenMatch[1] : null;

  // ステップ2: GraphQL POST
  const graphqlResp = await curl('https://www.tripadvisor.com/data/graphql/ids', {
    method: 'POST',
    impersonate: 'chrome120',
    proxy: PROXY_URL,
    headers: {
      'Content-Type': 'application/json',
      'X-Requested-By': requestedBy,
    },
    body: JSON.stringify({
      query: `query ReviewList($locationId: Int!, $offset: Int, $limit: Int) {
        reviewList(locationId: $locationId, offset: $offset, limit: $limit) {
          reviews { title rating text publishedDate }
        }
      }`,
      variables: { locationId: 123456, offset: 0, limit: 10 }
    }),
    timeout: 30000,
  });

  const data = JSON.parse(graphqlResp.body);
  data.data.reviewList.reviews.forEach(r => {
    console.log(`${r.title} | ${r.rating}/5 | ${r.publishedDate}`);
    console.log(r.text?.substring(0, 100) + '...');
  });
}

scrapeReviews().catch(console.error);

レート制限、スティッキーセッション、バックオフ戦略

TripAdvisorのスクレイピングでは、適切なレート制限管理が成功率に直結します。実測に基づく目安は以下の通りです。

現実的なレート制限

  • 1IPあたり:約50〜100リスティング/時間(GraphQLリクエスト含む)
  • ブロック後のクールダウン:約5〜10分間のIPローテーション待機
  • ページネーション間隔:各レビューページ(offset=0,10,20...)の間に3〜5秒の待機
  • 同時接続数:1IPあたり最大2〜3並列(それ以上はTLSフィンガープリントの異常として検出される可能性)

スティッキーセッションの活用

ProxyHatの -session- パラメータを使うと、同一セッションIDの間は同じ出口IPを維持できます。これは、トークン抽出→GraphQLリクエストの2段階処理でIPの一貫性を保つために重要です。異なるIPでトークンを取得し、別IPでGraphQLリクエストを送ると、DataDomeがセッションの不整合を検出する可能性があります。

# セッション固定の例(同一IPを維持)
PROXY_URL = "http://user-country-GB-city-london-session-hotel123:pass@gate.proxyhat.com:8080"

# セッションIDを変更すれば新しいIPにローテーション
PROXY_URL_2 = "http://user-country-GB-city-london-session-hotel456:pass@gate.proxyhat.com:8080"

指数バックオフの実装

import time
import random

def fetch_with_backoff(url, proxy_url, max_retries=5):
    for attempt in range(max_retries):
        try:
            resp = requests.get(url, impersonate="chrome", proxies={"http": proxy_url, "https": proxy_url}, timeout=30)
            if resp.status_code == 200:
                return resp
            elif resp.status_code == 403:
                # ブロック検出:新しいセッションIDでIPローテーション
                new_session = f"sess{random.randint(1000, 9999)}"
                proxy_url = proxy_url.replace("session-sess01", f"session-{new_session}")
                wait = (2 ** attempt) + random.uniform(1, 5)
                print(f"403 detected, waiting {wait:.1f}s, rotating session...")
                time.sleep(wait)
            else:
                time.sleep(5)
        except Exception as e:
            wait = (2 ** attempt) + random.uniform(0, 3)
            print(f"Error: {e}, retrying in {wait:.1f}s")
            time.sleep(wait)
    raise Exception(f"Max retries exceeded for {url}")

ページネーション戦略

GraphQLの offset パラメータを10ずつ増やしてページネーションを行います。1リスティングあたりのレビュー数が多い場合(500件以上)、同一IPで全ページを取得するとレート制限に抵触します。対策として、50件(5ページ)ごとにセッションIDを変更しIPをローテーションします。

よくある失敗とエッジケース

1. トークン有効期限の切れ

X-Requested-By トークンには有効期限があり、通常15〜30分で失効します。長時間実行するバッチ処理では、定期的にリスティングページを再取得してトークンを更新する必要があります。

2. 地域別リダイレクト

TripAdvisorはIPの地理情報に基づいて、tripadvisor.com から tripadvisor.co.uktripadvisor.fr 等の地域ドメインへリダイレクトします。これによりGraphQLエンドポイントのURLも変わる場合があります。ジオターゲティングでアクセス元国を固定すれば、リダイレクト先を予測可能にできます。

3. レビューの言語フィルタ

デフォルトでは、アクセス元IPの言語に合わせてレビューがフィルタリングされます。全言語のレビューを取得するには、GraphQLクエリの filterLang パラメータを ALL に設定します。

4. 構造変更によるクエリ破損

GraphQLのスキーマは予告なく変更されることがあります。本番運用では、レスポンスのキー存在チェックと、スキーマ変更を検知するアラート機構を実装すべきです。

ProxyHatのセットアップと内部リソース

ProxyHatのレジデンシャルプロキシは、TripAdvisorスクレイピングに必要なIP多様性とジオターゲティングを提供します。プロキシプランと料金では、トラフィック量に応じたプランを確認できます。TripAdvisorのようなアンチボット防御が強力なサイトでは、利用可能なロケーションから対象国のIPを選択し、-country- および -city- パラメータで固定します。

より一般的なウェブスクレイピングのベストプラクティスについては、ウェブスクレイピングのユースケースを参照してください。また、SERPデータ収集と組み合わせたレビュー分析には、SERPトラッキングのページも参考になります。ProxyHatの接続パラメータの詳細は、公式ドキュメントを参照してください。

倫理的スクレイピングと公式APIの使い分け

TripAdvisorレビューのスクレイピングは、以下の倫理的ガイドラインに従うべきです。

集計データのみを扱う

個別レビューのテキストをそのまま保存・再公開するのではなく、評価分布、平均スコア、レビュー数の推移等の集計データとして利用することを推奨します。これにより、GDPRの「個人データ」該当リスクを大幅に軽減できます。レビュー本文には投稿者の個人的な体験が含まれており、単独では個人を特定できなくても、複数のデータポイントと組み合わせることで再識別化の可能性があります。

EUレビュアーに関するGDPR注意点

  • レビュー投稿者のユーザー名、アバター画像URL、一意のユーザーIDは個人データに該当する可能性がある
  • EU対象者データを処理する場合、データ処理の根拠(合法的利益等)を文書化する必要がある
  • 削除要請があった場合、該当データを速やかに削除する仕組みが必要
  • データ保持期間を定義し、不要になったデータを定期削除する

公式Content APIを使うべきケース

TripAdvisorは商用向けにContent APIを提供しています。以下のケースでは公式APIの利用を推奨します:

  • 商用サービスにレビューデータを組み込む場合(利用許諾が明確)
  • SLAとデータ品質保証が必要なエンタープライズ用途
  • リアルタイムデータが必要な場合(APIは最新データを保証)
  • 法務チームが利用規約違反リスクを懸念する場合

一方、学術研究、競合分析の内部利用、プロトタイピング等では、公開データへのスクレイピングが現実的な選択肢となります。ただし、常に利用規約を確認し、取得したデータを再配布しないことが前提です。

Key Takeaways

  • TripAdvisorはCloudflare + DataDomeの二重防御とTLS/HTTP2フィンガープリンティングにより、データセンタープロキシをほぼ完全にブロックする
  • レジデンシャルプロキシ + curl_cffi(Chrome TLS偽装)の組み合わせが、2026年の標準アプローチ
  • 内部的なGraphQLエンドポイント /data/graphql/ids を利用すれば、HTMLパースより効率的に構造化データを取得できる
  • スティッキーセッション(-session-)でトークン取得からGraphQLリクエストまで同一IPを維持する
  • レート制限は約50〜100リスティング/時間/IP、ブロック時は5〜10分のクールダウン
  • 集計データのみを扱い、個人のレビュー投稿者データを収集しない倫理的ガイドラインを遵守する

FAQ

Q: TripAdvisorのレビューをスクレイピングする方法とは何ですか?

A: 公開されているホテル・レストランのレビューページから、評価テキスト、星評価、投稿日等のデータを収集することです。2026年では、HTMLパースではなく内部的なGraphQLエンドポイント(/data/graphql/ids)への直接リクエストが主流のアプローチです。ただし、Cloudflare + DataDomeのアンチボット防御を回避するため、Chrome TLS偽装(curl_cffi)とレジデンシャルプロキシの組み合わせが必要です。

Q: なぜプロキシユーザーにとってTripAdvisorスクレイピングが重要なのですか?

A: TripAdvisorは世界最大級の旅行レビュープラットフォームであり、ホテル・レストランの評価データは価格分析、競合調査、感情分析の貴重なソースです。しかし、強力なアンチボットシステムにより、プロキシの選択とTLS偽装技術が成功の鍵となります。プロキシユーザーにとって、TripAdvisorはプロキシ品質の実力を試す代表的なターゲットサイトでもあります。

Q: TripAdvisorスクレイピングに最適なプロキシタイプは何ですか?

A: レジデンシャルプロキシが最適です。データセンタープロキシはASNベースでブロックされ、成功率が5%〜15%に留まります。レジデンシャルプロキシはISP発行のIPを使用するため、DataDomeのASNチェックを通過します。モバイルプロキシも有効ですが、コスト効率を考えるとレジデンシャルが推奨されます。ジオターゲティング機能(-country-GB-city-london等)でアクセス元国を固定することも重要です。

Q: TripAdvisorスクレイピングでブロックを回避するにはどうすればよいですか?

A: 4つの要素を組み合わせます:(1) curl_cffiによるChrome TLS/HTTP2フィンガープリントの偽装、(2) レジデンシャルプロキシによるASN検知の回避、(3) スティッキーセッションで同一IPを維持しトークンとリクエストのIP一貫性を保つ、(4) 1IPあたり50〜100リスティング/時間に制限し、ブロック時は5〜10分のクールダウン後にセッションIDを変更してIPローテーションを行う。

始める準備はできましたか?

AIフィルタリングで148か国以上、5,000万以上のレジデンシャルIPにアクセス。

料金を見るレジデンシャルプロキシ
← ブログに戻る