12306搶票腳本開(kāi)發(fā)(二)解析火車站代號(hào)并分析查詢的HTTP請(qǐng)求


文章地址 :

12306搶票腳本開(kāi)發(fā)(一)提綱
12306搶票腳本開(kāi)發(fā)(二)解析火車站代號(hào)并分析查詢的HTTP請(qǐng)求
12306搶票腳本開(kāi)發(fā)(三)實(shí)現(xiàn)一個(gè)簡(jiǎn)單的查詢腳本
12306搶票腳本開(kāi)發(fā)(四)完善上節(jié)課的代碼并面向?qū)ο?/a>
12306搶票腳本開(kāi)發(fā)(五)更友好的使用方式
12306搶票腳本開(kāi)發(fā)(六)更友好的時(shí)間輸入方式
12306搶票腳本開(kāi)發(fā)(七)將前幾節(jié)課的成果結(jié)合起來(lái)實(shí)現(xiàn)一個(gè)完整的工具


簡(jiǎn)介 :

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

Paste_Image.png

可以看到這個(gè)文件其實(shí)是定義了一個(gè) js 的變量
這個(gè)變量好像是有一定的格式
可以很容易就分析這里應(yīng)該是以 '@' 這個(gè)字符來(lái)分隔每一個(gè)火車站的
然后又以 '|' 這個(gè)字符來(lái)分隔每一個(gè)火車站中的數(shù)據(jù) , 這些數(shù)據(jù)具體是干什么的我們現(xiàn)在還說(shuō)不準(zhǔn)
這個(gè)要通過(guò)分析 12306 網(wǎng)站的 js 代碼才能得知
那么我們應(yīng)該如何來(lái)分析這個(gè)變量到底是如何被解析 , 每一個(gè)字段都有什么用呢 ?
我們首先來(lái)下載 12306 網(wǎng)站的代碼 :

可以使用 wget 來(lái)遞歸下載整個(gè)網(wǎng)站
wget -r https://kyfw.12306.cn/otn/

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

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

結(jié)果如下 :

Paste_Image.png

哈 , 我們已經(jīng)可以定位到一個(gè)具體的文件的具體行了 :

./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('@');

那么解析的工作肯定是在這個(gè)文件中完成的
來(lái)看看這個(gè)文件吧 :

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

    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 有一定的關(guān)系 , 不過(guò)我們這里暫時(shí)不進(jìn)行登陸 , 這里應(yīng)該不用特別關(guān)注


Paste_Image.png

可以發(fā)現(xiàn) :
array_cities[i][1] == aCityname
array_cities[i][2] == aCidyID // aCidy ? aCity ? 不知道是手誤打錯(cuò)了還是....

Paste_Image.png

這里第二個(gè)字段應(yīng)該是城市名
第三個(gè)字段是城市ID
暫時(shí)好像還沒(méi)有找到對(duì)別的字段的引用 , 那就姑且先分析到這里


現(xiàn)在我們?cè)賮?lái)看看查詢余票按鈕會(huì)發(fā)送的 http 請(qǐng)求是什么樣的 :

Paste_Image.png
Paste_Image.png

可以看到 , 這里其實(shí)是發(fā)送了兩個(gè)請(qǐng)求 :

第一個(gè)請(qǐng)求的是 : 
https://kyfw.12306.cn/otn/leftTicket/log?
// 根據(jù)接口名稱和返回?cái)?shù)據(jù)可以推測(cè)出應(yīng)該是記錄日志的接口
第二個(gè)請(qǐng)求的是 : 
https://kyfw.12306.cn/otn/leftTicket/query?
// 根據(jù)接口名稱和返回?cái)?shù)據(jù)可以推測(cè)出應(yīng)該是真正查詢的接口

我們?cè)賮?lái)分析一下參數(shù) :

