12306之下單流程解析

前言

本套教程共分3章:

本套內容主要用于分析12306購票流程,意在編寫一套自動購票小程序。12306接口 api 經常變動,但是流程分析是固定的。因此,本套教程主要記錄12306 相關購票流程分析過程,以作記錄。

下單流程分析

查詢到余票后,點擊預定(假設用戶已經登錄),那么就會走下單流程:

  1. 下單第一步:submitOrderRequest,提交訂單請求。
    我們來細看下這個請求:
    submitOrderRequest_headers

可以看到,submitOrderRequestPOST請求,其請求地址為:

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

既然是POST請求,那么我們就來看下它的請求體內容:

submitOrderRequest_params

圖中對參數(shù)的解釋已經很清楚了,這里唯一要注意的是:我們在查詢時獲取得到的secretStr是已經被urlencode后的內容,因此,當我們在發(fā)送請求時,首先要把這個secretStr先解碼回來,這樣做的原因是大部分網(wǎng)絡請求框架對于form data數(shù)據(jù)都會自動進行urlencode(比如requests對于dict數(shù)據(jù)),因此,這里需要先解碼回原來的數(shù)據(jù),后面網(wǎng)絡請求框架編碼后才能得到正確的值。

最后,來看下服務器給我們返回的內容:


submitOrderRequest_response

這里我們只需看下status的內容即可,如果statustrue,說明我們的請求時成功的;如果statusfalse,說明我們請求失敗了,失敗原因可以在messages中獲取。

  1. 接下來,如果我們在12306官網(wǎng)進行預訂這個操作后,我們的頁面就會跳轉到:https://kyfw.12306.cn/otn/confirmPassenger/initDc(對于返程票,則跳轉到:https://kyfw.12306.cn/otn/confirmPassenger/initWc),這個界面是非常重要的,后續(xù)的請求中攜帶的參數(shù)信息很多都是存在于這個頁面源碼中。
    因此,我們先來看下這個網(wǎng)頁的源代碼:

    initDc_reqeatSubmitToken

    initDc_ticketInfoForPassengerForm

