Python爬虫实战:逆向分析携程AJAX评论接口与数据抓取 📅 发布时间:2026/8/28 8:01:26 👁 浏览次数: 1. 项目缘起为什么携程的评论数据这么“难啃”最近想分析一下热门旅游景点的用户评价趋势第一反应就是去携程上抓点数据。结果一上手就发现事情没那么简单。你打开一个景点页面比如“上海迪士尼乐园”翻看评论时页面地址栏的URL纹丝不动但下面的评论内容却在刷刷地更新。这明摆着就是AJAX动态加载的典型特征。对于刚接触爬虫的朋友来说这种网站简直就是“噩梦”。传统的爬虫思路是分析页面链接规律比如看到page1、page2这样的参数然后构造URL循环请求。但携程的评论翻页你根本找不到这样的链接。点击“下一页”按钮浏览器并没有跳转到一个新页面而是悄悄地发起了一个网络请求从服务器“偷”回来一页新的评论数据然后悄无声息地更新在当前页面上。这种技术让数据不再直接暴露在HTML源码里而是藏在了后台的XHRXMLHttpRequest请求中。更“狡猾”的是携程这类大型平台的反爬机制相当完善。你直接用requests去请求那个看似静态的景点详情页返回的HTML里评论区域往往是空的或者只有第一页的几条数据。真正的评论宝藏都埋藏在那些需要特定参数、特定请求头才能触发的AJAX接口里。这就是为什么标题里强调“【最新方法】”因为老一套的静态页面解析在这里完全行不通我们必须深入网络层面去“监听”和“模仿”浏览器与服务器之间的真实对话。这个项目的核心就是教会你如何成为这个对话的“窃听者”和“模仿者”精准地拿到每一页的评论数据。2. 核心武器库逆向分析与请求模拟面对AJAX动态加载我们不能蛮干得用对工具和方法。核心思路就八个字逆向工程模拟请求。简单说就是搞清楚浏览器是怎么拿到数据的然后我们用Python程序完全复现这个过程。2.1 第一步使用开发者工具进行网络抓包这是所有工作的起点也是最关键的一步。以Chrome浏览器为例打开携程景点页面例如上海迪士尼乐园按下F12打开开发者工具切换到Network网络面板。在开始操作前最好先点击一下面板上的“清除”按钮一个带斜杠的圆圈清空之前的记录。然后你在页面上点击“下一页”评论按钮。此时你的眼睛要紧盯Network面板会看到瞬间刷出来一堆新的请求。我们的目标就在这里面。你需要重点关注XHR/Fetch类型的请求因为AJAX请求通常属于这一类。你会看到一些名字可能包含comment,review,list等关键词的请求这些就是嫌疑对象。点击这个嫌疑请求查看它的详细信息。这里有几个关键标签页需要你仔细侦查Headers请求头这是请求的“身份证”和“通行证”。Request URL这是最重要的信息即AJAX接口的真实地址。把它复制下来。Request Method通常是GET或POST。Query String Parameters / Payload如果是GET请求参数会显示在Query String里如果是POST则显示在Payload里。这些参数就是控制页码、排序、景点ID的关键。页数跳转的秘密就藏在这些参数里。你需要找出哪个参数代表页码可能是page,pageIndex,start,offset等。Request Headers这里的信息至关重要用于模拟浏览器身份。必须关注的有User-Agent告诉服务器你是什么浏览器。不带这个或者用Python默认的很容易被识别为爬虫。Referer表示你这个请求是从哪个页面发起的。很多反爬会校验这个字段。Cookie维持登录状态和会话的关键。对于需要登录才能看评论的场景部分携程评论可能需要这个字段是必须的。其他如Accept,Accept-Language等尽量保持和浏览器一致让请求看起来更“自然”。Preview / Response响应这里展示了服务器返回的原始数据。携程的接口返回的数据格式极大概率是JSON。这是一种结构化的数据格式对我们来说是天大的好消息因为解析JSON比解析HTML简单、稳定得多。在这里你可以直观地看到评论内容、用户信息、评分等是否都在这个JSON里。通过这一步你已经完成了情报收集知道了去哪里拿数据Request URL、需要带什么“暗号”才能拿到Params Headers、以及数据长什么样JSON Response。2.2 第二步Python实战从单页请求到自动翻页拿到所有关键信息后就可以用Python的requests库来扮演浏览器了。下面是一个高度还原、可直接参考的代码框架和思路。首先安装必要的库pip install requests。import requests import json import time import pandas as pd # 1. 定义核心请求参数这些需要你从开发者工具里实际抓取并替换 # 假设我们分析出的接口和参数如下实际值以你抓包为准 base_url https://m.ctrip.com/restapi/soa2/xxx/getCommentList # 示例URL非真实 params { poiId: 123456, # 景点ID从景点页面URL或源码中获取 pageIndex: 1, # 页码参数这是我们实现翻页的关键 pageSize: 10, # 每页条数 sortType: 3, # 排序方式例如3可能是“最新” # ... 可能还有其他固定参数 } # 2. 准备请求头尽量模拟浏览器 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/sight/shanghai2/xxx.html, # 替换为实际景点页地址 Accept: application/json, text/plain, */*, Accept-Language: zh-CN,zh;q0.9,en;q0.8, # 注意Cookie非常敏感如果接口需要登录态必须从已登录的浏览器复制Cookie字符串过来。 # Cookie: 你的Cookie字符串慎用且不要泄露 } # 3. 定义一个函数用于请求单页数据 def fetch_single_page(page_index): 根据页码获取单页评论数据 # 更新页码参数 current_params params.copy() current_params[pageIndex] page_index try: response requests.get(base_url, paramscurrent_params, headersheaders, timeout10) response.raise_for_status() # 如果状态码不是200抛出异常 # 解析JSON响应 data_json response.json() # 通常数据在某个字段下例如 data[list] comment_list data_json.get(data, {}).get(list, []) return comment_list except requests.exceptions.RequestException as e: print(f请求第{page_index}页时发生错误: {e}) return [] except json.JSONDecodeError as e: print(f解析第{page_index}页JSON时发生错误: {e}) print(f原始响应文本: {response.text[:500]}) # 打印前500字符辅助调试 return [] # 4. 实现自动翻页爬取 all_comments [] max_pages 50 # 设置一个最大爬取页数防止无限循环 for page in range(1, max_pages 1): print(f正在爬取第 {page} 页...) comments fetch_single_page(page) if not comments: # 如果当前页没有数据可能已到末页 print(f第{page}页无数据可能已爬取完毕。) break all_comments.extend(comments) # 礼貌性延时避免请求过快被封IP time.sleep(1.5) # 建议1-3秒 # 一个简单的停止条件如果返回的评论数少于pageSize可能是最后一页 if len(comments) params[pageSize]: print(f第{page}页评论数不足判断为最后一页。) break print(f爬取结束共获取 {len(all_comments)} 条评论。) # 5. 数据保存示例保存为JSON和CSV with open(ctrip_comments.json, w, encodingutf-8) as f: json.dump(all_comments, f, ensure_asciiFalse, indent2) # 如果评论是字典列表可以转为DataFrame再存CSV if all_comments: df pd.DataFrame(all_comments) # 选择需要的字段例如用户昵称、评分、评论内容、时间 df_to_save df[[userNick, score, content, publishTime]] df_to_save.to_csv(ctrip_comments.csv, indexFalse, encodingutf-8-sig) print(数据已保存为 ctrip_comments.json 和 ctrip_comments.csv)这段代码清晰地展示了从分析到实现的全过程。核心逻辑在于fetch_single_page函数和循环中的翻页控制。我们通过修改params字典中的pageIndex来模拟点击“下一页”的操作。3. 关键细节与深度避坑指南上面的代码框架能跑通基础流程但想稳定、长期地爬取携程数据以下几个坑你必须提前知道怎么绕过去。3.1 参数逆向不仅仅是pageIndex你以为找到pageIndex就万事大吉了太天真了。携程的接口往往有多个关键参数且可能经过简单编码或校验。poiId / resourceId这是景点的唯一标识。你不能直接从浏览器的地址栏里找因为那是给用户看的“漂亮URL”。你需要从景点页面的HTML源码里搜索或者从其他初始化的AJAX请求响应里找。一个常见方法是在Network里找第一个加载页面核心数据的XHR请求它的响应里很可能包含这个ID。签名sign或令牌token这是高级反爬手段。服务器为了防止接口被随意调用可能会要求所有请求参数按照一定规则比如按字母排序后拼接密钥计算一个MD5或SHA值作为sign参数一同提交。如果你发现抓包看到的参数里有一个长长的、无规律的字符串很可能就是签名。逆向签名算法是爬虫工程师的进阶技能需要分析前端JavaScript代码。对于初学者如果只是短期、小量爬取可以尝试直接复用短时间内抓包到的有效签名注意它有有效期。时间戳_ts很多接口会要求一个当前时间戳参数可能是秒级或毫秒级用于防止请求重放。这个好办用Python的time.time()生成即可。实操心得在开发者工具的Headers里仔细对比你连续翻两页的请求参数找出哪些是变化的如pageIndex, _ts哪些是固定的如poiId哪些看起来是加密的如sign。先尝试只修改变化参数进行请求如果返回错误如“签名无效”那就说明遇到了签名校验。3.2 请求头伪装细节决定成败User-Agent是最基础的但远远不够。携程可能会检查一系列头信息来判断请求是否来自真实浏览器。Referer这个头必须正确设置为当前景点页的URL。缺少或错误服务器可能直接拒绝。Accept-Encoding浏览器通常会声明gzip, deflate, br表示可以接受压缩的响应。requests库会自动处理gzip和deflate但对于br (Brotli)可能需要额外库。一个稳妥的做法是在headers里设置Accept-Encoding: gzip, deflate并让requests自动解压。Connection: 可以设置为keep-alive。Host通常requests库会自动添加但如果你遇到奇怪问题可以手动指定。最棘手的是Cookie。如果评论数据需要登录后才能查看全部比如某些“精华评论”那么你必须使用一个有效的登录Cookie。获取方式是在浏览器中手动登录携程账号然后从开发者工具的请求头里复制整个Cookie字符串。请注意Cookie是个人敏感信息且会过期。用Cookie爬取也涉及法律和平台规则风险请务必谨慎仅用于个人学习研究。3.3 反爬对抗IP、频率与验证码即使你的请求模拟得天衣无缝服务器还是会从行为层面进行防御。IP限制与代理池如果你从一个IP地址在短时间内发起大量请求极大概率会被封IP。表现就是请求开始返回错误码如403、429或者返回一个包含验证码的HTML页面。解决方案是使用代理IP。你需要一个可靠的代理IP服务商然后在requests.get请求中通过proxies参数轮换使用不同的IP。proxies { http: http://your-proxy-ip:port, https: http://your-proxy-ip:port, } response requests.get(url, paramsparams, headersheaders, proxiesproxies, timeout10)对于大规模爬取你需要自己维护一个IP池并检测IP的有效性。请求频率控制time.sleep()是你的好朋友。在每次请求之间随机休眠一段时间例如time.sleep(random.uniform(1, 3))可以大大降低被识别为机器人的风险。模拟人类浏览的随机性很重要。动态参数与JavaScript渲染这是最复杂的情况。如果接口的关键参数如poiId,sign是由前端JavaScript代码在每次请求时动态计算生成的并且计算逻辑被混淆代码难以阅读那么单纯的请求模拟就会失败。这时可能需要用到Selenium或Playwright这类浏览器自动化工具直接驱动一个真实的浏览器如Chrome来访问页面让浏览器自然执行JS生成参数然后我们再从浏览器内存中截获AJAX请求。但这方法速度慢、资源消耗大是最后的备选方案。从携程目前基于常见分析的情况看其移动端m.ctrip.com或某些核心接口的加密强度可能低于主站可以作为突破口。4. 数据解析、存储与后续分析思路当你成功拿到JSON数据后事情就变得简单多了。4.1 解析与清洗JSON数据是结构化的用Python的json库解析后就是一个嵌套的字典/列表。你需要像剥洋葱一样一层层找到你要的数据。# 假设一条评论的JSON结构如下根据实际响应调整 # { # id: 10001, # content: 非常好玩孩子很喜欢, # score: 5, # userInfo: {nickName: 开心一家人}, # publishTime: 2023-10-01 12:30:45 # } for comment in comment_list: comment_id comment.get(id) content comment.get(content, ).strip() # 去除空白字符 score comment.get(score) user_nick comment.get(userInfo, {}).get(nickName, 匿名用户) publish_time comment.get(publishTime) # 可能还需要处理HTML实体、表情符号等 # content html.unescape(content) # 如果需要转换HTML实体清洗工作包括处理缺失值None、去除文本中的多余空格和换行、转换时间格式、将字符串数字转为数值类型等。4.2 存储方案JSON文件如上文代码所示json.dump是最简单直接的存储方式保留了数据的原始结构适合后续用Python直接读取分析。CSV文件使用pandas的DataFrame和to_csv方法适合用Excel打开或进行简单的统计分析。注意中文编码问题使用utf-8-sig编码可以让Excel正确识别。数据库如SQLite/MySQL如果数据量很大几十万条以上或者需要复杂的查询和关联存入数据库是更专业的选择。你可以预先设计好表结构如comments(id, poi_id, content, score, user_nick, publish_time)然后逐条或批量插入。4.3 简单的分析方向示例拿到数据后你可以做很多有趣的分析评分分布统计1-5分各有多少条绘制饼图或柱状图直观了解口碑概况。评论内容词云使用jieba分词库和wordcloud库生成评论关键词词云看看游客最常提及的是什么“好玩”、“排队”、“服务”、“门票贵”。时间趋势分析按月份或季度统计评论数量看看该景点的热度变化趋势是否与节假日相关。情感分析进阶使用情感分析模型如SnowNLP或训练自己的模型对评论内容进行情感打分量化游客的情绪是积极还是消极。整个爬取和分析过程从逆向分析到数据可视化是一个完整的闭环。它考验的不仅仅是写代码的能力更是分析问题、寻找规律、应对挑战的综合能力。每一次成功抓取一个“难啃”的网站都是对这套思维和方法论的一次有力验证。记住爬虫技术是一把双刃剑务必在法律和平台规则的框架内以学习和研究为目的合理使用尊重网站的robots.txt协议控制请求频率避免对目标网站造成不必要的负担。