https://kyfw.12306.cn/otn/leftTicket/query?
leftTicketDTO.train_date=2017-02-23&
leftTicketDTO.from_station=BJP&
leftTicketDTO.to_station=SHH&
purpose_codes=ADULT
很簡(jiǎn)單 , 四個(gè)參數(shù) : 
leftTicketDTO.train_date : 出發(fā)日期
leftTicketDTO.from_station : 出發(fā)站的代號(hào)
leftTicketDTO.to_station : 到達(dá)站的代號(hào)
purpose_codes : ADULT 表示成人票 , 改變選項(xiàng)為學(xué)生票可以發(fā)現(xiàn)該參數(shù)的值變成了 : 0X00

再看看返回的內(nèi)容 :

Paste_Image.png

json的數(shù)據(jù) , 我們可以根據(jù)變量名來(lái)大概猜一下這些鍵大概都是什么意思 :

這里為了可讀性 , 將火車的信息刪除到只剩一個(gè)
下面的注釋都是根據(jù)變量名猜的 , 不一定真正正確
但是話又說(shuō)回來(lái) , 我們查詢其實(shí)可能只關(guān)注其中的一些數(shù)據(jù) , 并不需要把所有的鍵都搞清楚
不過(guò)還是最好搞清楚 , 有助于提高代碼分析能力~
{
    "validateMessagesShowId": "_validatorMessage",
    "status": true,
    "httpstatus": 200,
    "data": [
        {
            "queryLeftNewDTO": {
                "train_no": "24000000G702", # 火車編號(hào)
                "station_train_code": "G7", # 火車站的火車編號(hào) ?
                "start_station_telecode": "VNP", # 始發(fā)站火車站的電話代碼 ? 還是遠(yuǎn)程代碼 ?
                # 這個(gè)在 station_names 這個(gè)文件中有 , 就是第三個(gè)字段
                "start_station_name": "北京南", # 始發(fā)站火車站名
                "end_station_telecode": "AOH",
                "end_station_name": "上海虹橋", # 終點(diǎn)站火車站名
                "from_station_telecode": "VNP",
                "from_station_name": "北京南", # 乘客上車的站的名稱
                "to_station_telecode": "AOH",
                "to_station_name": "上海虹橋", # 乘客下車的站的名稱
                "start_time": "19:00", # 開(kāi)車時(shí)間
                "arrive_time": "23:56", # 到達(dá)時(shí)間
                "day_difference": "0", # 是否跨天到達(dá) ?
                "train_class_name": "", # 火車的類名 ?
                "lishi": "04:56", # lishi ? 歷史 ?
                "canWebBuy": "Y", # 我們能不能買 ?
                "lishiValue": "296", # 歷史值 ?
                "yp_info": "yufrsBuLoo4eUOihUqJNFHJjp09eB27ShkcETr7CgLXSp2qD", 
                # 余票數(shù)據(jù) ? 這里如果遇到中文拼音變量名 , 有一個(gè)好的云聯(lián)想輸入法會(huì)很有幫助 :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", # 首發(fā)站省份編號(hào)
                "start_city_code": "0357", # 首發(fā)站城市編號(hào)
                "end_province_code": "33", # 終點(diǎn)站省份編號(hào)
                "end_city_code": "0712", # 終點(diǎn)站城市編號(hào)
                "seat_types": "OM9", # 座位類型
                "location_code": "P3",
                "from_station_no": "01", # 乘客上車的站的編號(hào)(估計(jì)是在這個(gè)城市的編號(hào) , 因?yàn)橐紤]到一個(gè)城市多個(gè)站的情況) ?
                "to_station_no": "04", # 乘客下車的站的編號(hào)
                "control_day": 29,
                "sale_time": "1230",
                "is_support_card": "1", # 是否支持刷卡 ?
                "controlled_train_flag": "0",
                "controlled_train_message": "正常車次垦写,不受控",
                # 下面的估計(jì)就是各種作為的余票數(shù) , 當(dāng)該列車沒(méi)有這樣的座位的時(shí)候就是 "--"
                # 這里具體哪個(gè)是哪個(gè)可以通過(guò)查詢不同的列車來(lái)推測(cè) , 這樣也最快 , 也可以通過(guò)閱讀代碼
                "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" # 商務(wù)座
            },
            # 秘密的字符串 , 暫時(shí)還不知道 , 但是這個(gè)對(duì)我們查詢余票并沒(méi)有影響 , 我們現(xiàn)在已經(jīng)可以解析余票的數(shù)據(jù)了
            # 這個(gè)字段應(yīng)該是在購(gòu)票的時(shí)候會(huì)用到 , 暫時(shí)先不分析
            "secretStr": "ECM6UXA86obwvu3av7Tmh%2BLNXyfi8vGfR1X%2BkTrDYcvjzpdLjFYUVuYLiLR8ifoxpyot3PM528xO%0A8iTmnvT7yKjUduPszD1BtH8xNzetGrb6aGO%2FEV1HNQ1aXscPoZhFjBwle1jIFS78FxMiD1Ch9yIt%0A84qJZdXFhTYrgeD5DuBN3UAg291pgwrcbo0eMnBlJSFQNcAK%2FlkcrbohuCbeqj8Xn8qjvFssZv5L%0AcgJnsXmLe9ZqHLUoGGLUUW2GpsIi%2BVMehOiIV8XmXnd2cvcEperf4neI2tkFk0gn%2BsDpkhGvNjSK%0AaG0OW98ByPk%3D",
            "buttonTextInfo": "預(yù)訂"
        }
    ]
}
QQ截圖20170223180323.png

