2026年如何抓取TripAdvisor评论:开发者实战指南

面向旅行情报与评论分析开发者的实战指南,涵盖TripAdvisor公开数据边界、反爬机制、GraphQL端点、Python curl_cffi示例及道德抓取规范。

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

法律与合规提示: 抓取TripAdvisor数据前,请务必阅读并遵守TripAdvisor的服务条款(Terms of Service)。在美国,未经授权的自动化访问可能触及《计算机欺诈与滥用法》(CFAA);在欧盟,处理评论者个人数据须遵守《通用数据保护条例》(GDPR)。本文仅讨论对公开数据的合法访问,不鼓励任何违反平台条款或适用法律的行为。如有疑问,请咨询专业律师。

对于旅行情报和评论分析团队来说,如何抓取TripAdvisor评论是一个反复出现的需求——酒店列表、餐厅评分、用户评论文本,这些数据对竞品分析、情感建模和市场趋势研究都极具价值。但TripAdvisor的反爬体系在2026年已经非常成熟,简单的requests库加User-Agent伪装几乎无法奏效。本文将从公开数据边界讲起,深入到GraphQL端点、TLS指纹、住宅代理策略,并给出可运行的Python代码。

2026年如何抓取TripAdvisor评论:公开数据的边界

TripAdvisor的酒店和餐厅评论页面(/Hotel_Review/Restaurant_Review URL)上展示的内容属于公开可见信息:商家名称、整体评分、评论标题、评论文本、发布日期、评分星级。这些数据无需登录即可在浏览器中查看,因此属于公开数据范畴。但“公开可见”并不等于“可以随意批量抓取”——平台条款和反爬技术仍然构成实际障碍。

关键在于,TripAdvisor对评论采用懒加载机制。页面初始加载时只渲染前5-10条评论,后续评论通过AJAX请求动态加载。这意味着直接请求HTML只能获得部分内容,完整评论数据需要通过其内部API获取。这也是为什么理解其反爬栈和API结构至关重要。

哪些数据是公开的

  • 商家列表页(搜索结果页)的名称、评分、位置、价格区间
  • /Hotel_Review/Restaurant_Review 页面上的评论标题、正文、评分、日期
  • 商家基本信息:地址、电话、营业时间、设施列表
  • 评论者的显示昵称和贡献数(但不应作为个人数据来采集——见道德抓取部分)

哪些数据需要登录或有其他限制

  • 评论者的真实身份信息(永远不应抓取)
  • 需要登录才能看到的私人消息或管理回应中的某些字段
  • 部分深度历史数据可能被分页限制所遮挡

TripAdvisor的反爬体系:Cloudflare + DataDome + TLS指纹

TripAdvisor的反爬不是单一方案,而是多层叠加。理解每一层的机制,才能设计有效的抓取策略。

Cloudflare与DataDome双重防护

TripAdvisor前端由 Cloudflare 提供CDN和基础WAF防护,同时部署了 DataDome 作为专门的机器人检测层。DataDome会分析请求的TLS指纹、HTTP/2设置帧、浏览器特征(canvas、WebGL、字体列表)、IP信誉、请求频率模式等多个维度。数据中心IP在DataDome的信誉库中几乎被标记为高风险,即使设置了完美的浏览器Header也难以通过。

TLS/HTTP2指纹:为什么光改Header没用

许多开发者以为设置 User-AgentAccept-LanguageReferer 等Header就能伪装成浏览器。但现代反爬系统早已深入到TLS握手层。TLS ClientHello中的密码套件顺序、扩展列表、椭圆曲线参数,以及HTTP/2的SETTINGS帧参数(如HEADER_TABLE_SIZE、INITIAL_WINDOW_SIZE),构成了所谓的“JA3”和“JA4”指纹。Python的 requests 库使用的是urllib3/OpenSSL,其TLS指纹与Chrome完全不同,DataDome可以在握手阶段就识别并拦截。

这就是为什么数据中心代理在抓取TripAdvisor时几乎无效——不仅IP信誉差,而且如果你的HTTP客户端TLS指纹不对,即使换1000个数据中心IP也照样被拦。解决方案是使用能够模拟真实浏览器TLS指纹的客户端(如 curl_cffi)配合住宅代理

GraphQL端点:/data/graphql/ids

TripAdvisor的评论数据并非直接嵌入HTML,而是通过一个内部GraphQL端点加载。在浏览器开发者工具的Network面板中,你可以观察到POST请求发往 /data/graphql/ids,请求体包含GraphQL查询字符串和变量,响应为JSON格式的结构化数据。

