12306搶票腳本開發(二)解析火車站代號并分析查詢的HTTP請求


文章地址 :

12306搶票腳本開發(一)提綱
12306搶票腳本開發(二)解析火車站代號并分析查詢的HTTP請求
12306搶票腳本開發(三)實現一個簡單的查詢腳本
12306搶票腳本開發(四)完善上節課的代碼并面向對象
12306搶票腳本開發(五)更友好的使用方式
12306搶票腳本開發(六)更友好的時間輸入方式
12306搶票腳本開發(七)將前幾節課的成果結合起來實現一個完整的工具


簡介 :

首先我們要實現的這個腳本為了實現易用性
在選擇出發地和目的地的時候應該是讓用戶直接輸入出發地的目的地的中文名
然后系統自動去識別 , 匹配到響應的火車站代碼然后再發送 http 請求
通過分析 http 請求可以得知
12306網站在用戶選擇火車站并點擊查詢的時候 , 并沒有把火車站的中文名作為 http 請求的參數傳遞的
是這樣做的 , 事先保存了一個火車站名稱和代碼對應關系的文件(其實是一個 js 變量) , 用戶輸入火車站名 , 或者火車站名拼音的首字母之后 , js 就會解析找到真正的火車站的代號 , 當然這個代號在前臺是看不見的 , 當點擊查詢之后就會構造一個 url , url 的 get 參數中就會有城市的代碼以及這次查詢的相關信息(出發日期等等)
大家可以想一下 , 為什么在我們輸入火車站名的時候下面會模糊匹配到以已輸入的字符串開頭的火車站 , 這個就是預先下載了一個這樣的保存火車站名和首字母的映射關系的文件 , 然后 js 在檢測到輸入框中的文本發生變化的時候就進行一次檢索 , 更新匹配到的火車站 , 并顯示在前臺

Paste_Image.png

可以看到這個文件其實是定義了一個 js 的變量
這個變量好像是有一定的格式
可以很容易就分析這里應該是以 '@' 這個字符來分隔每一個火車站的
然后又以 '|' 這個字符來分隔每一個火車站中的數據 , 這些數據具體是干什么的我們現在還說不準
這個要通過分析 12306 網站的 js 代碼才能得知
那么我們應該如何來分析這個變量到底是如何被解析 , 每一個字段都有什么用呢 ?
我們首先來下載 12306 網站的代碼 :

可以使用 wget 來遞歸下載整個網站
wget -r https://kyfw.12306.cn/otn/

下載完成后 , 我們需要在 js 文件或者 html 文件( html 也可能內嵌 js 代碼)中去搜索使用這個變量(station_names)的地方 , 暫時想到的方法就是搜索文件的內容 , 匹配變量名 , 不知道還有沒有更好的方法 , 比如說能不能利用 js 的引擎自動地找到引用該變量的函數什么的...

find ./ -type f -name "*.js" | xargs grep "station_names"

結果如下 :

Paste_Image.png

哈 , 我們已經可以定位到一個具體的文件的具體行了 :

