Python实现获取火车票信息(12306网站数据获取指南)

注意:该脚本仅用于学习交流,不可用于商业用途或高频访问12306服务器,强行高频使用所产生的一切后果与作者无关。

        12306作为我们日常出行的必备买票工具,使用非常广泛;那我们有如何在不打开官方app或网站的情况下获取到相关的出行信息呢?本次我们将利用Python中的requests库来实现这个功能;了解完之后你可以将此项目代码封装成一个接口,用于自己的出行车次查询项目中。

1、安装依赖

本文代码将使用Python以及requests运行库,这里就不详细介绍Python的安装教程了,Python环境以及PyCharm的安装https://blog.csdn.net/qq_56447863/article/details/140360049?spm=1001.2014.3001.5501安装完Python环境后,你可以通过pip命令来安装requests库,命令如下

pip install requests

2、准备工作

来到12306购票页面,按下键盘上的f12键,打开开发者页面,如图:

点击,标签卡上的网络,随后按下键盘上的Ctrl+R,刷新页面,如图 :

输入相关信息点击查询按钮,之后查看我们抓取到的数据,发现以queryG开头的文件中含有我们想要的数据。

于是我们得到了相关的URL链接,点击负载可以查看到相关参数,如图:

 12306源代码中包含有车站信息的js文件,这里就不啰嗦了,可自行查看车站名称。

3、代码分析

3.1 解析车站名称

def get_station_mapping():
    """获取车站名称与代码的映射关系"""
    url = 'https://kyfw.12306.cn/otn/resources/js/framework/station_name.js'
    response = requests.get(url, verify=True)
    stations = re.findall(r'([\u4e00-\u9fa5]+)\|([A-Z]+)', response.text)
    return {name: code for name, code in stations}, {code: name for name, code in stations}

此函数将发送GET请求到12306的车站名称代码映射接口,使用正则表达式提取车站名称和代码,并返回两个字典:车站名称到代码的映射和代码到名称的映射。

3.2 获取数据

def query_ticket_info(from_station, to_station, date):
    """查询车票信息"""
    name_to_code, code_to_name = get_station_mapping()

    # 转换车站名为代码
    from_code = name_to_code.get(from_station)
    to_code = name_to_code.get(to_station)
    # print(from_code, to_code)
    if not from_code or not to_code:
        print("车站名称错误,请检查输入!")
        return []

    # 构造请求参数
    url = 'https://kyfw.12306.cn/otn/leftTicket/queryG'
    params = {
        'leftTicketDTO.train_date': date,
        'leftTicketDTO.from_station': from_code,
        'leftTicketDTO.to_station': to_code,
        'purpose_codes': 'ADULT'
    }
    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://kyfw.12306.cn/otn/leftTicket/init',
        # 请添加你自己浏览器中的cookie
        'Cookie': ''
    }
    try:
        response = requests.get(url, params=params, headers=headers)
        response.raise_for_status()
        return response.json()['data']['result'], code_to_name
    except Exception as e:
        print(f"请求失败:{e}")
        return [], None

本段函数主要实现以下功能: 

  1. 调用get_station_mapping获取车站代码。
  2. 构造请求参数和头部信息,发送GET请求到12306的查询接口。
  3. 解析返回的JSON数据,提取车票信息。
  4. 如果请求失败,捕获异常并打印错误信息。

3.3  解析数据

def parse_ticket_data(raw_data, code_to_name):
    """解析车票数据"""
    tickets = []
    seat_map = {
        'train_number': 3,  # 车次
        'from_station': 6,  # 出发站代码
        'to_station': 7,  # 到达站代码
        'departure_time': 8,  # 出发时间
        'arrival_time': 9,  # 到达时间
        'duration': 10,  # 历时
        'business_seat': 32,  # 商务座
        'first_seat': 31,  # 一等座
        'second_seat': 30,  # 二等座
        'soft_sleeper': 23,  # 软卧
        'hard_sleeper': 28,  # 硬卧
        'hard_seat': 29,  # 硬座
        'no_seat': 26  # 无座
    }

    for item in raw_data:
        fields = item.split('|')
        try:
            ticket = {
                '车次': fields[seat_map['train_number']],
                '出发站': code_to_name[fields[seat_map['from_station']]],
                '到达站': code_to_name[fields[seat_map['to_station']]],
                '出发时间': fields[seat_map['departure_time']],
                '到达时间': fields[seat_map['arrival_time']],
                '历时': fields[seat_map['duration']],
                '商务座': fields[seat_map['business_seat']] or '--',
                '一等座': fields[seat_map['first_seat']] or '--',
                '二等座': fields[seat_map['second_seat']] or '--',
                '软卧': fields[seat_map['soft_sleeper']] or '--',
                '硬卧': fields[seat_map['hard_sleeper']] or '--',
                '硬座': fields[seat_map['hard_seat']] or '--',
                '无座': fields[seat_map['no_seat']] or '--'
            }
            tickets.append(ticket)
        except (IndexError, KeyError):
            continue
    return tickets

