1. 程式人生 > >12306搶票V1.0

12306搶票V1.0

申明:本文僅供學習交流,若讀者對12306伺服器造成任何損失,後果自負!

 

  隨著國泉來京務工3月有餘,計劃火車出行回家歡度一下國慶佳節,可在買票上犯了難,感嘆這大北京還真是去哪都不容易啊,本著學習的目的,研究了一下12306搶票,以下為國泉實現的搶票過程。

 

一、改造計劃

1、車次和乘車人改為選擇式,從服務請求下來資訊後在介面打鉤選擇,這樣相對更方便。

2、實現離線操作,優化的主要目的!開發一個BS版本,將S端部署至雲伺服器,瀏覽器只需要選擇乘車人和車次進行下單,伺服器日夜兼程的進行刷票,成功後傳送簡訊告知。

 

二、 設計思路

1、http抓包分析協議,基於win32視窗及控制元件,gdi+繪製驗證碼圖片,c++11執行緒及鎖,curl做http客戶端,jsoncpp解析服務返回的json,ini檔案儲存服務url方便修改。

2、雙執行緒,刷票為一個執行緒,點選刷票後,此執行緒一直去查票,每迴圈一次,給主執行緒發訊息重新整理介面(若想提高刷票效率,可考慮多執行緒多路訪問12306不同CDN節點)。

3、使用者先登入,一直保持登入狀態在去刷票,有餘票立即搶單,而非有餘票後登入在下單,刷票中若檢測到被踢下線,顯示登入介面,如此迴圈。

4、驗證碼每登入成功一次,將圖片及選擇結果快取下來,這也會是國泉以後做BS版本實現自動登入的關鍵,需要大資料支撐。

 

三、效果預覽

 

 

四、細節分析

藍色為專案裡用到的協議,下面一個一個分析:

1、https://kyfw.12306.cn/otn/login/init

初始化,目的是獲取伺服器給的cookie資訊,以後的操作都需要帶著這些標識,獲取方法網上都是解析返回結果從httpHeader解析出來,然後加到請求頭裡,一開始國泉也跟著這樣做,但是後來發現不用這麼費勁,因為牛鼻curl支援cookie操作,見圖二。

 

2、https://kyfw.12306.cn/passport/captcha/captcha-image?login_site=E&module=login&rand=sjrand&0.2729611590628027

獲取圖片驗證碼,url最後的一串數字需要客戶端每次隨機生成一個16位的浮點數,國泉的做法是double d = rand() * 100.0000 / RAND_MAX

成功返回的body內容即是一張jpg圖片,我們只需要將圖片繪製出來,當滑鼠點選的時候,我們需要提取到滑鼠相對於圖片左上角的偏移座標,就是選擇結果,同時可以繪製一個小圖片。

 

3、https://kyfw.12306.cn/passport/captcha/captcha-check

圖片驗證,將滑鼠選擇的xy座標用逗號間隔開,拼成以上的字串傳入body進行驗證,在測試過程中國泉共出現過以下4種結果,第一種就是上一步說的cookie問題,這個困惑了國泉好一陣!

 

4、https://kyfw.12306.cn/passport/web/login

賬戶密碼驗證,格式是固定的,傳入賬戶密碼,返回帶uamtk表示成功,需要將他解析出來並儲存

 

5、https://kyfw.12306.cn/passport/web/auth/uamtk

uamtk驗證,分析協議內容發現需要將上一步返回的uamtk追加到cookie裡,國泉的做法是在這一步curl操作額外增加

curl_easy_setopt(curl, CURLOPT_COOKIE, "uamtk=4t8HHxPat82ghbQunJn6tkH0DDxoAZtpNP3qoA36l2l0");

若成功會攜帶newapptk,同樣解析出來並儲存。

 

6、https://kyfw.12306.cn/otn/uamauthclient

newapptk驗證,將上一步的newapptk內容按格式作為body傳送過去,成功後返回apptk,同樣解析並儲存下來。

 

7、https://kyfw.12306.cn/otn/login/userLogin

登入,將上一步提取的apptk追加到cookie裡傳送過去

curl_easy_setopt(curl, CURLOPT_COOKIE, "tk=YU0JXfSejqzzmSdvvRppefWnhkKmHq5gKycl_Q64l2l0");

