当前位置: 首页 > news >正文

无需Selenium:巧用Python捕获携程机票Ajax请求并解析JSON数据

一、核心原理:为什么可以“无需Selenium”?

当你在携程网站(<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">flights.ctrip.com</font>)上搜索机票时,页面并不会一次性加载所有机票数据。而是在你点击查询后,由浏览器中的JavaScript代码向服务器发送一个或多个HTTP请求。服务器接收到请求后,并不会返回一个完整的HTML页面,而是返回一个纯数据的响应,通常是JSON(JavaScript Object Notation) 格式。浏览器的JavaScript引擎再根据这个JSON数据包,动态地渲染出机票列表、价格等信息。

这个“发送请求-获取JSON-渲染页面”的过程就是Ajax。

我们的目标就是绕过浏览器渲染这一步,直接:

  1. 发现:找到是哪个请求获取了机票JSON数据。
  2. 模拟:用Python的<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">requests</font>库完全模拟这个请求。
  3. 解析:从响应的JSON中直接提取我们需要的信息。

这种方法直接从数据源头获取信息,效率比操作浏览器快数个数量级。

二、实战:捕获并分析携程机票Ajax请求

第一步:使用浏览器开发者工具抓包

  1. 打开Chrome或Edge浏览器,进入携程国际机票页面(<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">flights.ctrip.com</font>)。
  2. F12 键打开“开发者工具”。
  3. 切换到 “网络”(Network) 选项卡。
  4. 勾选 “保留日志”(Preserve log) 并点击 “XHR”“Fetch/XHR” 筛选器。这样能过滤出最常见的Ajax请求。
  5. 在页面中选择出发地(如北京-BJS)、目的地(如上海-SHA)、日期等,点击“搜索”。
  6. 此时,“网络”面板会冒出大量请求。我们需要从中找到那个包含机票列表数据的请求。

https://img-blog.csdnimg.cn/direct/1c07d6c15c6546788b1c9a0c88c9c6f5.png (示意图:注意观察以关键词如_<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">Flight</font>__<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">Search</font>_命名的请求)

  1. 逐个点击新出现的请求,查看其“预览”(Preview)或“响应”(Response)选项卡。我们的目标是找到一个响应内容为JSON格式,并且里面包含可读的机票信息(如航班号、起飞时间、价格等)的请求。
  2. 一旦找到,记录下这个请求的详细信息:
    • 请求URL (Request URL):这是最重要的信息。
    • 请求方法 (Request Method):通常是<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">GET</font><font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">POST</font>
    • 请求头 (Request Headers):特别是<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">User-Agent</font>, <font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">Referer</font>, 以及可能的认证信息。
    • 查询参数 (Query String Parameters)载荷 (Payload):如果是<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">GET</font>请求,参数在URL里;如果是<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">POST</font>请求,参数通常在“载荷”选项卡里,格式可能是<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">Form Data</font><font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">JSON</font>

以某次搜索为例,我们可能发现一个关键的请求:

URL: <font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">https://flights.ctrip.com/itinerary/api/12808/products</font>
Method: <font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">POST</font>
Headers: 需要包含 <font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">Content-Type: application/json</font>
Payload: 是一个庞大的JSON对象,里面包含了查询的出发地、目的地、日期等信息。

第二步:用Python模拟请求

现在我们有了所需的信息,就可以用<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">requests</font>库来精确地模拟这个请求。

然后,开始编写代码。请注意,以下代码中的请求头和载荷(data)需要根据你实际抓包到的信息进行修改,否则无法成功。 这里提供一个高度仿真的模板。

import requests
import json
from pprint import pprintdef crawl_ctrip_flights():# 1. 定义目标URL (从开发者工具中复制)url = 'https://flights.ctrip.com/itinerary/api/12808/products'# 2. 定义请求头 (从开发者工具中复制并简化,User-Agent和Referer至关重要)headers = {'Content-Type': 'application/json','User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36','Referer': 'https://flights.ctrip.com/itinerary/oneway/bjs-sha?date=2023-10-01', # 这个Referer需要根据你的搜索修改'Connection': 'keep-alive',# 有时可能需要其他Header,如Authorization等,请根据抓包实际情况添加。}# 3. 构建请求载荷 (Payload) - 这是最核心的部分,需要根据你的搜索条件构建# 这个JSON结构非常复杂,通常直接从浏览器抓包复制,然后修改关键参数。request_payload = {"flightWay": "Oneway","classType": "ALL","hasChild": False,"hasBaby": False,"searchIndex": 1,"airportParams": [{"dcity": "BJS", # 出发地城市代码"acity": "SHA", # 目的地城市代码"dcityname": "北京","acityname": "上海","date": "2023-10-01" # 出发日期,格式YYYY-MM-DD}],"selectedInfos": None# ... 这里可能还有大量其他字段,请务必使用你抓包到的完整JSON结构}# 4. 发送POST请求print("正在发送请求...")try:# 将Python字典转换为JSON字符串并发送response = requests.post(url=url,headers=headers,data=json.dumps(request_payload)  # 使用json.dumps转换)response.raise_for_status()  # 检查请求是否成功# 5. 解析响应# 响应内容直接就是JSON,我们可以用.json()方法将其转换为Python字典data = response.json()print("请求成功!")return dataexcept requests.exceptions.RequestException as e:print(f"请求发生错误: {e}")return Noneif __name__ == '__main__':result_data = crawl_ctrip_flights()if result_data:# 使用pprint美化输出,初步查看数据结构pprint(result_data)

重要提示<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">request_payload</font>的构造是成功与否的最大关键。携程的请求载荷结构非常复杂且可能经常变动。最稳妥的方法是:在浏览器开发者工具中,找到该请求,在“载荷”选项卡中直接复制完整的JSON,然后使用 <font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">json.loads()</font> 将其转换为Python字典,再在此基础上修改<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">dcity</font>, <font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">acity</font>, <font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">date</font>等参数。直接自己手写构造极易因缺少某些字段而失败。

第三步:解析JSON数据

得到<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">response.json()</font>后,我们面对的是一个多层嵌套的庞大字典(dict)。接下来的任务就是像剥洋葱一样,一层层地找到我们需要的数据。

def parse_flight_data(data):"""从返回的JSON数据中解析出航班信息"""# 1. 首先检查数据结构和状态码if not data or data.get('status') != 0:print("数据获取失败或状态非零")return# 2. 找到核心数据路径# 这个路径需要通过在Preview中不断展开来摸索try:# 这是一个示例路径,实际路径请根据你获取到的JSON结构进行调整!itinerary_list = data['data']['itineraryList']print(f"共找到 {len(itinerary_list)} 个行程")for itinerary in itinerary_list:# 继续深入挖掘,找到航班信息列表和价格legs = itinerary['legs']for leg in legs:flights = leg['flights']for flight in flights:# 提取具体信息airline_name = flight['airlineName']flight_number = flight['flightNumber']departure_city = flight['departureCityName']arrival_city = flight['arrivalCityName']departure_time = flight['departureDate']arrival_time = flight['arrivalDate']# 提取价格信息 (价格可能在行程itinerary层,也可能在航班层,需仔细分析)# 这里假设价格在itinerary层price_info = itinerary.get('priceList', [{}])[0]price = price_info.get('price', '无价格信息')# 打印信息print(f"""
航空公司: {airline_name}
航班号: {flight_number}
出发: {departure_city} - 时间: {departure_time}
到达: {arrival_city} - 时间: {arrival_time}
价格: ¥{price}
{'-' * 50}""")except KeyError as e:print(f"解析数据时出错,键不存在: {e}")pprint(data) # 再次输出数据,方便调试

最后,在主函数中调用解析函数:

if __name__ == '__main__':result_data = crawl_ctrip_flights()if result_data:parse_flight_data(result_data)

三、注意事项与优化建议

  1. 反爬虫机制:携程一定有反爬措施。除了标准的<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">User-Agent</font><font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">Referer</font>,还可能验证Cookie、IP频率等。你需要:
    • 使用代理IP池 来规避IP限制。如https://www.16yun.cn/
    • 合理设置请求间隔(如<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">time.sleep(random.uniform(1, 3))</font>),避免过高频率的请求。
    • 考虑维护一个有效的Cookie池
  2. 参数化与规模化:将出发地、目的地、日期等参数提取出来,做成函数参数,方便批量抓取。
  3. 错误处理与日志:增加完善的异常捕获(<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">try...except</font>)和日志记录,保证爬虫长期稳定运行。
  4. 数据存储:将解析后的数据存入CSV、MySQL或MongoDB等数据库,而非仅仅打印出来。

结论

通过“捕获Ajax请求 -> 模拟请求 -> 解析JSON”这条技术路径,我们成功地实现了一个高效、专业的携程机票爬虫,完全摒弃了笨重低效的浏览器自动化方案。这个过程不仅适用于携程,也适用于绝大多数现代Web应用(如淘宝、美团、微博等),是中级爬虫工程师必须掌握的核心技能。

http://www.dtcms.com/a/389329.html

相关文章:

  • Python版Kafka基础班 - 学习笔记
  • IDEA 查看 Maven 依赖树与解决 Jar 包冲突
  • 【LVS入门宝典】LVS与Nginx、HAProxy的对比:四层(LVS) vs 七层(Nginx)的适用场景
  • 系统安全配置与加固
  • 【AI-Agent】AI游戏库
  • 病毒库更新原理
  • 服务器内存爆炸,日志无报错,通过分析 Dump 文件查找问题原因
  • 【Redis学习】服务端高并发分布式结构演变之路
  • 【JavaScript 性能优化实战】第三篇:内存泄漏排查与根治方案
  • 关于JavaScript性能优化实战的技术
  • 分布式流处理与消息传递——Paxos Stream 算法详解
  • ​​瑞芯微RK3576多路AHD摄像头实测演示,触觉智能配套AHD硬件方案
  • mysql删除数据库命令,如何安全彻底地删除MySQL数据库?
  • vscode中创建项目、虚拟环境,安装项目并添加到工作空间完整步骤来了
  • 如何快速传输TB级数据?公司大数据传输的终极解决方案
  • Linux的进程调度及内核实现
  • 使用BeanUtils返回前端为空值?
  • Windows Server数据库服务器安全加固
  • Linux TCP/IP调优实战,性能提升200%
  • Amazon ElastiCache:提升应用性能的云端缓存解决方案
  • 查找并替换 Excel 中的数据:Java 指南
  • 多线服务器具体是指什么?
  • Golang语言基础篇001_常量变量与数据类型
  • pytest文档1-环境准备与入门
  • MySQL 专题(四):MVCC(多版本并发控制)原理深度解析
  • 【开发者导航】在终端中运行任意图形应用:term.everything
  • [Python]pytest是什么?执行逻辑是什么?为什么要用它测试?
  • Nginx set指令不能使用在http块里,可以使用map指令
  • LeetCode 1759.统计同质子字符串的数目
  • 揭秘Linux文件管理与I/O重定向核心