请求结构

该端点需要以下关键要素:

  • HTTP方法: POST
  • 请求头: X-Requested-By token(通常是一个Base64编码的值,可从页面HTML中的 <script> 标签或meta标签提取)
  • Content-Type: application/json
  • 请求体: GraphQL查询 + variables(包含locationId、offset、limit等参数)

响应中的每个评论节点包含 titleratingtextpublishedDate 等字段,但文本可能被截断(通常在约200-300字符处截断,完整文本可能需要额外的查询或从HTML页面提取)。

为什么需要住宅代理配合地理定位

TripAdvisor的搜索结果和列表排序具有地理敏感性——同一关键词在不同国家/城市搜索,结果排序和可见商家不同。如果你需要抓取某个城市本地化的餐厅列表,你的请求IP应当来自该地区。数据中心IP无法提供地理真实性,而住宅代理可以精确到城市级别。

使用ProxyHat住宅代理时,可以通过用户名参数指定国家和城市:

http://user-country-GB-city-london:pass@gate.proxyhat.com:8080

这样请求会从英国伦敦的住宅IP发出,TripAdvisor会返回该地区本地化的列表数据。更多可用位置可查看 ProxyHat代理位置列表

Python实战:curl_cffi + ProxyHat住宅代理抓取评论

以下示例使用 curl_cffi 库模拟Chrome的TLS指纹,通过ProxyHat住宅代理发送GraphQL请求,抓取一个酒店的评论数据。

安装依赖

pip install curl_cffi

完整代码示例

from curl_cffi import requests
import json

# ProxyHat住宅代理 - 英国伦敦
proxy_url = "http://user-country-GB-city-london-session-hotel001:pass@gate.proxyhat.com:8080"

# TripAdvisor GraphQL端点
graphql_url = "https://www.tripadvisor.com/data/graphql/ids"

# 从页面HTML中提取的X-Requested-By token
x_requested_by = "eyJyIjoidHJpcGFkdmlzZXIifQ=="  # 示例值,需替换为实际token

# GraphQL查询 - 获取酒店评论
query = """
query ReviewList($locationId: Int!, $offset: Int!, $limit: Int!) {
  location(id: $locationId) {
    reviewList(offset: $offset, limit: $limit) {
      reviews {
        title
        rating
        text
        publishedDate
      }
    }
  }
}
"""

variables = {
    "locationId": 1234567,  # 替换为实际locationId
    "offset": 0,
    "limit": 10
}

headers = {
    "Content-Type": "application/json",
    "X-Requested-By": x_requested_by,
    "Referer": "https://www.tripadvisor.com/",
    "Accept": "application/json",
}

response = requests.post(
    graphql_url,
    json={"query": query, "variables": variables},
    headers=headers,
    proxies={"http": proxy_url, "https": proxy_url},
    impersonate="chrome",  # 关键:模拟Chrome TLS指纹
    timeout=30,
)

if response.status_code == 200:
    data = response.json()
    reviews = data.get("data", {}).get("location", {}).get("reviewList", {}).get("reviews", [])
    for review in reviews:
        print(f"标题: {review.get('title')}")
        print(f"评分: {review.get('rating')}/5")
        print(f"日期: {review.get('publishedDate')}")
        print(f"正文: {review.get('text', '')[:100]}...")
        print("---")
else:
    print(f"请求失败: {response.status_code}")
    print(response.text[:500])

注意:x_requested_by token 和 locationId 需要从目标页面中提取。你可以先用一个简单的GET请求获取页面HTML,然后用正则或BeautifulSoup解析出这些值。

Node.js示例

const { fetch } = require('undici');

const proxyUrl = 'http://user-country-GB-city-london-session-hotel001:pass@gate.proxyhat.com:8080';
const graphqlUrl = 'https://www.tripadvisor.com/data/graphql/ids';

async function scrapeReviews() {
  const query = `
    query ReviewList($locationId: Int!, $offset: Int!, $limit: Int!) {
      location(id: $locationId) {
        reviewList(offset: $offset, limit: $limit) {
          reviews { title rating text publishedDate }
        }
      }
    }`;

  const resp = await fetch(graphqlUrl, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-Requested-By': 'YOUR_TOKEN',
      'Referer': 'https://www.tripadvisor.com/',
    },
    body: JSON.stringify({
      query,
      variables: { locationId: 1234567, offset: 0, limit: 10 }
    }),
    dispatcher: new (require('undici').ProxyAgent)(proxyUrl),
  });

  const data = await resp.json();
  const reviews = data?.data?.location?.reviewList?.reviews || [];
  reviews.forEach(r => console.log(r.title, r.rating, r.publishedDate));
}