./otn/resources/js/framework/city_name.js
821:            if (typeof (station_names) != "undefined") {
822:                if (station_names.indexOf(join) == -1) {
1186:       if (typeof (station_names) != "undefined") {
1188:           var cities = station_names.split('@');

那么解析的工作肯定是在這個文件中完成的
來看看這個文件吧 :

Paste_Image.png
Paste_Image.png
if (typeof (station_names) != "undefined") {
    // 分拆城市信息
    var cities = station_names.split('@');
    for ( var i = 0; i < cities.length; i++) { // 遍歷所有的火車站
        var titem = cities[i];
        var raha = titem.toString().charAt(0);

        for(var k in city_name_character){
            if (raha == city_name_character[k]) {
                liarray_cities_array[k].push(titem.split('|'));
            }
        }

        if (titem.length > 0) { // @bjb|北京北|VAP|beijingbei|bjb|0
            titem = titem.split('|'); // 把每個火車站的信息再用 '|' 來分隔 , 也就是每個字段
            if (favcityID != "" && titem[2] == favcityID) { // 這里判斷了第三個字段 , 用到了 favcityID , 先不用關注這里
                favcity = titem;
                array_cities.unshift(titem); // 向 array_cities 這個數組中插入一個城市 , 也就是 "@bjb|北京北|VAP|beijingbei|bjb|0" 這樣的字符串 , 加到首部
                // 當fav城市位于第一頁時,避免重復顯示
                if (i > 6) { // 可以發現幾乎大部分的火車站的字段都是 6 個 , 那這里也先不要太關注了
                    array_cities.push(titem); // 加到數組末尾
                }
            } else {
                array_cities.push(titem); // 總是就是向這個數組中加入一個城市的所有字段組成的數組 , 需要查一下這個變量
                // 只要我們能找到 js 是怎么使用這個 變量 的 , 那么我們就可以知道這 6 個字段都是什么
            }
        }
    }

    liarray_cities1 = liarray_cities_array[0].concat(liarray_cities_array[1]).concat(liarray_cities_array[2]).concat(liarray_cities_array[3]).concat(liarray_cities_array[4]);
    liarray_cities2 = liarray_cities_array[5].concat(liarray_cities_array[6]).concat(liarray_cities_array[7]).concat(liarray_cities_array[8]).concat(liarray_cities_array[9]);
    liarray_cities3 = liarray_cities_array[10].concat(liarray_cities_array[11]).concat(liarray_cities_array[12]).concat(liarray_cities_array[13]).concat(liarray_cities_array[14]);
    liarray_cities4 = liarray_cities_array[15].concat(liarray_cities_array[16]).concat(liarray_cities_array[17]).concat(liarray_cities_array[18]).concat(liarray_cities_array[19]);
    liarray_cities5 = liarray_cities_array[20].concat(liarray_cities_array[21]).concat(liarray_cities_array[22]).concat(liarray_cities_array[23]).concat(liarray_cities_array[24]).concat(liarray_cities_array[25]);

    list_stations[0] = [liarray_cities_array[0],liarray_cities_array[1],liarray_cities_array[2],liarray_cities_array[3],liarray_cities_array[4]];
    list_stations[1] = [liarray_cities_array[5],liarray_cities_array[6],liarray_cities_array[7],liarray_cities_array[8],liarray_cities_array[9]];
    list_stations[2] = [liarray_cities_array[10],liarray_cities_array[11],liarray_cities_array[12],liarray_cities_array[13],liarray_cities_array[14]];
    list_stations[3] = [liarray_cities_array[15],liarray_cities_array[16],liarray_cities_array[17],liarray_cities_array[18],liarray_cities_array[19]];
    list_stations[4] = [liarray_cities_array[20]/*,liarray_cities_array[21]*/,liarray_cities_array[22],liarray_cities_array[23],liarray_cities_array[24],liarray_cities_array[25]];

    for ( var i = 0; i < array_cities.length; i++) {
        array_cities[i].push(i);
    }
}

Paste_Image.png

這里的 favcityID 似乎和 cookie 有一定的關系 , 不過我們這里暫時不進行登陸 , 這里應該不用特別關注


Paste_Image.png

可以發現 :
array_cities[i][1] == aCityname
array_cities[i][2] == aCidyID // aCidy ? aCity ? 不知道是手誤打錯了還是....

Paste_Image.png

這里第二個字段應該是城市名
第三個字段是城市ID
暫時好像還沒有找到對別的字段的引用 , 那就姑且先分析到這里


現在我們再來看看查詢余票按鈕會發送的 http 請求是什么樣的 :

Paste_Image.png
Paste_Image.png

可以看到 , 這里其實是發送了兩個請求 :

第一個請求的是 : 
https://kyfw.12306.cn/otn/leftTicket/log?
// 根據接口名稱和返回數據可以推測出應該是記錄日志的接口
第二個請求的是 : 
https://kyfw.12306.cn/otn/leftTicket/query?
// 根據接口名稱和返回數據可以推測出應該是真正查詢的接口

我們再來分析一下參數 :

https://kyfw.12306.cn/otn/leftTicket/query?
leftTicketDTO.train_date=2017-02-23&
leftTicketDTO.from_station=BJP&
leftTicketDTO.to_station=SHH&
purpose_codes=ADULT
很簡單 , 四個參數 : 
leftTicketDTO.train_date : 出發日期
leftTicketDTO.from_station : 出發站的代號
leftTicketDTO.to_station : 到達站的代號
purpose_codes : ADULT 表示成人票 , 改變選項為學生票可以發現該參數的值變成了 : 0X00

再看看返回的內容 :

Paste_Image.png

json的數據 , 我們可以根據變量名來大概猜一下這些鍵大概都是什么意思 :

這里為了可讀性 , 將火車的信息刪除到只剩一個
下面的注釋都是根據變量名猜的 , 不一定真正正確
但是話又說回來 , 我們查詢其實可能只關注其中的一些數據 , 并不需要把所有的鍵都搞清楚
不過還是最好搞清楚 , 有助于提高代碼分析能力~
{
    "validateMessagesShowId": "_validatorMessage",
    "status": true,
    "httpstatus": 200,
    "data": [
        {
            "queryLeftNewDTO": {
                "train_no": "24000000G702", # 火車編號
                "station_train_code": "G7", # 火車站的火車編號 ?
                "start_station_telecode": "VNP", # 始發站火車站的電話代碼 ? 還是遠程代碼 ?
                # 這個在 station_names 這個文件中有 , 就是第三個字段
                "start_station_name": "北京南", # 始發站火車站名
                "end_station_telecode": "AOH",
                "end_station_name": "上海虹橋", # 終點站火車站名
                "from_station_telecode": "VNP",
                "from_station_name": "北京南", # 乘客上車的站的名稱
                "to_station_telecode": "AOH",
                "to_station_name": "上海虹橋", # 乘客下車的站的名稱
                "start_time": "19:00", # 開車時間
                "arrive_time": "23:56", # 到達時間
                "day_difference": "0", # 是否跨天到達 ?
                "train_class_name": "", # 火車的類名 ?
                "lishi": "04:56", # lishi ? 歷史 ?
                "canWebBuy": "Y", # 我們能不能買 ?
                "lishiValue": "296", # 歷史值 ?
                "yp_info": "yufrsBuLoo4eUOihUqJNFHJjp09eB27ShkcETr7CgLXSp2qD", 
                # 余票數據 ? 這里如果遇到中文拼音變量名 , 有一個好的云聯想輸入法會很有幫助 :D
                "control_train_day": "20301231",
                "start_train_date": "20170223",
                "seat_feature": "O3M393",
                "yp_ex": "O0M090",
                "train_seat_feature": "3",
                "train_type_code": "2",
                "start_province_code": "31", # 首發站省份編號
                "start_city_code": "0357", # 首發站城市編號
                "end_province_code": "33", # 終點站省份編號
                "end_city_code": "0712", # 終點站城市編號
                "seat_types": "OM9", # 座位類型
                "location_code": "P3",
                "from_station_no": "01", # 乘客上車的站的編號(估計是在這個城市的編號 , 因為要考慮到一個城市多個站的情況) ?
                "to_station_no": "04", # 乘客下車的站的編號
                "control_day": 29,
                "sale_time": "1230",
                "is_support_card": "1", # 是否支持刷卡 ?
                "controlled_train_flag": "0",
                "controlled_train_message": "正常車次,不受控",
                # 下面的估計就是各種作為的余票數 , 當該列車沒有這樣的座位的時候就是 "--"
                # 這里具體哪個是哪個可以通過查詢不同的列車來推測 , 這樣也最快 , 也可以通過閱讀代碼
                "gg_num": "--",
                "gr_num": "--",
                "qt_num": "--",
                "rw_num": "--",
                "rz_num": "--",
                "tz_num": "--",
                "wz_num": "--",
                "yb_num": "--",
                "yw_num": "--",
                "yz_num": "--",
                "ze_num": "有", # 二等座
                "zy_num": "10", # 一等座
                "swz_num": "8" # 商務座
            },
            # 秘密的字符串 , 暫時還不知道 , 但是這個對我們查詢余票并沒有影響 , 我們現在已經可以解析余票的數據了
            # 這個字段應該是在購票的時候會用到 , 暫時先不分析
            "secretStr": "ECM6UXA86obwvu3av7Tmh%2BLNXyfi8vGfR1X%2BkTrDYcvjzpdLjFYUVuYLiLR8ifoxpyot3PM528xO%0A8iTmnvT7yKjUduPszD1BtH8xNzetGrb6aGO%2FEV1HNQ1aXscPoZhFjBwle1jIFS78FxMiD1Ch9yIt%0A84qJZdXFhTYrgeD5DuBN3UAg291pgwrcbo0eMnBlJSFQNcAK%2FlkcrbohuCbeqj8Xn8qjvFssZv5L%0AcgJnsXmLe9ZqHLUoGGLUUW2GpsIi%2BVMehOiIV8XmXnd2cvcEperf4neI2tkFk0gn%2BsDpkhGvNjSK%0AaG0OW98ByPk%3D",
            "buttonTextInfo": "預訂"
        }
    ]
}
QQ截圖20170223180323.png

總結和預告 :

好了 , 分析到這里已經差不多了 , 下節課我們來寫代碼進行真正的查詢 , 謝謝大家的支持~
特別希望能和大家交流思路 , 希望共同進步 , 有什么問題就隨便提哦

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,117評論 6 537
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,860評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 177,128評論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,291評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,025評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,421評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,477評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,642評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,177評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,970評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,157評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,717評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,410評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,821評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,053評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,896評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,157評論 2 375

推薦閱讀更多精彩內容