總結(jié)和預(yù)告 :

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子同诫,更是在濱河造成了極大的恐慌篙悯,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,482評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異噪沙,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)吐根,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門正歼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人拷橘,你說(shuō)我怎么就攤上這事局义。” “怎么了冗疮?”我有些...
    開(kāi)封第一講書人閱讀 152,762評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵萄唇,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我术幔,道長(zhǎng)另萤,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 55,273評(píng)論 1 279
  • 正文 為了忘掉前任诅挑,我火速辦了婚禮四敞,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘拔妥。我一直安慰自己忿危,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評(píng)論 5 373
  • 文/花漫 我一把揭開(kāi)白布没龙。 她就那樣靜靜地躺著铺厨,像睡著了一般缎玫。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上解滓,一...
    開(kāi)封第一講書人閱讀 49,046評(píng)論 1 285
  • 那天碘梢,我揣著相機(jī)與錄音,去河邊找鬼伐蒂。 笑死煞躬,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的逸邦。 我是一名探鬼主播恩沛,決...
    沈念sama閱讀 38,351評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼缕减!你這毒婦竟也來(lái)了雷客?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 36,988評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤桥狡,失蹤者是張志新(化名)和其女友劉穎搅裙,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體裹芝,經(jīng)...
    沈念sama閱讀 43,476評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡部逮,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評(píng)論 2 324
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了嫂易。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片兄朋。...
    茶點(diǎn)故事閱讀 38,064評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖怜械,靈堂內(nèi)的尸體忽然破棺而出颅和,到底是詐尸還是另有隱情,我是刑警寧澤缕允,帶...
    沈念sama閱讀 33,712評(píng)論 4 323
  • 正文 年R本政府宣布峡扩,位于F島的核電站,受9級(jí)特大地震影響障本,放射性物質(zhì)發(fā)生泄漏教届。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評(píng)論 3 307
  • 文/蒙蒙 一彼绷、第九天 我趴在偏房一處隱蔽的房頂上張望巍佑。 院中可真熱鬧,春花似錦寄悯、人聲如沸萤衰。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,264評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)脆栋。三九已至倦卖,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間椿争,已是汗流浹背怕膛。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,486評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留秦踪,地道東北人褐捻。 一個(gè)月前我還...
    沈念sama閱讀 45,511評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像椅邓,于是被迫代替她去往敵國(guó)和親柠逞。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容