scrapeReviews().catch(console.error);

速率限制与会话管理

即使有了正确的TLS指纹和住宅代理,仍然需要遵守合理的请求频率。以下是经过实践验证的经验值:

参数建议值说明
每IP请求频率50-100个列表/小时超过此频率触发DataDome临时封禁
封禁冷却时间约5-10分钟临时封禁后等待重试
并发连接数3-5个/IP避免同时大量并发引发风控
分页offset步长10-15条/页与页面懒加载行为一致
请求间隔3-8秒随机模拟人类浏览节奏

粘性会话与IP轮换

抓取单个商家的所有评论时,使用粘性会话(sticky session)保持同一IP,避免在分页过程中频繁切换IP引起怀疑。ProxyHat支持通过用户名中的 -session- 参数实现会话保持:

# 同一会话保持同一IP
http://user-country-GB-city-london-session-hotel001:pass@gate.proxyhat.com:8080

# 切换到新会话获得新IP
http://user-country-GB-city-london-session-hotel002:pass@gate.proxyhat.com:8080

建议每个商家使用一个会话ID,抓取完所有分页后切换到新会话抓取下一个商家。

指数退避策略

import time
import random

def fetch_with_backoff(url, max_retries=5):
    for attempt in range(max_retries):
        try:
            response = requests.post(url, ...)
            if response.status_code == 200:
                return response.json()
            elif response.status_code == 429:
                wait = (2 ** attempt) + random.uniform(1, 5)
                print(f"被限流,等待 {wait:.1f} 秒...")
                time.sleep(wait)
            elif response.status_code == 403:
                print("IP被拦截,切换会话...")
                return None
        except Exception as e:
            print(f"请求异常: {e}")
            time.sleep(5)
    return None

道德抓取与何时使用官方API

抓取评论数据时,必须考虑伦理和法律边界。以下是核心原则:

只采集聚合数据,不采集个人数据

评论者的显示昵称和贡献数虽然在页面上可见,但在GDPR框架下,这些信息如果与特定个人关联,可能构成个人数据。建议:

  • 只提取评论文本、评分、日期——这些是分析所需的核心数据
  • 不存储评论者用户名、头像URL或个人主页链接
  • 如需引用评论,进行匿名化处理(如“一位2025年6月入住的旅客”)
  • 遵守 GDPR 关于数据最小化原则——只采集业务必需的数据

尊重robots.txt和平台条款

在开始抓取前,检查 https://www.tripadvisor.com/robots.txt 了解平台对爬虫的声明。虽然robots.txt不具有法律强制力,但它是平台意愿的明确表达。如果你的项目需要大规模、持续性的数据采集,强烈建议优先考虑TripAdvisor的官方Content API。

何时使用官方API

TripAdvisor提供 Content API,通过正式授权获取评论和商家数据。以下情况应优先使用官方API:

  • 需要大规模、系统性数据采集(如构建商业产品)
  • 需要实时或近实时数据更新
  • 项目对数据合规性有严格要求(如上市公司产品、受监管行业)
  • 预算允许支付API授权费用

官方API的优势在于合法合规、数据结构稳定、有技术支持。劣势在于可能有调用限制和费用。对于一次性的研究项目或小规模分析,代理抓取可能更灵活;但对于生产级系统,官方API是更可持续的选择。

关键要点

抓取TripAdvisor评论的核心要点:

  • TripAdvisor使用Cloudflare + DataDome双重防护,数据中心IP几乎无法通过
  • 评论数据通过内部GraphQL端点 /data/graphql/ids 加载,需POST请求和X-Requested-By token
  • 必须使用能模拟Chrome TLS指纹的客户端(如curl_cffi)配合住宅代理
  • 地理定位影响搜索结果,使用 -country-XX-city-yyy 参数获取本地化数据
  • 合理速率:50-100个列表/小时/IP,使用粘性会话保持分页连续性
  • 只采集聚合评论数据,不采集个人数据,遵守GDPR和平台条款
  • 生产级系统优先考虑TripAdvisor官方Content API

准备好开始抓取?查看 ProxyHat定价方案,选择适合你项目规模的住宅代理套餐。更多关于网页抓取的最佳实践,可参考 网页抓取用例指南SERP追踪用例。完整的API连接文档请访问 ProxyHat文档站

准备开始了吗?

通过AI过滤访问148多个国家的5000多万个住宅IP。

查看价格住宅代理
← 返回博客