我們主要獲取的就是globalRepeatSubmitTokenticketInfoForPassengerForm這兩個變量內容,其中globalRepeatSubmitToken就是一個字符串,可以很容易由正則得到它的內容;而ticketInfoForPassengerForm里面我們需要的內容較多,用正則獲取會繁瑣很多,仔細看一下,其實它就是一個json數(shù)據(jù),所以我們可以先用正則獲取json字符串內容,再將其轉成json就可以了,但是這里需要注意一下,ticketInfoForPassengerForm的格式不是嚴格符合json格式,因此這里獲取到字符串后,需要把里面的單引號(')轉換成雙引號("),這樣才能正確的用json來解析(小細節(jié),注意一下)。

  1. 接下來,就到getPassengerDTOs,即獲取乘客信息:
    這里也是一個POST請求,請求網(wǎng)址為:
https://kyfw.12306.cn/otn/confirmPassenger/getPassengerDTOs

參數(shù)信息也比較簡單:


getPassengerDTOs_params

參數(shù)內容見上圖。

那么我們下面我們來看下服務器給我返回的數(shù)據(jù):


getPassengerDTOs_response01

可以看到服務給我呢返回的是一個json數(shù)據(jù)。這里同樣要先看下status的狀態(tài),為true,請求成功,為false,請求失敗,失敗內容從messages中獲取。
如果我們的請求成功了(status:true),那么我們就可以打開data字段,就可以看到如下圖所示內容:

getPassengerDTOs_response02

可以看到,在data字段里面的normal_passengers字段就存儲了我們賬號中所有乘客的信息。因此,我們這里最主要的就是提取出這些乘客的信息,為后續(xù)提交訂單做準備(乘客信息)。

  1. 接下來,會對訂單信息進行檢查:checkOrderInfo,對應于網(wǎng)頁操作 提交訂單 這個過程:
    首先,這是一個POST請求,網(wǎng)址為:
https://kyfw.12306.cn/otn/confirmPassenger/checkOrderInfo

然后,看下請求體:


checkRoderInfo_params

這里的cancel_flagbed_level_order_num應該是固定值,但是這兩個鍵在ticketInfoForPassengerForm里面也能查找到,只是為null,這里應該是12306預留的一些鍵值,建議在程序中首先從ticketInfoForPassengerForm獲取,當其為null時,才采用上面的默認值(鬼知道12306什么時候就用上這些預留的鍵訥!!!)

passengerTicketStr這個參數(shù)的組合方式為: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這個參數(shù)的組合方式為:張三(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數(shù)據(jù)(包含status,message用來判別請求是否成功,以及另一個鍵內部擁有一個標志的鍵值),那么,在請求成功,動作失敗時,一定會伴隨有一個失敗信息的鍵值返回,即errMsg

  1. 接下來的一步,就是請求:getQueueCount,獲取余票和排隊信息。
    這里同樣還是一個POST請求,請求網(wǎng)址為:
https://kyfw.12306.cn/otn/confirmPassenger/getQueueCount

這里 Firefox 獲取的參數(shù)train_date的格式是錯誤的,因此這里放上 Chrome 上截取到的最新參數(shù)信息(內容可能與上面的信息不同,但是參數(shù)獲取原來一致):

getQueueCount_params

最后來看下返回體內容吧:


getQueueCount_response

可以看到返回結果也是一個json,返回結果的參數(shù)內容見上圖,至于其它參數(shù)是什么意思,筆者暫時也未能分析出來!哈哈。

  1. 獲取到剩余票數(shù)和排隊信息后,接下來就是要將我們的訂單入隊確認過程:
    這里對應網(wǎng)頁操作的 選擇座位確定 這個過程,如下圖所示:
    confirmSigle/GoForQueue_web

注:此處,根據(jù)票的類型,向不同的網(wǎng)址發(fā)送確認信息:

  • 如果是單程票(dc):則訂單入隊確認過程為:confirmSingleForQueue
  • 如果是往返票(wc):則訂單入隊確認過程為:confirmGoForQueue

還是按照我們上面的分析過程來:

首先,這是一個POST請求,請求網(wǎng)址為:

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

然后,來看下請求體:


confirmSingle(Go)ForQueue_params

參數(shù)詳情見上圖。
這里要講一下:choose_seats,這里的值可以為空,表示隨機座位;也可以按照上面所示的輸入座位號,具體的座位號應該是按下圖所示方式進行排序:

choose_seat_code

最后,看下返回結果:


confirmSingleForQueue_response

這里看到其返回的json格式跟我們上面分析checkOrderInfo格式是一致的,所以這里大膽猜測一下,當submitStatus返回false時,會同時返回errMsg字段。

  1. 上面已經入隊成功了,那么接下來就等待下單成功:queryOrderWaitTime:
    這個過程需要我們自己不斷的對服務器進行輪詢,獲取下單結果。因此,這是一個GET操作,其 URL 為:
https://kyfw.12306.cn/otn/confirmPassenger/queryOrderWaitTime

那么,我們來看下其發(fā)送的參數(shù)內容:


queryOrderWaitTime_params

參數(shù)詳情請查看上圖。

最后,來看下服務器返回的結果:


queryOrderWaitTime_response

這里的操作步驟是,我們需要死循環(huán)輪詢訂單結果,直到當waitTime為負數(shù)并且返回的訂單號(orderId)不為null,那么就表明我們下單成功了。如果waitTime為負數(shù),但是orderIdnull,那么就需要從data[msg]提取出失敗信息,12306官方請求輪詢時間為3秒。(筆者測試發(fā)現(xiàn),程序中請求時,waitTime的值經常為-100,因此,這里用waitTime進行判斷可能存在誤區(qū),必須用orderId進行判斷,當orderId為空,并且data[msg]不為空,則失敗,退出程序)

: 上述接口中的waitTime其實就是訂單排隊時間,單位:秒。
所以我們可以通過waitTime數(shù)值,大概估計出排隊時間,既可以給出友好的提示,又可以根據(jù)這個預估時間動態(tài)調整再次查詢請求,減少請求次數(shù)。

  1. 此時,我們還剩下最后一步,就是查詢下我們這個訂單號是不是最終成功下單了:resultOrderForDcQueueresultOrderForWcQueue):
    首先,這是一個POST請求,網(wǎng)址為:
  • 單程票(dc)
https://kyfw.12306.cn/otn/confirmPassenger/resultOrderForDcQueue
  • 往返票(wc)
https://kyfw.12306.cn/otn/confirmPassenger/resultOrderForWcQueue

然后,看下請求體:


resultOrderForDcQueue_params

參數(shù)含義見上圖。

最后,看下返回結果:


resultOrderForDcQueue_response

參數(shù)含義見上圖。

以上,其實就已經算是完成了下單流程。
但是,如果還想獲取訂單詳細信息,則請看第9步。

  1. 獲取訂單詳細信息:queryMyOrderNoComplete:
    這個接口也是一個POST請求,其api為:
https://kyfw.12306.cn/otn/queryOrder/queryMyOrderNoComplete

其參數(shù)為:

queryMyOrderNoComplete_params

最重要的是其返回值:

{
    "validateMessagesShowId": "_validatorMessage",
    "status": true,
    "httpstatus": 200,
    "data": {
        "orderDBList": [
            {
                "sequence_no": "EH66031825", //訂單號
                "order_date": "2018-01-31 17:56:12", //下單時間
                "ticket_totalnum": 2, //車票總張數(shù)
                "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": "深圳北", //出發(fā)站
                            "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", //出發(fā)日期
                        "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": "待支付", //支付狀態(tài)
                        "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]中,這是一個數(shù)組,數(shù)組的長度就是下單的票數(shù)(就像上面下單數(shù)為2張,所以長度為2),每個數(shù)組內包含了車票的詳細信息,我們可以從中取出所需信息進行展示。

至此,我們終于完成12306下單整個流程。

總結

到這里,我們對12306的登錄流程余票查詢流程下單流程都已經詳細的解析完畢了,讀者根據(jù)我們這3篇的內容就可以編寫出一套自動購票小程序了 ^-^ 。

最后,附上筆者自己寫的一套自動搶票小軟件:EasyTrain

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。