本段函数主要实现以下功能:

  1. 定义座位类型与数据索引的映射。
  2. 遍历原始数据,根据映射关系解析每个字段的信息。
  3. 构造车票信息字典,添加到车票列表中。
  4. 返回车票列表。

4、 完整代码

"""
#!/usr/bin/env python3
# --*-- coding:UTF-8 --*--
@Author : LuoQiu
@Project : Project
@Software : PyCharm
@File : 12306.py
@Time : 2025/05/12 16:43:13
"""
import requests
import re


def get_station_mapping():
    """获取车站名称与代码的映射关系"""
    url = 'https://kyfw.12306.cn/otn/resources/js/framework/station_name.js'
    response = requests.get(url, verify=True)
    stations = re.findall(r'([\u4e00-\u9fa5]+)\|([A-Z]+)', response.text)
    return {name: code for name, code in stations}, {code: name for name, code in stations}


def query_ticket_info(from_station, to_station, date):
    """查询车票信息"""
    name_to_code, code_to_name = get_station_mapping()

    # 转换车站名为代码
    from_code = name_to_code.get(from_station)
    to_code = name_to_code.get(to_station)
    # print(from_code, to_code)
    if not from_code or not to_code:
        print("车站名称错误,请检查输入!")
        return []

    # 构造请求参数
    url = 'https://kyfw.12306.cn/otn/leftTicket/queryG'
    params = {
        'leftTicketDTO.train_date': date,
        'leftTicketDTO.from_station': from_code,
        'leftTicketDTO.to_station': to_code,
        'purpose_codes': 'ADULT'
    }
    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://kyfw.12306.cn/otn/leftTicket/init',
        # 请使用自己浏览器中的cookie
        'Cookie': ''
    }
    try:
        response = requests.get(url, params=params, headers=headers)
        response.raise_for_status()
        return response.json()['data']['result'], code_to_name
    except Exception as e:
        print(f"请求失败:{e}")
        return [], None


def parse_ticket_data(raw_data, code_to_name):
    """解析车票数据"""
    tickets = []
    seat_map = {
        'train_number': 3,  # 车次
        'from_station': 6,  # 出发站代码
        'to_station': 7,  # 到达站代码
        'departure_time': 8,  # 出发时间
        'arrival_time': 9,  # 到达时间
        'duration': 10,  # 历时
        'business_seat': 32,  # 商务座
        'first_seat': 31,  # 一等座
        'second_seat': 30,  # 二等座
        'soft_sleeper': 23,  # 软卧
        'hard_sleeper': 28,  # 硬卧
        'hard_seat': 29,  # 硬座
        'no_seat': 26  # 无座
    }

    for item in raw_data:
        fields = item.split('|')
        try:
            ticket = {
                '车次': fields[seat_map['train_number']],
                '出发站': code_to_name[fields[seat_map['from_station']]],
                '到达站': code_to_name[fields[seat_map['to_station']]],
                '出发时间': fields[seat_map['departure_time']],
                '到达时间': fields[seat_map['arrival_time']],
                '历时': fields[seat_map['duration']],
                '商务座': fields[seat_map['business_seat']] or '--',
                '一等座': fields[seat_map['first_seat']] or '--',
                '二等座': fields[seat_map['second_seat']] or '--',
                '软卧': fields[seat_map['soft_sleeper']] or '--',
                '硬卧': fields[seat_map['hard_sleeper']] or '--',
                '硬座': fields[seat_map['hard_seat']] or '--',
                '无座': fields[seat_map['no_seat']] or '--'
            }
            tickets.append(ticket)
        except (IndexError, KeyError):
            continue
    return tickets


if __name__ == "__main__":
    # 用户输入
    from_station = input("请输入出发站(如:北京):")
    to_station = input("请输入到达站(如:上海):")
    date = input("请输入乘车日期(格式:YYYY-MM-DD):")

    # 获取数据
    raw_data, code_to_name = query_ticket_info(from_station, to_station, date)
    if not raw_data:
        exit()

    # 解析并显示结果
    tickets = parse_ticket_data(raw_data, code_to_name)
    # print(tickets)
    print(f"\n找到 {len(tickets)} 个车次信息:")
    for idx, ticket in enumerate(tickets, 1):
        print(
            f"{idx}. 车次:{ticket['车次']} 出发站:{ticket['出发站']} 到达站:{ticket['到达站']} 出发时间:{ticket['出发时间']} 到达时间:{ticket['到达时间']} 历时:{ticket['历时']} 商务座:{ticket['商务座']} 一等座:{ticket['一等座']} 二等座:{ticket['二等座']} 软卧:{ticket['软卧']} 硬卧:{ticket['硬卧']} 硬座:{ticket['硬座']} 无座:{ticket['无座']}\n")

 注意:12306网站可能有反爬虫机制,如果请求频繁可能会被封IP。

5、 运行效果

 

作者:TLuoQiu

物联沃分享整理
物联沃-IOTWORD物联网 » Python实现获取火车票信息(12306网站数据获取指南)

发表回复