TripAdvisor坐拥网络上最大的用户生成旅游数据池之一:数亿条针对酒店、餐厅和景点的评论、评分和列表信息。这使它成为市场研究、竞品对标和声誉追踪的诱人数据源。问题在于,TripAdvisor用JavaScript渲染它的大部分内容,并对自动化流量严防死守,所以一个普通HTTP请求拿回的是一个近乎空白的页面,而且往往还会被封锁。
本指南向你展示如何用Python以可靠的方式抓取TripAdvisor:把你的请求路由通过Crawlbase Smart AI Proxy,让它替你渲染页面并轮换IP,再用BeautifulSoup解析返回的HTML,从公开的搜索列表中提取名称、评分、评论数和位置。这里的一切都可运行,整个演练只限定在公开数据上。
抓取TripAdvisor合法吗
抓取一个大型商业平台处于法律的灰色地带,是否被允许取决于TripAdvisor的使用条款、你所在的司法辖区,以及你用这些数据做什么。TripAdvisor的条款限制自动化访问,所以不论你的工具多么谨慎,抓取都可能与那些条款相抵触。这里的代码没有一行能改变这一点;它只是把技术部分跑通。
有几条值得坚守的底线。只采集公开数据:任何人无需账户就能看到的列表名称、评分、评论数和位置。尊重TripAdvisor的robots.txt及其声明的速率预期,把你的请求量保持得足够低,以免给任何人的服务器带来压力。评论是关联到真实个人的用户生成内容,所以不要把它们整体转载,也不要抓取任何能识别个人身份的内容。如果你打算把数据用于商业用途,去取得许可或正式的数据协议,而不要假定沉默就是同意。
本指南刻意限定在公开列表数据上,因为那是让这项工作站得住脚的那条线。它不涉及任何登录之后的内容、账户或个人资料数据、私信,或任何绕过身份验证的尝试。如果你的项目所需超出公开列表,正确的做法是与TripAdvisor签订一个正式的API或数据协议,而不是一个更聪明的抓取器。
为什么要抓取TripAdvisor
公众的旅游情绪时刻在变,而单次页面浏览只能告诉你一个列表此刻的样子。抓取TripAdvisor的公开列表,让你能长期追踪评分、评论数和排名,而那正是大多数研究任务真正依赖的东西。几个具体用途:
- 市场研究。通过观察某个品类下的评论量和评分,发现热门目的地和正在变化的客户偏好。
- 竞争分析。在同一市场上,以评分和评论数为基准,把你的酒店、餐厅或景点与竞争对手对标。
- 声誉监控。长期追踪你自己列表的评分走势,以便在下滑早期就做出反应。
- 趋势发现。跨多个列表汇总情绪,浮现出单个页面永远无法揭示的新兴模式。
对工程师而言,价值在于结构化数据:把一个渲染后的搜索页变成你可以查询、绘图或喂给模型的干净行。
为什么普通获取在这里会失败
如果你用一个裸HTTP客户端请求TripAdvisor的URL,你通常会得到一个状态为200、却几乎没有你想要的数据的响应。有两件事在和你作对。首先,TripAdvisor用JavaScript在浏览器中渲染它的列表,所以初始HTML只是一个外壳,要在页面脚本运行之后才填充。其次,TripAdvisor会很快标记自动化流量:数据中心IP以及看起来不像真实浏览器的请求模式,会在到达渲染后的内容之前就被质询或封锁。
因此一个能用的TripAdvisor抓取器需要在一次请求中具备两样东西:一个真正渲染页面的浏览器,以及一个被平台读作真实访客的IP。你可以自己用无头浏览器加上一组轮换的住宅代理来拼凑这套方案,但把它们缝合起来并保持健康才是大部分工作。Crawlbase Smart AI Proxy把两者折叠进一个端点:你把你普通的HTTP客户端指向它,传入几个参数,它就会在一个受信任的、轮换的IP背后渲染页面,并返回处理好的HTML。
Smart AI Proxy是一个由Crawlbase的Crawling API支撑的即插即用代理端点。你照常继续使用requests(或任何客户端),只是把流量路由经过它。在底层,它轮换一个庞大的数据中心和住宅IP池,并能渲染JavaScript,所以你无需围绕一个新的SDK重写代码,就能获得Crawling API的能力。
搭建环境
你需要Python 3.8或更高版本。确认你的版本,创建一个虚拟环境让项目依赖保持隔离,然后安装本指南所用的三个库。
python --version python -m venv tripadvisor_env source tripadvisor_env/bin/activate pip install requests beautifulsoup4 pandas
在Windows上,用tripadvisor_env\Scripts\activate来激活环境,替代那行source。这三个依赖完成主要工作:requests发起HTTP调用,beautifulsoup4解析返回的HTML,而pandas组织你的结果并把它们写入文件。
你还需要一个Crawlbase账户和一个访问令牌,注册之后从仪表盘获取它。把它放进代码中任何出现YOUR_ACCESS_TOKEN的地方。
通过Smart AI Proxy发送请求
Smart AI Proxy只是一个HTTP代理端点,所以你配置它的方式,和你在requests中配置任何代理的方式相同。把你的访问令牌放进代理URL,让http和https两个条目都指向它,并发起一个普通的GET请求。因为代理替你终结TLS,所以传入verify=False来跳过本地证书检查。
import requests proxy_url = "http://YOUR_ACCESS_TOKEN:@smartproxy.crawlbase.com:8012" target_url = "https://www.tripadvisor.com/Search?q=london" proxies = {"http": proxy_url, "https": proxy_url} response = requests.get(target_url, proxies=proxies, verify=False) print("Status:", response.status_code) print(response.content[:500])
那一次调用就是整个集成。你的代码保持纯粹的requests;代理处理IP轮换以及那种让你在第一次访问就不被封锁的受信任访客信誉。
传递Crawling API参数
Smart AI Proxy通过一个请求头CrawlbaseAPI-Parameters,暴露出和Crawling API相同的那些控制项。你以查询字符串风格的值传入选项。例如,要把请求地理定位到美国,设置country参数:
import requests proxy_url = "http://YOUR_ACCESS_TOKEN:@smartproxy.crawlbase.com:8012" target_url = "https://www.tripadvisor.com/Search?q=london" headers = {"CrawlbaseAPI-Parameters": "country=US"} proxies = {"http": proxy_url, "https": proxy_url} response = requests.get(target_url, headers=headers, proxies=proxies, verify=False) print(response.content[:500])
你可以在同一个请求头的值里用&把多个选项串起来,接下来你正是这样打开JavaScript渲染的。
渲染JavaScript密集的页面
TripAdvisor在客户端加载它的列表,所以不渲染的话你又会拿到那个空壳。用javascript=true打开一个无头浏览器,并加上page_wait,在加载后保持固定的毫秒数,让晚渲染的元素在页面被捕获之前出现。五秒是一个合理的起点;如果结果回来得很单薄,就把它调高。
import requests proxy_url = "http://YOUR_ACCESS_TOKEN:@smartproxy.crawlbase.com:8012" target_url = "https://www.tripadvisor.com/Search?q=london" headers = {"CrawlbaseAPI-Parameters": "javascript=true&page_wait=5000"} proxies = {"http": proxy_url, "https": proxy_url} response = requests.get(target_url, headers=headers, proxies=proxies, verify=False) html_content = response.content.decode("utf-8") print("Fetched", len(html_content), "characters of rendered HTML")
打开JavaScript渲染后,响应体现在包含的是填充好的列表,而不是一个空白的骨架。那就是你将交给BeautifulSoup的HTML。
TripAdvisor需要一个在受信任IP背后渲染的页面,而Smart AI Proxy以一个即插即用的端点把两者都给你。照常继续使用requests,传入javascript=true,它就会在真实浏览器中渲染页面,在服务端轮换住宅和数据中心IP,并把处理好的HTML交回给你,这样你就无需自己运行一支无头集群和一个代理池。先在免费层把它指向一个公开搜索试试。
用BeautifulSoup解析搜索列表
手里有了渲染后的HTML,把它加载进BeautifulSoup并遍历结果卡片。第一项工作是找到承载搜索结果的容器。在浏览器中打开页面,右键点击一个结果,选择"检查"以读取实时标记。
在TripAdvisor的搜索视图中,每个列表都位于一个带有result类的div里,而这些都活在一个以data-widget-type="LOCATIONS"为键的结果列表内。选中它并循环卡片:
from bs4 import BeautifulSoup soup = BeautifulSoup(html_content, "html.parser") search_results = soup.select( 'div.search-results-list[data-widget-type="LOCATIONS"] div.result' ) for result in search_results: # extract fields from each result here pass
现在把每个字段映射到它的选择器。检查一张卡片,能看出名称、评分、评论数和位置都在哪里:
-
名称位于一个
div.result-title内的<span>里。 -
评分在一个
span.ui_bubble_rating中,其值存放在它的alt属性里。 -
评论数是一个
a.review_count链接的文本。 -
位置是一个
div.address-text的文本。
name_el = result.select_one("div.result-title span") name = name_el.text.strip() if name_el else None rating_el = result.select_one("span.ui_bubble_rating") rating = rating_el["alt"] if rating_el else None reviews_el = result.select_one("a.review_count") reviews = reviews_el.text.strip() if reviews_el else None location_el = result.select_one("div.address-text") location = location_el.text.strip() if location_el else None
TripAdvisor会不经通知地更新它的标记,所以像result-title和ui_bubble_rating这样的类名可能会变。把上面的选择器当作起始模板,而非契约。当某个字段返回None时,在浏览器开发者工具中重新检查一个实时搜索页并更新选择器。对任何生产级抓取器来说,这都是正常的维护,并不意味着出了什么问题。
完整的抓取器
下面是接进一个可运行文件的全部内容。它通过Smart AI Proxy获取渲染后的搜索页,解析每个结果,收集这些行,并以JSON打印出来。
import json import requests from bs4 import BeautifulSoup proxy_url = "http://YOUR_ACCESS_TOKEN:@smartproxy.crawlbase.com:8012" target_url = "https://www.tripadvisor.com/Search?q=london" headers = {"CrawlbaseAPI-Parameters": "javascript=true&page_wait=5000"} proxies = {"http": proxy_url, "https": proxy_url} def parse_results(html): soup = BeautifulSoup(html, "html.parser") cards = soup.select( 'div.search-results-list[data-widget-type="LOCATIONS"] div.result' ) rows = [] for result in cards: name_el = result.select_one("div.result-title span") rating_el = result.select_one("span.ui_bubble_rating") reviews_el = result.select_one("a.review_count") location_el = result.select_one("div.address-text") rows.append({ "name": name_el.text.strip() if name_el else None, "rating": rating_el["alt"] if rating_el else None, "reviews": reviews_el.text.strip() if reviews_el else None, "location": location_el.text.strip() if location_el else None, }) return rows def main(): response = requests.get(target_url, headers=headers, proxies=proxies, verify=False) html = response.content.decode("utf-8") rows = parse_results(html) print(json.dumps(rows, indent=2)) if __name__ == "__main__": main()
输出长什么样
用python tripadvisor_scraper.py运行它,你会得到一个由结构化列表对象组成的数组。一个精简后的样本:
[ { "name": "London Eye", "rating": "4.5 of 5 bubbles", "reviews": "89,766 reviews", "location": "Westminster Bridge Road, London, England, United Kingdom" }, { "name": "Big Bus London Hop-On Hop-Off Tour and River Cruise", "rating": "4 of 5 bubbles", "reviews": "8,656 reviews", "location": "London, England, United Kingdom" }, { "name": "North London Skydiving Centre", "rating": "5 of 5 bubbles", "reviews": "2,889 reviews", "location": "Block Fen Drove, Wimblington, Cambridgeshire, England, United Kingdom" } ]
处理分页
一页结果只是演示;真实的任务会遍历多页。TripAdvisor用o(offset,偏移量)查询参数来翻阅搜索结果,每次前进一页的结果数量。在一个范围上循环,每次抬高偏移量,并把这些行收集到一个列表中。
base_url = "https://www.tripadvisor.com/Search?q=london" all_rows = [] offset = 0 for page in range(5): # adjust to the number of pages you want target_url = f"{base_url}&o={offset}" response = requests.get(target_url, headers=headers, proxies=proxies, verify=False) html = response.content.decode("utf-8") all_rows.extend(parse_results(html)) offset += 30 # results per page print(f"Collected {len(all_rows)} listings")
复用完整脚本中的parse_results,能把提取逻辑保持在一处,所以每一页都经过相同的选择器。给循环放慢节奏,别一个接一个地连发请求;代理替你轮换IP,但平稳的节奏能在一个防御性强的站点上让一次运行保持健康。
把数据保存到Excel
在你迭代的时候记录到控制台没问题,但你会想把数据落到磁盘上。有了pandas,把你的行变成一个任何人都能在电子表格中打开的Excel文件只需两行。
import pandas as pd df = pd.DataFrame(all_rows) df.to_excel("tripadvisor_data.xlsx", index=False) print("Saved tripadvisor_data.xlsx")
每个对象键都成为一列,所以你最终得到一张整洁的工作表,含有名称、评分、评论数和位置。如果你更愿意用SQL查询数据,就把同样的行写进一张SQLite表;解析部分保持不变。
保持不被封锁
即便渲染和IP轮换已由Smart AI Proxy处理,TripAdvisor仍会监控具有抓取器特征的流量。一些习惯能让一次运行保持健康,它们适用于任何难啃的商业目标。
- 给请求放慢节奏。在一个紧凑的循环里猛锤同一个搜索,是最快被限流的方式。把请求摊开,并变换你的搜索查询。
- 依靠轮换。一组住宅代理把请求分散到许多真实用户IP上,这样就没有任何单一地址会触发速率限制。Smart AI Proxy替你处理这件事;如果你自己搭建,这就是需要做对的部分。
- 读懂状态码。一次开始返回质询或错误的运行,是在告诉你当前的速率或IP层级已经不够用了。把那些响应当作信号,收手,稍后再重试。
关于更宏观的攻略,参见如何抓取网站而不被封锁。
核心要点
- TripAdvisor是客户端渲染的。普通获取返回的是一个近乎空白的外壳,所以你必须在解析之前先渲染页面。
-
Smart AI Proxy是一个即插即用的修复方案。把纯粹的
requests路由经过它,传入javascript=true,你就能在一个端点里得到渲染加IP轮换,无需新的SDK。 -
用参数调优。通过
CrawlbaseAPI-Parameters请求头发送像country和page_wait这样的选项,以进行地理定位和等待内容。 - 由BeautifulSoup完成提取。把名称、评分、评论数和位置映射到当前的选择器,并预期那些选择器会随时间漂移。
- 守在公开数据上。尊重TripAdvisor的使用条款和robots.txt;不碰账户、不碰个人数据、不整体转载评论。
常见问题
抓取TripAdvisor合法吗?
这取决于TripAdvisor的使用条款、你所在的司法辖区,以及你的目的,而它们的条款限制自动化访问。严格只采集公开的列表数据,例如名称、评分、评论数和位置,尊重robots.txt和声明的速率预期,并且永远不要触碰账户、个人数据或登录墙后的内容。评论是关联到真实个人的用户内容,所以不要把它们整体转载。对于商业再利用,去取得许可或正式的数据协议,而不要依赖一个抓取器。
为什么普通获取从TripAdvisor返回不了数据?
因为TripAdvisor用JavaScript在客户端渲染它的列表。初始HTML只是一个外壳,要在页面脚本在浏览器中运行之后才填充,所以一个原始HTTP请求返回的是状态200、而字段为空。要拿到真实数据,你必须先渲染页面,而那正是Smart AI Proxy的javascript=true参数替你处理的事。
我如何处理TripAdvisor上的动态内容加载?
通过在CrawlbaseAPI-Parameters请求头中传入javascript=true,经由Smart AI Proxy启用JavaScript渲染,并加上page_wait,在加载后保持几秒,让晚渲染的元素出现。那个组合会在返回HTML之前于一个真实的无头浏览器中运行页面,所以当BeautifulSoup解析时,动态列表已经在那里了。
我能抓取TripAdvisor搜索结果的多个页面吗?
可以。TripAdvisor用o偏移量参数来翻阅搜索结果,所以你在一个范围上循环,每次把偏移量抬高一页的结果数量,并把这些行收集到一个列表中。给循环放慢节奏,别一个接一个地连发请求,并对每一页复用相同的解析函数。
我的选择器返回None。变了什么?
几乎肯定是TripAdvisor的标记变了。像result-title和ui_bubble_rating这样的类名会不经通知地变,所以上个月还能用的选择器可能会失效。在浏览器开发者工具中重新检查一个实时搜索页并更新选择器。对任何生产级抓取器来说,定期维护选择器都属正常。
TripAdvisor允许网络抓取吗?
TripAdvisor的条款限制自动化访问,所以它并不普遍许可抓取。话虽如此,在负责任地进行时,通过像Smart AI Proxy这样的工具采集像列表名称、评分、评论数和位置这类公开可见的数据,是风险更低的路径:守在公开数据上,尊重robots.txt和速率限制,并避开任何关联到个体用户的内容。
大规模爬取任何站点,无需与基础设施对抗。
Crawlbase 负责处理代理、指纹和 CAPTCHA,让你的团队专注于交付数据流水线,而非维护爬取管道。1,000 次请求免费,无需信用卡。
