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

小白学大数据
• 阅读 5

一、核心原理:为什么可以“无需Selenium”? 当你在携程网站(flights.ctrip.com)上搜索机票时,页面并不会一次性加载所有机票数据。而是在你点击查询后,由浏览器中的JavaScript代码向服务器发送一个或多个HTTP请求。服务器接收到请求后,并不会返回一个完整的HTML页面,而是返回一个纯数据的响应,通常是JSON(JavaScript Object Notation) 格式。浏览器的JavaScript引擎再根据这个JSON数据包,动态地渲染出机票列表、价格等信息。 这个“发送请求-获取JSON-渲染页面”的过程就是Ajax。 我们的目标就是绕过浏览器渲染这一步,直接:

  1. 发现:找到是哪个请求获取了机票JSON数据。
  2. 模拟:用Python的requests库完全模拟这个请求。
  3. 解析:从响应的JSON中直接提取我们需要的信息。 这种方法直接从数据源头获取信息,效率比操作浏览器快数个数量级。 二、实战:捕获并分析携程机票Ajax请求 第一步:使用浏览器开发者工具抓包
  4. 打开Chrome或Edge浏览器,进入携程国际机票页面(flights.ctrip.com)。
  5. 按 F12 键打开“开发者工具”。
  6. 切换到 “网络”(Network) 选项卡。
  7. 勾选 “保留日志”(Preserve log) 并点击 “XHR” 或 “Fetch/XHR” 筛选器。这样能过滤出最常见的Ajax请求。
  8. 在页面中选择出发地(如北京-BJS)、目的地(如上海-SHA)、日期等,点击“搜索”。
  9. 此时,“网络”面板会冒出大量请求。我们需要从中找到那个包含机票列表数据的请求。 https://img-blog.csdnimg.cn/direct/1c07d6c15c6546788b1c9a0c88c9c6f5.png (示意图:注意观察以关键词如Flight、Search命名的请求)
  10. 逐个点击新出现的请求,查看其“预览”(Preview)或“响应”(Response)选项卡。我们的目标是找到一个响应内容为JSON格式,并且里面包含可读的机票信息(如航班号、起飞时间、价格等)的请求。
  11. 一旦找到,记录下这个请求的详细信息: ○ 请求URL (Request URL):这是最重要的信息。 ○ 请求方法 (Request Method):通常是GET或POST。 ○ 请求头 (Request Headers):特别是User-Agent, Referer, 以及可能的认证信息。 ○ 查询参数 (Query String Parameters) 或 载荷 (Payload):如果是GET请求,参数在URL里;如果是POST请求,参数通常在“载荷”选项卡里,格式可能是Form Data或JSON。 以某次搜索为例,我们可能发现一个关键的请求: URL: https://flights.ctrip.com/itinerary/api/12808/products Method: POST Headers: 需要包含 Content-Type: application/json Payload: 是一个庞大的JSON对象,里面包含了查询的出发地、目的地、日期等信息。 第二步:用Python模拟请求 现在我们有了所需的信息,就可以用requests库来精确地模拟这个请求。 然后,开始编写代码。请注意,以下代码中的请求头和载荷(data)需要根据你实际抓包到的信息进行修改,否则无法成功。 这里提供一个高度仿真的模板。 import requests import json from pprint import pprint

def 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 data

except requests.exceptions.RequestException as e:
    print(f"请求发生错误: {e}")
    return None

if name == 'main': result_data = crawl_ctrip_flights() if result_data: # 使用pprint美化输出,初步查看数据结构 pprint(result_data) 重要提示:request_payload的构造是成功与否的最大关键。携程的请求载荷结构非常复杂且可能经常变动。最稳妥的方法是:在浏览器开发者工具中,找到该请求,在“载荷”选项卡中直接复制完整的JSON,然后使用 json.loads() 将其转换为Python字典,再在此基础上修改dcity, acity, date等参数。直接自己手写构造极易因缺少某些字段而失败。 第三步:解析JSON数据 得到response.json()后,我们面对的是一个多层嵌套的庞大字典(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. 反爬虫机制:携程一定有反爬措施。除了标准的User-Agent和Referer,还可能验证Cookie、IP频率等。你需要: ○ 使用代理IP池 来规避IP限制。如https://www.16yun.cn/ ○ 合理设置请求间隔(如time.sleep(random.uniform(1, 3))),避免过高频率的请求。 ○ 考虑维护一个有效的Cookie池。
  2. 参数化与规模化:将出发地、目的地、日期等参数提取出来,做成函数参数,方便批量抓取。
  3. 错误处理与日志:增加完善的异常捕获(try...except)和日志记录,保证爬虫长期稳定运行。
  4. 数据存储:将解析后的数据存入CSV、MySQL或MongoDB等数据库,而非仅仅打印出来。 结论 通过“捕获Ajax请求 -> 模拟请求 -> 解析JSON”这条技术路径,我们成功地实现了一个高效、专业的携程机票爬虫,完全摒弃了笨重低效的浏览器自动化方案。这个过程不仅适用于携程,也适用于绝大多数现代Web应用(如淘宝、美团、微博等),是中级爬虫工程师必须掌握的核心技能。
