github地址: https://github.com/KKDestiny/TrainTicket12306.git
演示地址(Demo Url):http://tickets.onelib.biz/
TrainTicket12306
这是一款用于在铁道部12306官网爬取车票等信息的node.js应用。
An app to query Tickects and other information from 12306 website by node.js
火车票查询
途经站查询
master branch
master分支为最新功能,所有功能都会最终同步到这里。
element branch
element分支为所有功能的原型
1.安装 Installation
如果直接使用模块
TrainTickects
,则不需要任何依赖,直接引用此模块即可。
RequireTrainTickects
directly if you just want to useTrainTickects
module.如果你要查看网页Demo,则需要安装,直接执行
npm install
即可
Executenpm install
to install modules needed automaticly if you want to check website demo.
2.文件结构 FileStructure
主要文件结构如下:
Main Structure:
root |---TrainTickects.js // TrainTickects Module |---test.js // Debug file, you can use this file to test // each function in TrainTickects module |---package.json // Dependences |---cert |---srca_der.cer // Certification
3.更新 Updates
V0.0.1
首次提交于 2017/07/20,制作了 TrainTickects
模块,实现了三个简单功能:
查询火车票
查询中途停靠站列表
收集所有火车站数据
V1.0.0
2017/07/20 18:38
实现网页版小工具
可以输入中文站名、日期,从而查询火车
点击结果列表还可以查看本次列车的途经站点信息
4.文档 Documents
4.1 12306接口 Interface
请求方式为:
Request:https
Host:kyfw.12306.cn/
查询前缀为:
Query Prefix/otn/
已知的几个接口有:
Known interfaces as follows:
Interface | name | rule | Demo |
---|---|---|---|
leftTicket/query | 查询符合条件的车票 Query tickets that satisfy query conditions | leftTicketDTO.train_date=<date> &leftTicketDTO.from_station= <from_station> &leftTicketDTO.to_station= <to_station> &purpose_codes=ADULT | leftTicketDTO.train_date=2017-07-21 &leftTicketDTO.from_station= SZQ &leftTicketDTO.to_station= XMS &purpose_codes=ADULT |
czxx/queryByTrainNo | 查询某车次的途经站及时间 Query the stations&time of a train passing by | train_no=<train_no> &from_station_telecode= <from_station_telecode> &to_station_telecode= <to_station_telecode> &depart_date= <depart_date> | train_no=6i000D40920A &from_station_telecode= SZQ &to_station_telecode= XMS &depart_date= 2017-07-21 |
resources/js/framework/station_name.js | 获取所有火车站数据 Collection all stations data | station_version=<version> | station_version=1.8964 |
4.2 OL_TrainTickects.QueryTickects()
查询符合条件的车次、各类车票的剩余数量。
此接口为 TrainTickects
模块提供的接口,需要传入两个参数:config
和 callback
。
其中 config
为对象,其属性包括:
date
: String, 日期, 格式"yyyy-mm-dd"from_station
: String, 始发站车站代码,如"SZQ"end_station
: String, 终到站车站代码,如"XMS"print
: boolean, 是否后台打印
callback
为回调函数,允许为空。
下面为一个简单的测试例子:
var OL_TrainTickects = require('./TrainTickects');// 测试查询火车票var Config = { date : '2017-07-24', // 日期, 格式"yyyy-mm-dd" from_station : 'SZQ', // 始发站车站代码,这里是深圳 end_station : 'XMS', // 终到站车站代码,这里是厦门 print : true, // 打印查询结果 }; OL_TrainTickects.QueryTickects(Config, function(err, tickects) { // console.log(tickects)});
执行效果:
车票查询结果
4.3 OL_TrainTickects.QueryStations()
用于查询某车次的停靠站点及到站、发车和停靠时间。
此接口为 TrainTickects
模块提供的接口,需要传入两个参数:config
和 callback
。
其中 config
为对象,其属性包括:
train_no
: String, 列车编号, 如"6i000D40920A"from_station
: String, 始发站车站代码,如"SZQ"end_station
: String, 终到站车站代码,如"XMS"date
: String, 日期, 格式"yyyy-mm-dd"print
: boolean, 是否后台打印
callback
为回调函数,允许为空。
下面为一个简单的测试例子:
var OL_TrainTickects = require('./TrainTickects');// 测试查询中途停靠站列表var Config = { train_no : '6i000D40920A', // 列车编号 from_station : 'SZQ', // 始发站车站代码 end_station : 'XMS', // 终到站车站代码 date : '2017-07-24', // 日期, 格式"yyyy-mm-dd" print : true, // 是否打印 }; OL_TrainTickects.QueryStations(Config, function(err, sList) { // console.log(sList)});
执行效果:
途经站查询结果
4.4 OL_TrainTickects.CollectStations()
手机全国所有火车站的数据。
此接口为 TrainTickects
模块提供的接口,只需要传入回调函数callback
即可,允许为空。
下面为一个简单的测试例子:
var OL_TrainTickects = require('./TrainTickects');// 测试收集所有火车站数据OL_TrainTickects.CollectStations(function(err, data) { var fs = require('fs'); // 写入js文件 var dir = "./js/" if(!fs.existsSync(dir)) { fs.mkdirSync(dir); } fs.writeFile(dir+'stations_py.js', "var StationData = "+JSON.stringify(data)); })
执行效果:
生成全国火车站数据
4.5 OL_TrainTickects.QueryPrice()
查询火车票价格。
此接口为 TrainTickects
模块提供的接口,需要传入两个参数:config
和 callback
。
其中 config
为对象,其属性包括:
train_no
: String, 列车编号, 如"650000Z23001"from_station_no
: String, 出发地车序,如"01"to_station_no
: String, 目的地车序,如"23"seat_types
: String, 如"113"train_date
: String, 乘车日期,如"2017-07-23"print
: boolean, 是否后台打印
callback
为回调函数,允许为空。
下面为一个简单的测试例子:
// 测试查询票价var Config = { train_no : '650000Z23001', // 列车编号 from_station_no : '01', // 出发地车序 to_station_no : '23', // 目的地车序 seat_types : '113', // 如"113" train_date : '2017-07-23', // 日期, 格式"yyyy-mm-dd" }; OL_TrainTickects.QueryPrice(Config, function(err, tickects) { console.log(tickects) });
5.一些问题
5.1 余票查询的返回数据
从12306获取到的原始数据结构如下:
{ validateMessagesShowId: '_validatorMessage', status: true, httpstatus: 200, data: { result: [ '3ec5X9fRSYq5hiOeUulIj%2F6TbXHRQBiBhbLIFmE0GQj8rPSTYtSYZl6KDzSfChayvwsPRbgom0Fc%0Afi61kzfkhRhOqB6nkxQCUwijG2opA6FpNXyvejLtqlVrbqTiTVZMukqyucS5ldEcSsttYhaiTEdq%0ADS3WuQw4FUd8LazFoDT5eVXtAfyaNuuMtD9VR5dJKNAyMMBDI8T%2FeCJu0pPMIpxF%2FY%2FkWAK%2FANmK%0AqEKHximPszTr|预订|6i000D40920A|D4092|IOQ|XKS|IOQ|XKS|06:40|10:16|03:36|Y|yx1mCzhcHlabqde2UZwX9Y8XCtPt2nDYQaY%2BBGBmT33wpoaD|20170724|3|Q6|01|07|1|0|||||||有||||有|有|||O0M0O0|OMO', ... ], flag: '1', map: { XMS: '厦门', IOQ: '深圳北', XKS: '厦门北' }, messages: [], validateMessages: {} }
对我们来说,有意义的数据只要两个:data.result
和 map
。
其中,data.result
是一个数组,数组的每一个元素都代表一个符合条件的车次,包含了车次、起止站、各坐席票数等信息。
map
是一个对象,属性名为火车站的代码,属性值为该代码对应的中文站名。
map
的作用在于,当我们查询 深圳
到 厦门
的火车票时,可能出现 深圳北
、厦门北
这样的车站,它们和 深圳
到 厦门
不同,需要做好对应关系。
关于如何使用这个映射关系,在5.3节会做介绍。
5.2 关于余票信息的数据处理
从12306获取到的每一个车次的余票信息的原始数据如下:
原始数据
可以看出,这些数据是以 |
为分割的,因此可以通过 split()
函数来切分原始数据:
arr = raw_data.split("|");// Result[ /* 0 */ 'uR7MTk54pjLluluefRzSndjJsuDNdhtgJK6caO246PF7XGyVDML5aJ6EgQZRE7yXyONObC8q4E5c%0ASfuGXEljez5PZuzJlFASnSuluBeLTzL06LR18feZ3AtLCG%2FjqeqBks3tpY168pxxeGdkPOr0rakC%0AhJ6gEXT%2Bbo0OSKVXn9fWm3pXsU16O9tZBcbD4LGLtUlynzNxM%2FB%2BN0rRNodacPi3VHx5wMIpgeaH%0AIF%2FkfW2ySCl1',/* 1 */ '预订',/* 2 */ '6i000D40920A',/* 3 */ 'D4092',/* 4 */ 'IOQ',/* 5 */ 'XKS',/* 6 */ 'IOQ',/* 7 */ 'XKS',/* 8 */ '06:40',/* 9 */ '10:16',/* 10 */ '03:36',/* 11 */ 'Y',/* 12 */ 'VYa0L6Eo5FxIlzIT86a%2BZ1aAkg1iGV%2FwY8DzVKRX2iKk0iN%2B',/* 13 */ '20170724',/* 14 */ '3',/* 15 */ 'Q6',/* 16 */ '01',/* 17 */ '07',/* 18 */ '1',/* 19 */ '0',/* 20 */ '',/* 21 */ '',/* 22 */ '',/* 23 */ '',/* 24 */ '',/* 25 */ '',/* 26 */ '有',/* 27 */ '',/* 28 */ '',/* 29 */ '',/* 30 */ '有',/* 31 */ '有',/* 32 */ '',/* 33 */ '',/* 34 */ 'O0M0O0',/* 35 */ 'OMO' ]
可以看出,每个元素都严格对应一个数据;但是由于缺少文档,我不清楚每个位置代表什么意思。
不过,根据我们已知的情况, arr[3]
肯定是车次, arr[8]
肯定是发车时间, arr[9]
肯定是到达时间, arr[10]
肯定是总时间, arr[13]
肯定是乘车日期。
另外,根据后来做 OL_TrainTickects.QueryStations()
的时候分析其接口时可以知道:arr[2]
肯定是列车编号,这个编号可以用在后面查询某车次中途停靠站信息中。
因此源代码中,是这样处理的:
... tickect.train_no = temp[2]; // 火车编号tickect.tId = temp[3]; // Train IDtickect.sTime = temp[8]; // Start Timetickect.eTime = temp[9]; // End Timetickect.tTime = temp[10]; // Total Timetickect.date = temp[13]; ...
那么,剩余的一些字段该如何解读呢?
首先,我们看到了一些位置出现了 有
这个数值,但这并不足以让我们知道这些位置代表什么数据;我们看下面图中,一等座、二等座和无座三类席位都是 有
,我们仅仅能知道第26
30
31
个位置代表一等座、二等座和无座,并且顺序也不清楚。
12306官网数据
这里提供一种比较笨的方法:大量查找不同起止站的列车,推理出各类坐席剩余票数在原始数据中的位置。
举个例子,我们在12306官网中查询一下深圳
-郑州
的列车:
对应原始数据
可以知道商务座、一二等座的票数分别为 16
12
和 有
。那么我们同时查看余票的原始数据:
[ '9IQVfT3yCLkzmM1ZghLmyzwq%2Fn4vNliAfmnnwzZ01FtloWX5j1WC%2B2eLlKI7MhrbrUUwKKRc9zh7%0APzQNPa6qbZxP0F 9Sm4zin2w4d8sWSsB6ZlL8anu7LciQyVlV6XPMZqEMgcRW5T6tcH2MOmxuLsxH%0AE5L%2BJd8FmD029yCje%2F%2Bwir7yHyLyA M7oElbTsa2c5C%2BfouxQU1V6lb1bcL%2FueyAUsI1qd0OObjT2%0Afg%3D%3D', '预订', '6i00000G7210', 'G72', 'NZQ', 'BXP', 'NZQ', 'ZAF', '07:43', '14:56', '07:13', 'Y', 'UM2Ja13aKKsc6qxZtVDIqImzPYBW8yoFun0JxcFfLjb8BFPl', '20170724', '3', 'Q6', '01', '10', '0', '0', '', '', '', '', '', '', '', '', '', '', /* 30 */ '有', /* 31 */ '12', /* 32 */ '16', '', 'O0M090', 'OM9' ]
这样,很明显arr[30]
代表的是二等座,arr[31]
代表的是一等座,arr[32]
商务座。
用同样的方式,再查找其他车次的车票数,只要不全是相同的值,我们就能够通过对比这两个信息得到各个位置代表什么坐席。
根据多次尝试,得到以下结果(2017.07.20):
tickect.ruanwo = temp[23]; // 软卧tickect.ruanzuo = temp[24]; // 软座tickect.wuzuo = temp[26]; // 无座tickect.yingwo = temp[28]; // 硬卧tickect.yingzuo = temp[29]; // 硬座tickect.scSeat = temp[30]; // 二等座tickect.fcSeat = temp[31]; // 一等座tickect.bcSeat = temp[32]; // 商务座 / 特等座tickect.dongwo = temp[33]; // 动卧
5.3 始发站/终点站 与 出发站/到达站
5.1中的余票数据中,有四个站点代码:
/* 4 */ 'IOQ',/* 5 */ 'XKS',/* 6 */ 'IOQ',/* 7 */ 'XKS',
发现4和6、5和7是一样的代码。原因很简单,因为我们出发的火车站就是始发站、到达的火车站就是终点站。
那么到底前两个是始发站/终点站,还是后两个是?我们可以在12306官网找一个例子:深圳
-梅州
的列车只有一趟,很好分析:
12306官网信息
所以我们可以更改一下查询条件:惠州
-兴宁
,结果为:
// {HCQ: "惠州", ENQ: "兴宁"}/* 4 */ 'SZQ',/* 5 */ 'MOQ',/* 6 */ 'HCQ',/* 7 */ 'ENQ',
因此可以确定,5
和6
为此列车的始发站和终点站,6
和7
为实际的出发站和到达站。
由此,我们也可以根据map
信息来得到火车站代码对应的中文名:
/* * 根据车站code转换为中文名称 */function TMapStations(code, map) { if(map) { var name = map[code]; if(name) { return name } } return code; } ...// 解析余票信息部分代码tickect.fSation = TMapStations(temp[6], map); // From Station Nametickect.tSation = TMapStations(temp[7], map); // To Station Name...
5.4 出发地和目的地车序以及seat_types
在获取车票价格的请求中,我们可以看到有至少3个字段:
http请求头
from_station_no
: 01to_station_no
: 23seat_types
: 113
那么这三个数据是从哪里来的呢?
我们先看一下获取到的车票的原始数据:
[ "8ug8apI2e0XX%2BolsYZc4z%2FDkjvClldv5Bg8q%2BZq9xkYTNdJQdu1X4NIxb2hWi7oSF%2FVMsfRnDUoV%0AtgGs1o9VzGfjN8Q4VUTKusWMtiwidqHG9lTBpTnN09xV6LOWlC0FK3ZicLhxc4E%2FwFNuCnlQUfhG%0Af9AM%2BdkPfPy755xwhHwVZz9rdjCJOjf8tAom4hJb27XWdlvZo2viDjCwfhYMkTbCSE6b0eV0XDhH%0AhdN1Z1Q%3D", "预订", "650000Z23001", "Z230", "SZQ","WAR", "SZQ", "WAR", "09:30", "10:33", "47:14", "Y", "4Qoh7OTkPpObyF7cv4441dPVJFE7XBu06AVmiPYDitUlwBcb", "20170723", "3", "Q7", /* 16 */ "01", /* 17 */ "23", "0", "0", "", "", "", "", "", "", "有","", "无", "无", "", "", "", "", "101030", /* 35 */ "113"]
可以知道,第16位、17位、35位分别代表了 from_station_no
、to_station_no
和seat_types
作者:Mike的读书季
链接:https://www.jianshu.com/p/aea9455227c1