法律与合规提示: 抓取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-Agent、Accept-Language、Referer 等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-Bytoken(通常是一个Base64编码的值,可从页面HTML中的<script>标签或meta标签提取) - Content-Type:
application/json - 请求体: GraphQL查询 + variables(包含locationId、offset、limit等参数)
响应中的每个评论节点包含 title、rating、text、publishedDate 等字段,但文本可能被截断(通常在约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文档站。