這一步協議的返回值為302。下面的協議都需要帶著這個tk碼

 

8、https://kyfw.12306.cn:443/otn/index/initMy12306

登入,返回值200即成功,至此經過3輪驗證,終於登入成功,可喜可賀,可喜可賀。


9、https://kyfw.12306.cn/otn/leftTicket/queryO?leftTicketDTO.train_date=2018-10-18&leftTicketDTO.from_station=BJP&leftTicketDTO.to_station=CQW&purpose_codes=ADULT

餘票查詢,即便未登入,我們也是可以查詢餘票的。這裡需要注意的是url裡介面名queryO官方會隨著版本迭代做修改,國泉在測試的時候用的queryA,寫這篇文章的時候成了queryO,在呼叫A介面時會返回302重定向。

後面的乘車日期,出發和到達車站程式碼可通過服務https://kyfw.12306.cn/otn/resources/js/framework/station_name.js?station_version=1.9053獲取,格式是車站以@符號艾特分隔,車站資訊以|符號豎槓分隔,名稱和程式碼分別在索引1和2的位置,我們需要快取到本地檔案,在程式啟動時讀取到記憶體,也可每次啟動程式時去請求一次。若status為true,flag=1即解析result內容,結果是一個json陣列,車次內容以|符號豎槓分隔,這裡需要把內容解析並快取下來,其中比較重要的是索引為0~15的值,後面會用到,25~後面的就是餘票資訊,其中比較重要的是下標為0的那個很長的一串字元secretStr,用來標記每一次查詢結果,下單時必須對應起來。

 

10、https://kyfw.12306.cn/otn/login/checkUser

登入檢查,body內容是固定的,若沒有被踢下線,返回flag為true

 

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

申請訂單,這裡面的body內容比較多用&與號分隔,格式為:餘票返回的secretStr、出發日、返程日、其次兩個固定內容,後面是出發地名稱、目的地名稱、最後一個內容固定,成功status為true。

 

12、https://kyfw.12306.cn/otn/confirmPassenger/initDc

獲取訂單編號,請求內容固定,返回的body是一個html頁,需要解析內容裡的兩個32位md5,其中兩個的關鍵字分別是globalRepeatSubmitToken和key_check_isChange。

 

13、https://kyfw.12306.cn/otn/confirmPassenger/checkOrderInfo

檢查訂單,body內容比較多展開後就是圖2的內容

REPEAT_SUBMIT_TOKEN是上一步解析出的globalRepeatSubmitToken

oldPassengerStr需要單獨進行urlEncode編碼,格式為姓名,身份證型別,身份證,型別_ 見圖3

passengerTicketStr需要單獨進行urlEncode編碼,格式為坐席型別,0,1,姓名,身份證型別,身份證號碼,聯絡電話,N,多個以下劃線隔開,見圖3,坐席型別可參考圖4,國泉在做的時候只加了常用的5種坐席。

其中的乘客資訊,有兩種方式,國泉是登入成功後讀取的https://kyfw.12306.cn/otn/passengers/query服務獲取的聯絡人,也可以在下單的時候選擇聯絡人。

其他的內容都是固定的,成功status為true,ifShowPassCode為N

 

 

14、https://kyfw.12306.cn/otn/confirmPassenger/getQueueCount

 獲取排隊人數,確定是否可以下單,body內容展開見圖2

REPEAT_SUBMIT_TOKEN和一步一樣。

seatType為坐席型別碼,見上圖。

train_date為格林時間,需要將本地時間轉為圖2的格式在進行urlEncode編碼。

然後其他的資料全部來自query介面結果我們需要購買車票的那列車次資料,對應著賦值即可。

返回內容status為true標識成功,這一步國泉在判斷的時候沒管data裡的排隊資訊,直接跳過去下一步下單。

  

 

15、https://kyfw.12306.cn/otn/confirmPassenger/confirmSingleForQueue

下單,這裡只需要將12步裡解析出的key_check_isChange用上,其他資訊在上面都用過了,這裡就不在解釋

當submitStatus=True標識成功。

 

恭喜你,買到票了,是不是很簡單,晚上加個雞腿!