点赞
收藏
评论区
推荐文章
Wesley13 Wesley13
3年前
java将前端的json数组字符串转换为列表
记录下在前端通过ajax提交了一个json数组的字符串,在后端如何转换为列表。前端数据转化与请求varcontracts{id:'1',name:'yanggb合同1'},{id:'2',name:'yanggb合同2'},{id:'3',name:'yang
凝雪探世界 凝雪探世界
4年前
js-Answers二
前端框架相关什么是单页面应用(SPA)?单页面应用(SPA)是指用户在浏览器加载单一的HTML页面,后续请求都无需再离开此页目标:旨在用为用户提供了更接近本地移动APP或桌面应用程序的体验。流程:第一次请求时,将导航页传输到客户端,其余请求通过RESTAPI获取JSON数据实现:数据的传输通过
搞懂设计模式——代理模式 + 原理分析
举个栗子,众所周知,我们是可以在京东上购买机票的。但机票是航司提供的,我们本质上是代理销售而已。那为什么航司要让我们代理销售呢?我们又是如帮他做代理的呢?别急,本文将展开说说他们之间的关系。。。
Stella981 Stella981
3年前
Django中的session的使用
一、Session的概念cookie是在浏览器端保存键值对数据,而session是在服务器端保存键值对数据session的使用依赖cookie:在使用Session后,会在Cookie中存储一个sessionid的数据,每次请求时浏览器都会将这个数据发给服务器,服务器在接收到sess
Stella981 Stella981
3年前
Flask(中间件与全局变量 九)
中间件middlewarerequest当Flask应用处理请求时,它会根据从WSGI服务器收到的环境创建一个Request对象.因为工作者(取决于服务器的线程,进程或者协程)一次只能处理一个请求,所以在该请求期间请求数据可被认为是该工作者的全部数据。Flask对此的术语为本地请求请
Stella981 Stella981
3年前
PHP+Ajax实现文章心情投票功能实例
一个PHPAjax实现文章心情投票功能实例,可以学习了解实现投票的基本流程:通过ajax获取心情图标及柱状图相关数据,当用户点击其中的一个心情图标时,向Ajax.php发送请求,PHP验证用户cookie防止重复提交,然后将mysql中对应的数据心情字段内容加_1,成功后返回前端页面,并更新柱状图和统计数据。__!(https://img.suca
小白学大数据 小白学大数据
2个月前
Python爬虫多线程并发时的503错误处理最佳实践
一、503错误产生的原因在HTTP协议中,503错误表示服务器当前无法处理请求,通常是因为服务器暂时过载或维护。在多线程爬虫场景下,503错误可能由以下几种原因引起:1.服务器负载过高:当多个线程同时向服务器发送请求时,服务器可能因负载过高而拒绝部分请求,
利好政策下火车票、际机票预订量达今年峰值,票价继续回落
近日,随着多个利好政策发布,多平台火车票、机票搜索量11月上半月出现激增,国际机票价格持续回落。11月15日,文化和旅游部发布通知称,明确优化跨省旅游管理政策,跨省旅游经营活动不再与风险区实施联动管理。据发布数据显示在线旅游平台“去哪儿”火车票预订量
小万哥 小万哥
1年前
AJAX 前端开发利器:实现网页动态更新的核心技术
AJAXAJAX是开发者的梦想,因为你可以:在不重新加载页面的情况下更新网页在页面加载后请求来自服务器的数据在页面加载后接收来自服务器的数据在后台向服务器发送数据HTML页面html让AJAX更改这段文字更改内容HTML页面包含一个部分和一个部分用于显示来
taskbuilder taskbuilder
8个月前
TaskBuilder触发前端组件请求后台服务的常见事件
当以下事件发生时,可能会触发相关前端组件向后台服务发起请求:4.3.1页面加载时这种情况主要用于数据查询,在前端页面加载完毕后(页面静态内容渲染完毕),会自动判断页面里的每个前端组件是否设置了数据查询操作,如果设置了,则自动请求相应的后台服务进行数据查询,
小白学大数据
小白学大数据
Lv1
男 · 亿牛云 · python技术
宁为代码类弯腰,不为bug点提交!
文章
119
粉丝
5
获赞
18