美文网首页
12306之下单流程解析

12306之下单流程解析

作者: Whyn | 来源:发表于2018-01-08 23:05 被阅读3499次

    前言

    本套教程共分3章:

    本套内容主要用于分析12306购票流程,意在编写一套自动购票小程序。12306接口 api 经常变动,但是流程分析是固定的。因此,本套教程主要记录12306 相关购票流程分析过程,以作记录。

    下单流程分析

    查询到余票后,点击预定(假设用户已经登录),那么就会走下单流程:

    1. 下单第一步:submitOrderRequest,提交订单请求。
      我们来细看下这个请求:
      submitOrderRequest_headers

    可以看到,submitOrderRequestPOST请求,其请求地址为:

    https://kyfw.12306.cn/otn/leftTicket/submitOrderRequest
    

    既然是POST请求,那么我们就来看下它的请求体内容:

    submitOrderRequest_params

    图中对参数的解释已经很清楚了,这里唯一要注意的是:我们在查询时获取得到的secretStr是已经被urlencode后的内容,因此,当我们在发送请求时,首先要把这个secretStr先解码回来,这样做的原因是大部分网络请求框架对于form data数据都会自动进行urlencode(比如requests对于dict数据),因此,这里需要先解码回原来的数据,后面网络请求框架编码后才能得到正确的值。

    最后,来看下服务器给我们返回的内容:


    submitOrderRequest_response

    这里我们只需看下status的内容即可,如果statustrue,说明我们的请求时成功的;如果statusfalse,说明我们请求失败了,失败原因可以在messages中获取。

    1. 接下来,如果我们在12306官网进行预订这个操作后,我们的页面就会跳转到:https://kyfw.12306.cn/otn/confirmPassenger/initDc(对于返程票,则跳转到:https://kyfw.12306.cn/otn/confirmPassenger/initWc),这个界面是非常重要的,后续的请求中携带的参数信息很多都是存在于这个页面源码中。
      因此,我们先来看下这个网页的源代码:

      initDc_reqeatSubmitToken
      initDc_ticketInfoForPassengerForm

    我们主要获取的就是globalRepeatSubmitTokenticketInfoForPassengerForm这两个变量内容,其中globalRepeatSubmitToken就是一个字符串,可以很容易由正则得到它的内容;而ticketInfoForPassengerForm里面我们需要的内容较多,用正则获取会繁琐很多,仔细看一下,其实它就是一个json数据,所以我们可以先用正则获取json字符串内容,再将其转成json就可以了,但是这里需要注意一下,ticketInfoForPassengerForm的格式不是严格符合json格式,因此这里获取到字符串后,需要把里面的单引号(')转换成双引号("),这样才能正确的用json来解析(小细节,注意一下)。

    1. 接下来,就到getPassengerDTOs,即获取乘客信息:
      这里也是一个POST请求,请求网址为:
    https://kyfw.12306.cn/otn/confirmPassenger/getPassengerDTOs
    

    参数信息也比较简单:


    getPassengerDTOs_params

    参数内容见上图。

    那么我们下面我们来看下服务器给我返回的数据:


    getPassengerDTOs_response01

    可以看到服务给我呢返回的是一个json数据。这里同样要先看下status的状态,为true,请求成功,为false,请求失败,失败内容从messages中获取。
    如果我们的请求成功了(status:true),那么我们就可以打开data字段,就可以看到如下图所示内容:

    getPassengerDTOs_response02

    可以看到,在data字段里面的normal_passengers字段就存储了我们账号中所有乘客的信息。因此,我们这里最主要的就是提取出这些乘客的信息,为后续提交订单做准备(乘客信息)。

    1. 接下来,会对订单信息进行检查:checkOrderInfo,对应于网页操作 提交订单 这个过程:
      首先,这是一个POST请求,网址为:
    https://kyfw.12306.cn/otn/confirmPassenger/checkOrderInfo
    

    然后,看下请求体:


    checkRoderInfo_params

    这里的cancel_flagbed_level_order_num应该是固定值,但是这两个键在ticketInfoForPassengerForm里面也能查找到,只是为null,这里应该是12306预留的一些键值,建议在程序中首先从ticketInfoForPassengerForm获取,当其为null时,才采用上面的默认值(鬼知道12306什么时候就用上这些预留的键讷!!!)

    passengerTicketStr这个参数的组合方式为:1(seatType),0,1(车票类型:ticket_type_codes),张三(passenger_name),1(证件类型:passenger_id_type_code),320xxxxxx(passenger_id_no),151xxxx(mobile_no),N

    如果有多个乘客,那么各个乘客之间用一个_分隔:seatType,0,ticket_type_codes,xxxx,mobile_no,N_seatType,0,ticket_type_codes,xxxx,mobile_no,N

    oldPassengerStr这个参数的组合方式为:张三(passenger_name),1(证件类型:passenger_id_type_code),320xxxxxx(passenger_id_no),1_

    如果有多个乘客,那么直接拼接到后面就可以了:name,1,identity,1_name2,1,identity2,1_

    最后看下返回结果:


    checkRoderInfo_response

    各键值含义请看上图。
    这里有一个点可以注意的是:我们上面看到的是成功的返回信息,笔者在测试时,抓包抓到检测失败(即status:true,submitStatus:false)的信息里面data字段里面会有一个errMsg键值,其携带了失败的具体信息,所以在程序中如果submitStatus失败了,那么可以输出errMsg显示原因。

    注:笔者觉得这里可以大胆假设一下,形如上面格式的json数据(包含status,message用来判别请求是否成功,以及另一个键内部拥有一个标志的键值),那么,在请求成功,动作失败时,一定会伴随有一个失败信息的键值返回,即errMsg

    1. 接下来的一步,就是请求:getQueueCount,获取余票和排队信息。
      这里同样还是一个POST请求,请求网址为:
    https://kyfw.12306.cn/otn/confirmPassenger/getQueueCount
    

    这里 Firefox 获取的参数train_date的格式是错误的,因此这里放上 Chrome 上截取到的最新参数信息(内容可能与上面的信息不同,但是参数获取原来一致):

    getQueueCount_params

    最后来看下返回体内容吧:


    getQueueCount_response

    可以看到返回结果也是一个json,返回结果的参数内容见上图,至于其它参数是什么意思,笔者暂时也未能分析出来!哈哈。

    1. 获取到剩余票数和排队信息后,接下来就是要将我们的订单入队确认过程:
      这里对应网页操作的 选择座位确定 这个过程,如下图所示:
      confirmSigle/GoForQueue_web

    注:此处,根据票的类型,向不同的网址发送确认信息:

    • 如果是单程票(dc):则订单入队确认过程为:confirmSingleForQueue
    • 如果是往返票(wc):则订单入队确认过程为:confirmGoForQueue

    还是按照我们上面的分析过程来:

    首先,这是一个POST请求,请求网址为:

    • 单程票(dc)
    https://kyfw.12306.cn/otn/confirmPassenger/confirmSingleForQueue
    
    • 往返票(wc)
    https://kyfw.12306.cn/otn/confirmPassenger/confirmGoForQueue
    

    然后,来看下请求体:


    confirmSingle(Go)ForQueue_params

    参数详情见上图。
    这里要讲一下:choose_seats,这里的值可以为空,表示随机座位;也可以按照上面所示的输入座位号,具体的座位号应该是按下图所示方式进行排序:

    choose_seat_code

    最后,看下返回结果:


    confirmSingleForQueue_response

    这里看到其返回的json格式跟我们上面分析checkOrderInfo格式是一致的,所以这里大胆猜测一下,当submitStatus返回false时,会同时返回errMsg字段。

    1. 上面已经入队成功了,那么接下来就等待下单成功:queryOrderWaitTime:
      这个过程需要我们自己不断的对服务器进行轮询,获取下单结果。因此,这是一个GET操作,其 URL 为:
    https://kyfw.12306.cn/otn/confirmPassenger/queryOrderWaitTime
    

    那么,我们来看下其发送的参数内容:


    queryOrderWaitTime_params

    参数详情请查看上图。

    最后,来看下服务器返回的结果:


    queryOrderWaitTime_response

    这里的操作步骤是,我们需要死循环轮询订单结果,直到当waitTime为负数并且返回的订单号(orderId)不为null,那么就表明我们下单成功了。如果waitTime为负数,但是orderIdnull,那么就需要从data[msg]提取出失败信息,12306官方请求轮询时间为3秒。(笔者测试发现,程序中请求时,waitTime的值经常为-100,因此,这里用waitTime进行判断可能存在误区,必须用orderId进行判断,当orderId为空,并且data[msg]不为空,则失败,退出程序)

    : 上述接口中的waitTime其实就是订单排队时间,单位:秒。
    所以我们可以通过waitTime数值,大概估计出排队时间,既可以给出友好的提示,又可以根据这个预估时间动态调整再次查询请求,减少请求次数。

    1. 此时,我们还剩下最后一步,就是查询下我们这个订单号是不是最终成功下单了:resultOrderForDcQueueresultOrderForWcQueue):
      首先,这是一个POST请求,网址为:
    • 单程票(dc)
    https://kyfw.12306.cn/otn/confirmPassenger/resultOrderForDcQueue
    
    • 往返票(wc)
    https://kyfw.12306.cn/otn/confirmPassenger/resultOrderForWcQueue
    

    然后,看下请求体:


    resultOrderForDcQueue_params

    参数含义见上图。

    最后,看下返回结果:


    resultOrderForDcQueue_response

    参数含义见上图。

    以上,其实就已经算是完成了下单流程。
    但是,如果还想获取订单详细信息,则请看第9步。

    1. 获取订单详细信息:queryMyOrderNoComplete:
      这个接口也是一个POST请求,其api为:
    https://kyfw.12306.cn/otn/queryOrder/queryMyOrderNoComplete
    

    其参数为:

    queryMyOrderNoComplete_params

    最重要的是其返回值:

    {
        "validateMessagesShowId": "_validatorMessage",
        "status": true,
        "httpstatus": 200,
        "data": {
            "orderDBList": [
                {
                    "sequence_no": "EH66031825", //订单号
                    "order_date": "2018-01-31 17:56:12", //下单时间
                    "ticket_totalnum": 2, //车票总张数
                    "ticket_price_all": 34200, //车票总价:342.00元
                    "cancel_flag": "Y",
                    "resign_flag": "4",
                    "return_flag": "N",
                    "print_eticket_flag": "N",
                    "pay_flag": "Y",
                    "pay_resign_flag": "N",
                    "confirm_flag": "N",
                    "tickets": [
                        {
                            "stationTrainDTO": {
                                "trainDTO": {
                                    "train_no": "6i000D40920C"  
                                },
                                "station_train_code": "D4092", //车次
                                "from_station_telecode": "IOQ",
                                "from_station_name": "深圳北", //出发站
                                "start_time": "1970-01-01 06:40:00",
                                "to_station_telecode": "CBQ",
                                "to_station_name": "潮汕", //到达站
                                "arrive_time": "1970-01-01 08:44:00",
                                "distance": "305" //距离(km)
                            },
                            "passengerDTO": {
                                "passenger_name": "用户名1", //用户名
                                "passenger_id_type_code": "1",
                                "passenger_id_type_name": "二代身份证",
                                "passenger_id_no": "xxxxxxxxxx", //身份证号
                                "total_times": "98"
                            },
                            "ticket_no": "EH66031825101003D",
                            "sequence_no": "EH66031825", //订单号
                            "batch_no": "1",
                            "train_date": "2018-02-27 00:00:00", //出发日期
                            "coach_no": "01", //车厢号
                            "coach_name": "01", //车厢名
                            "seat_no": "003D", //座位号
                            "seat_name": "03D号", //座位名
                            "seat_flag": "0",
                            "seat_type_code": "M", //座位类别号
                            "seat_type_name": "一等座", //座位类型名
                            "ticket_type_code": "1", //票类型号
                            "ticket_type_name": "成人票", //票类别
                            "reserve_time": "2018-01-31 17:56:12", //票预订时间
                            "limit_time": "2018-01-31 17:56:12",
                            "lose_time": "2018-01-31 18:26:13", 
                            "pay_limit_time": "2018-01-31 18:26:13",//最迟付款时间
                            "ticket_price": 17100, //票单价(171.00元)
                            "print_eticket_flag": "N",
                            "resign_flag": "4",
                            "return_flag": "N",
                            "confirm_flag": "N",
                            "pay_mode_code": "Y",
                            "ticket_status_code": "i",
                            "ticket_status_name": "待支付", //支付状态
                            "cancel_flag": "Y",
                            "amount_char": 0,
                            "trade_mode": "",
                            "start_train_date_page": "2018-02-27 06:40",
                            "str_ticket_price_page": "171.0", //票价字符串
                            "come_go_traveller_ticket_page": "N",
                            "return_deliver_flag": "N",
                            "deliver_fee_char": "",
                            "is_need_alert_flag": false,
                            "is_deliver": "N",
                            "dynamicProp": "",
                            "fee_char": "",
                            "insure_query_no": "",
                            "column_nine_msg": "",
                            "lc_flag": "4",
                            "integral_pay_flag": "N"
                        },
                        {
                            "stationTrainDTO": {
                                "trainDTO": {
                                    "train_no": "6i000D40920C"
                                },
                                "station_train_code": "D4092",
                                "from_station_telecode": "IOQ",
                                "from_station_name": "深圳北",
                                "start_time": "1970-01-01 06:40:00",
                                "to_station_telecode": "CBQ",
                                "to_station_name": "潮汕",
                                "arrive_time": "1970-01-01 08:44:00",
                                "distance": "305"
                            },
                            "passengerDTO": {
                                "passenger_name": "用户2",
                                "passenger_id_type_code": "1",
                                "passenger_id_type_name": "二代身份证",
                                "passenger_id_no": "xxxxxxxxxxxx", //身份证
                                "total_times": "98"
                            },
                            "ticket_no": "EH66031825101003F",
                            "sequence_no": "EH66031825",
                            "batch_no": "1",
                            "train_date": "2018-02-27 00:00:00",
                            "coach_no": "01",
                            "coach_name": "01",
                            "seat_no": "003F",
                            "seat_name": "03F号",
                            "seat_flag": "0",
                            "seat_type_code": "M",
                            "seat_type_name": "一等座",
                            "ticket_type_code": "1",
                            "ticket_type_name": "成人票",
                            "reserve_time": "2018-01-31 17:56:12",
                            "limit_time": "2018-01-31 17:56:12",
                            "lose_time": "2018-01-31 18:26:13",
                            "pay_limit_time": "2018-01-31 18:26:13",
                            "ticket_price": 17100,
                            "print_eticket_flag": "N",
                            "resign_flag": "4",
                            "return_flag": "N",
                            "confirm_flag": "N",
                            "pay_mode_code": "Y",
                            "ticket_status_code": "i",
                            "ticket_status_name": "待支付",
                            "cancel_flag": "Y",
                            "amount_char": 0,
                            "trade_mode": "",
                            "start_train_date_page": "2018-02-27 06:40",
                            "str_ticket_price_page": "171.0",
                            "come_go_traveller_ticket_page": "N",
                            "return_deliver_flag": "N",
                            "deliver_fee_char": "",
                            "is_need_alert_flag": false,
                            "is_deliver": "N",
                            "dynamicProp": "",
                            "fee_char": "",
                            "insure_query_no": "",
                            "column_nine_msg": "",
                            "lc_flag": "4",
                            "integral_pay_flag": "N"
                        }
                    ],
                    "reserve_flag_query": "p",
                    "if_show_resigning_info": "N",
                    "recordCount": "1",
                    "isNeedSendMailAndMsg": "N",
                    "array_passser_name_page": [ //用户列表
                        "用户1",
                        "用户2",
                         ........
                        "用户n"
    
                    ],
                    "from_station_name_page": [
                        "深圳北"
                    ],
                    "to_station_name_page": [
                        "潮汕"
                    ],
                    "start_train_date_page": "2018-02-27 06:40",
                    "start_time_page": "06:40",
                    "arrive_time_page": "08:44",
                    "train_code_page": "D4092",
                    "ticket_total_price_page": "342.0",
                    "come_go_traveller_order_page": "N",
                    "canOffLinePay": "N",
                    "if_deliver": "N",
                    "insure_query_no": ""
                }
            ],
            "to_page": "db"
        },
        "messages": [],
        "validateMessages": {}
    }
    

    所以,对于该接口返回内容,首先要判断status的值为true,表明请求成功,如果statusfalse,那么猜测错误原因在messages字段中。然后下单的车票内容在data["orderDBList"][0]["tickets"][index]中,这是一个数组,数组的长度就是下单的票数(就像上面下单数为2张,所以长度为2),每个数组内包含了车票的详细信息,我们可以从中取出所需信息进行展示。

    至此,我们终于完成12306下单整个流程。

    总结

    到这里,我们对12306的登录流程余票查询流程下单流程都已经详细的解析完毕了,读者根据我们这3篇的内容就可以编写出一套自动购票小程序了 ^-^ 。

    最后,附上笔者自己写的一套自动抢票小软件:EasyTrain

    相关文章

      网友评论

          本文标题:12306之下单流程解析

          本文链接:https://www.haomeiwen.com/subject/lpyinxtx.html