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阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容