携程景点评论爬虫实战:接口分析、反爬应对与数据落地
发布时间:2026/9/30 4:24:46来源:尧图网络
这几年做景点口碑分析十次有八次都要跟携程的评论区打交道。印象最深的是前阵子帮一个朋友整理某5A景区的用户评价手动翻页看到眼睛发酸半天才收集几百条而且翻页过程中页面时不时弹个验证码心态直接崩掉。后来下决心写一套Python爬虫把目标景区下的评论成批拉取下来从接口分析到数据落地前前后后踩了不少坑也总结出一套比较稳的流程。这篇就把完整思路、核心代码、反爬应对和工程化细节一次性说清楚适合正在用爬虫做旅游数据分析、舆情监控或者课程设计的同学参考。1. 这次爬虫的起点一份景区口碑分析需求1.1 环境准备与必要依赖先说环境。我的本机是Windows 11Python用的3.11版本项目单独建了虚拟环境避免把系统Python搞乱。依赖只需要几样requests发请求、pandas存数据、openpyxlExcel读写、lxml备用解析方案。安装命令如下pip install requests pandas openpyxl lxml如果有selenium兜底需求再补一个selenium和webdriver-manager后者可以自动管理浏览器驱动版本省去手动下载的麻烦pip install selenium webdriver-manager这里多说一句很多人爬虫第一步就习惯性上selenium其实对于携程这种大量数据通过XHR接口加载的站点requests加接口调用完全够用而且速度更快、更不容易被识别。selenium只作为最后兜底方案后面会详细讲。1.2 先搞清楚目标页面长什么样开工第一步不是写代码而是打开浏览器手动把携程某个景点页面的评论区翻一遍。以携程移动端页面为例访问景点详情页后下滑到用户点评区域你会看到评论列表、评分、点评标签、用户头像等元素。这时候按F12打开开发者工具切换到Network网络面板勾选Fetch/XHR筛选条件然后点击评论区的下一页。你会看到页面并没有整页刷新而是发了一个以getCommentList之类命名的请求返回的是JSON格式数据。这个观察非常重要它告诉你评论内容来自接口而不是HTML源码。如果一上来就requests.get整个页面然后用正则或者xpath去抠数据你会发现自己辛苦半天的效果远不如直接调接口干净利落。1.3 动手前必须明确的合规边界允许我说几句可能在很多教程里看不到的话。爬虫能做不代表可以乱来。我这个项目出发点是做景区口碑分析属于个人学习研究范畴。携程这类平台有大量公开的评价数据但它同样有反爬措施也有服务条款限制。实际操作时我给自己定了三条规矩不碰用户隐私信息只取评论内容、评分、标签、评论时间这些维度控制请求频率每页之间至少间隔3到5秒绝不给目标服务器造成压力数据仅用于内部学习分析不做二次转售不批量发布。另外启动采集前最好看一眼目标站点的robots.txt和点评区相关页面确认哪些路径是明确禁止的。技术能力是一回事使用方式又是一回事把合规边界想清楚后面跑数据才踏实。2. 观察携程评论的前端加载逻辑接口远比页面好抓2.1 为什么先找接口而不是直接解析HTML携程的景点评论页属于典型的SPA交互结构页面主体是服务端渲染的但评论区块是前端异步加载的。如果直接请求HTML源码你大概率拿不到完整的评论列表只能拿到一个加载中的空壳。更麻烦的是评论区的翻页、排序、筛选都通过JS触发不同操作对应不同的接口参数组合。与其去HTML里碰运气不如直接从接口入手。接口返回的JSON结构清晰字段完整分页参数直接暴露在请求载荷里解析成本低一个量级。我自己的判断标准是只要目标数据能通过接口拿到就绝不用解析HTML的笨办法。接口方案的稳定性也更高因为页面改版往往只是改UI结构接口路径和参数大概率保持不变。2.2 用Chrome DevTools定位评论XHR接口在Network面板里翻页后找名称里带comment或者getCommentList的那条请求。点开之后看两个关键地方一是Request Headers请求头二是Request Payload请求载荷。这里贴一份我测试时看到的典型请求头结构实际字段以你抓包结果为准但思路通用POST https://m.ctrip.com/restapi/soa2/13444/json/getCommentList Content-Type: application/json User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Referer: https://you.ctrip.com/sight/...请求载荷通常是一个JSON对象核心参数包括resourceId景点资源ID、resourceType资源类型、pageIndex页码、pageSize每页条数、order排序方式。把这些参数搞清楚接口调用的基础就有了。2.3 接口参数含义与请求头必备字段在编写代码前建议建一个表格把每个参数的含义记录下来。下表是简化版的字段说明帮助你养成先整理后编码的习惯参数名含义示例值备注resourceId景点资源ID68525评论归属的景区标识resourceType资源类型1固定值不同站点可能不同pageIndex当前页码1从1开始递增pageSize每页条数20最大通常不超过几十order排序方式3不同值代表默认/最新/好评优先请求头里User-Agent、Referer、Content-Type这三个字段建议每次都带上。尤其Referer很多站点的反爬系统会校验这个字段少了它可能直接被拒。Cookie一般放在headers里一并提交登录态不是必须的但带上之后接口放行的概率更高、封控更慢。3. 核心代码拆解请求、解析、清洗、落地一条线3.1 用curl转requests快速跑通第一版定位到接口后最省事的办法是在DevTools的Network面板里找到那条XHR请求右键选择Copy as cURL把复制出来的内容粘贴到curlconverter之类的在线工具里一键转成requests代码。这个方法对新手特别友好不用手拼请求头和载荷转出来的代码基本可以直接运行。我实际跑通的第一版代码长这样注意resourceId要替换成目标景点IDimport requests import json url https://m.ctrip.com/restapi/soa2/13444/json/getCommentList headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36, Referer: https://you.ctrip.com/, Content-Type: application/json, } payload { arg: { resourceId: 68525, resourceType: 1, pageIndex: 1, pageSize: 20, order: 3, channel: 7 } } resp requests.post(url, headersheaders, jsonpayload) print(resp.status_code) print(resp.text[:500])如果你看到返回的JSON里带了commentList字段说明第一步已经通了。后面的工作无非是把这个请求循环起来再对返回数据做解析和清洗。3.2 JSON数据解析提取评论内容与评分标签接口返回的JSON结构通常比较规整核心数据嵌在data.commentList里。每一条评论对象里常见字段有评论正文content、综合评分ratingScore、点评标签tagList、点评时间、发布者昵称等。我的解析思路很直接先把整个JSON转成Python字典再定位到评论列表逐条抽取字段组装成统一的记录格式。下面是一个解析并打印评论内容的简化版本data resp.json() comment_list data.get(data, {}).get(commentList, []) for item in comment_list: content item.get(content, ).strip() score item.get(ratingScore, ) tags [tag.get(name, ) for tag in item.get(tagList, [])] publish_time item.get(publishTime, ) print(score, tags, content[:80], publish_time)实际跑的时候发现有些评论内容是空的需要过滤掉有些评分字段不在顶层而是嵌套在子对象里还有评论标签的key名在不同接口版本下会变化。这些细节不用背跑一版打印出来对比着调就行。3.3 评论清洗与分页循环的边界条件解析完单页数据紧接着就要考虑两个问题什么时候停止翻页重复评论怎么处理第一版我只写了一个简单的for循环从pageIndex1开始一直加直到返回的评论列表为空才停。这个逻辑简单粗暴但有个隐患如果某页因为网络抖动返回了空列表程序会提前终止导致后续数据漏采。更稳妥的做法是记录上次请求是否真正返回数据并对空列表做二次确认比如连续三次空列表才退出循环。同时用评论ID或者内容时间组合做去重把重复项过滤掉。去重判断放到内存里即可采集量在几千条规模时完全不担心性能。page_index 1 seen set() results [] while True: payload[arg][pageIndex] page_index resp requests.post(url, headersheaders, jsonpayload, timeout10) comment_list resp.json().get(data, {}).get(commentList, []) if not comment_list: empty_retry 1 if empty_retry 3: break page_index 1 continue empty_retry 0 for item in comment_list: cid item.get(commentId) if cid and cid not in seen: seen.add(cid) results.append({ 评论ID: cid, 评分: item.get(ratingScore, ), 内容: item.get(content, ).strip(), 标签: |.join(tag.get(name, ) for tag in item.get(tagList, [])), 时间: item.get(publishTime, ) }) page_index 1 time.sleep(3.5) print(共采集评论:, len(results))这段代码里time.sleep(3.5)是必须写的既是反爬策略也是基本的网络礼貌。后面在反爬章节我会再展开聊。4. 反爬虫交手记录携程在哪些环节拦截你4.1 携程反爬的3个典型触发点跑了大概半小时后我发现事情没那么简单。前几百条评论很顺利越往后越频繁出现异常响应。总结下来携程反爬主要有三个触发点第一请求频率过高。连续以小于1秒的间隔翻页很快会触发限流接口返回异常或者跳验证码。第二请求头不完整。Cookie丢失、Referer错误、User-Agent过于标准都容易触发风控。第三行为模式异常。比如翻页速度快到不符合人类浏览规律一定时间内的请求总量超过阈值即便频率看起来正常也会被标记。有一次测试我按0.5秒一页的速度抓了200页直接触发了滑块验证码弹窗。后来把间隔调到3秒以上情况立刻好转。这个对比让我确认了携程对请求频率的敏感度远高于对请求头细节的敏感度。4.2 Session、Cookie与请求头的维护技巧一个很实用的技巧是维护一个requests.Session对象。Session会自动保存服务端下发的Cookie并在后续请求中自动携带省去手动管理Cookie环节。session requests.Session() session.headers.update(headers) def fetch_comment_page(params): resp session.post(url, jsonparams, timeout10) return resp把headers初始化时塞进session后面所有请求都走sessionCookie一致性由模块内部管理。如果你的浏览器已经登录过携程还可以从DevTools里复制当前Cookie值手动覆盖一下这样能延续登录态。实测下来带Cookie的请求被风控的概率更低一些。请求头方面我见过有人刻意伪造一整套浏览器指纹其实大可不必。保持User-Agent非空且版本合理、Referer指向目标站点、Content-Type正确这三样就够了。过度伪装反而容易触发异常行为检测。4.3 遇到滑块验证码时的正确姿势滑块验证码是全自动采集绕不开的坎。我的原则是能不碰尽量不碰碰上了就停下来休息。触发验证码后接口返回内容通常不再包含正常JSON而是跳转到一个验证页。此时继续发请求没有任何意义只会加重风控标签。正确做法如下立即停止请求等待5到15分钟这段时间手动在浏览器里访问一次目标景点页重新从浏览器复制Cookie更新session.headers降低后续请求频率把页间隔从3秒调到5秒以上如果频繁触发换个时间段再采集。这里强调一点我没有选择去模拟滑动轨迹或者接入打码平台。原因很简单项目本身只是拿公开评论做分析付出大量成本去对抗验证码没有意义而且容易踩到法律灰线。换个时间、降个频次往往就能解决问题。4.4 selenium兜底方案的取舍如果你仔细观察评论区后确认某条信息只能通过页面渲染得到接口里没有对应字段这时候才轮到selenium登场。一个轻量级兜底方案是用selenium打开浏览器翻页等DOM渲染完成后用find_elements拉评论节点。from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.chrome.options import Options options Options() options.add_argument(--disable-blink-featuresAutomationControlled) options.add_experimental_option(excludeSwitches, [enable-automation]) driver webdriver.Chrome(optionsoptions) driver.get(https://you.ctrip.com/sight/68525.html) for _ in range(3): cards driver.find_elements(By.CSS_SELECTOR, .commentItem) for card in cards: content card.find_element(By.CSS_SELECTOR, .commentContent).text print(content) next_btn driver.find_element(By.CSS_SELECTOR, .nextPage) next_btn.click() time.sleep(4)这套代码能跑但效率和稳定性都比接口方案差一截主要是页面渲染慢、元素定位依赖CSS类名类名一旦改动就得跟着改代码。所以我的建议是先花时间在DevTools里找接口实在找不到接口再用selenium不要一上来就上重型武器。5. 批量采集多景点时必须处理的三个工程问题5.1 多景点ID的批量获取思路如果只是爬一个景点前文的代码已经够用了。但真实需求往往是分析同一区域多个景区的口碑或者对比不同城市的同类景区。这时候就涉及景点ID的批量获取。携程景点详情页的URL里通常带一串数字ID比如sight/68525.html中的68525就是景点资源ID。人工逐个复制当然可以但效率太低。借助景点搜索接口搜关键词后返回的结果JSON里往往包含景点ID列表。可以先请求搜索接口提取ID列表再遍历调用评论接口。这个思路其实暴露了一个核心能力爬虫不只是一段请求代码而是一套围绕目标数据的处理器流程。拿到ID列表后建议写入一个CSV文件便于断点续跑和排查问题。5.2 断点续爬与异常重试机制采集中最常见的意外是网络超时和反爬触发。几千条数据采集过程中如果跑到一半程序崩了从头再来非常浪费。我会做两件事一是把所有请求放进一个循环里单条请求失败时捕获异常并重试二是定期把已采集的数据写入本地文件而不是等到全部跑完再落盘。def safe_fetch(params, retry3): for i in range(retry): try: resp session.post(url, jsonparams, timeout10) if resp.status_code 200: return resp.json() except requests.RequestException as e: print(请求异常, retry:, i 1, e) time.sleep(2) return NoneCycle结构再加入每采集50页就写一次Excel的机制。这样即便中途报错最多损失最近50页的数据重跑成本非常小。5.3 数据落地Excel与后续分析最后一批数据落地最简单的做法是用pandas把列表转DataFrame再输出到Excel。import pandas as pd df pd.DataFrame(results) df df.drop_duplicates(subset[评论ID]) df df[df[内容].str.len() 0] df.to_excel(scenic_comments.xlsx, indexFalse) print(df.shape)落地之后做统计就非常方便了。比如计算不同评分档位的占比用标签类目统计游客最关注的体验维度按时间维度看评论趋势。这些分析直接基于Excel即可完成不需要建数据库。如果数据量超过几万条再考虑换成SQLite。顺便提醒一句Excel单元格有字符数限制超长评论可能被截断。如果发现评论内容不完整可以把评论内容单独存成CSV或者JSON行Excel只放汇总统计表。6. 跑完上千条评论后的几点体会整套流程跑下来印象最深的一件事就是爬虫的瓶颈几乎永远不在代码而在对目标站点的理解程度。你花十分钟抓包找到接口比花两小时写一个完美的HTML解析器有价值得多你把请求频率控制在一个礼貌的区间比研究各种花哨的隐藏技巧重要得多。最终数据量是1287条有效评论清洗后拿来做情感分析和标签统计置信度基本够用了。耗时大概四十分钟其中很大一部分是等待请求间隔。如果你要爬的景点评论量特别大可以把间隔时间适当压缩到2秒左右同时关注响应码变化在触发风控前及时刹车。后续要扩展的话可以考虑接入代理池做分布式采集或者把数据落在数据库里做增量更新。但这些属于锦上添花对大部分个人分析和学习场景来说Requests加pandas这套组合已经足够解决问题。
网站建设高端定制企业官网