用Node.js做一个火车票查询小工具

2024-05-05 16:38
文章标签 工具 查询 js node 火车票

本文主要是介绍用Node.js做一个火车票查询小工具,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

github地址: https://github.com/KKDestiny/TrainTicket12306.git
演示地址:http://tickets.onelib.biz/


TrainTicket12306

这是一款用于在铁道部12306官网爬取车票等信息的node.js应用。

An app to query Tickects and other information from 12306 website by node.js

8.jpg

9.jpg


master branch

master分支为最新功能,所有功能都会最终同步到这里。


element branch

element分支为所有功能的原型


1.安装 Installation

  • 如果直接使用模块TrainTickects,则不需要任何依赖,直接引用此模块即可。
    Require TrainTickects directly if you just want to use TrainTickects module.

  • 如果你要查看网页Demo,则需要安装,直接执行 npm install 即可
    Execute npm 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
实现网页版小工具
+ 可以输入中文站名、日期,从而查询火车
+ 点击结果列表还可以查看本次列车的途经站点信息

V1.1.0

2017/07/21 14:00 完善网页版功能
+ 输入出发地、目的地时,出现下来选项,不必输入完整的名称;拼音(大小写均可)和中文均支持
+ 到达时间与出发时间不在同一天时,会给出提示,“+1”、“+2”或更多
+ 新增过滤功能:过滤车次、站名
效果


4.文档 Documents

4.1 12306接口 Interface

请求方式为:
Request:
https

Host:
kyfw.12306.cn/

查询前缀为:
Query Prefix
/otn/

已知的几个接口有:
Known interfaces as follows:

InterfacenameruleDemo
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 模块提供的接口,需要传入两个参数:configcallback

其中 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)
});

执行效果:
upload.png


4.3 OL_TrainTickects.QueryStations()

用于查询某车次的停靠站点及到站、发车和停靠时间。

此接口为 TrainTickects 模块提供的接口,需要传入两个参数:configcallback

其中 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)
});

执行效果:
upload.png


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));
})

执行效果:

upload.png


4.5 OL_TrainTickects.QueryPrice()

查询火车票价格。

此接口为 TrainTickects 模块提供的接口,需要传入两个参数:configcallback

其中 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.resultmap

其中,data.result 是一个数组,数组的每一个元素都代表一个符合条件的车次,包含了车次、起止站、各坐席票数等信息。

map是一个对象,属性名为火车站的代码,属性值为该代码对应的中文站名。

map的作用在于,当我们查询 深圳厦门 的火车票时,可能出现 深圳北厦门北 这样的车站,它们和 深圳厦门不同,需要做好对应关系。

关于如何使用这个映射关系,在5.3节会做介绍。


5.2 关于余票信息的数据处理

从12306获取到的每一个车次的余票信息的原始数据如下:

upload.png

可以看出,这些数据是以 | 为分割的,因此可以通过 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 Time
tickect.eTime = temp[9];    // End Time
tickect.tTime = temp[10];   // Total Time
tickect.date = temp[13];...

那么,剩余的一些字段该如何解读呢?

首先,我们看到了一些位置出现了 这个数值,但这并不足以让我们知道这些位置代表什么数据;我们看下面图中,一等座、二等座和无座三类席位都是 ,我们仅仅能知道第26 30 31个位置代表一等座、二等座和无座,并且顺序也不清楚。

upload.png


这里提供一种比较笨的方法:大量查找不同起止站的列车,推理出各类坐席剩余票数在原始数据中的位置。

举个例子,我们在12306官网中查询一下深圳-郑州的列车:
upload.png

可以知道商务座、一二等座的票数分别为 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官网找一个例子:深圳-梅州的列车只有一趟,很好分析:
upload.png

所以我们可以更改一下查询条件:惠州-兴宁,结果为:

// {HCQ: "惠州", ENQ: "兴宁"}/* 4 */  'SZQ',
/* 5 */  'MOQ',
/* 6 */  'HCQ',
/* 7 */  'ENQ',

因此可以确定,56为此列车的始发站和终点站,67为实际的出发站和到达站。

由此,我们也可以根据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 Name
tickect.tSation = TMapStations(temp[7], map);   // To Station Name...

5.4 出发地和目的地车序以及seat_types

在获取车票价格的请求中,我们可以看到有至少3个字段:

upload.png

  • from_station_no : 01
  • to_station_no : 23
  • seat_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_noto_station_noseat_types


余票返回数据格式总结

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 - ?(canWebBuy) */  'Y',
/* 12 - ?(yp_info) */  'VYa0L6Eo5FxIlzIT86a%2BZ1aAkg1iGV%2FwY8DzVKRX2iKk0iN%2B',
/* 13 - 乘车日期 */  '20170724',
/* 14 - ?(train_seat_feature) */  '3',
/* 15 - ?(location_code) */  'Q6',
/* 16 - 出发地车序 */  '01',
/* 17 - 到达地车序 */  '07',
/* 18 - ?(is_support_card) */  '1',
/* 19 - ?(controlled_train_flag) */  '0',
/* 20 - ? */  '',
/* 21 - ? */  '',
/* 22 - ? */  '',
/* 23 - 软卧 */  '',
/* 24 - 软座 */  '',
/* 25 - ? */  '',
/* 26 - 无座 */  '有',
/* 27 - ? */  '',
/* 28 - 硬卧 */  '',
/* 29 - 硬座 */  '',
/* 30 - 二等座 */  '有',
/* 31 - 一等座 */  '有',
/* 32 - 商务座 / 特等座 */  '',
/* 33 - 动卧 */  '',
/* 34 - ?(yp_ex) */  'O0M0O0',
/* 35 - seat_type */  'OMO' ]

感谢

感谢 落花落雨不落叶 的博文提供的帮助!
http://www.cnblogs.com/hongrunhui/p/6284192.html

这篇关于用Node.js做一个火车票查询小工具的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/962156

相关文章

Python实现微信自动锁定工具

《Python实现微信自动锁定工具》在数字化办公时代,微信已成为职场沟通的重要工具,但临时离开时忘记锁屏可能导致敏感信息泄露,下面我们就来看看如何使用Python打造一个微信自动锁定工具吧... 目录引言:当微信隐私遇到自动化守护效果展示核心功能全景图技术亮点深度解析1. 无操作检测引擎2. 微信路径智能获

MyBatis模糊查询报错:ParserException: not supported.pos 问题解决

《MyBatis模糊查询报错:ParserException:notsupported.pos问题解决》本文主要介绍了MyBatis模糊查询报错:ParserException:notsuppo... 目录问题描述问题根源错误SQL解析逻辑深层原因分析三种解决方案方案一:使用CONCAT函数(推荐)方案二:

Java中的工具类命名方法

《Java中的工具类命名方法》:本文主要介绍Java中的工具类究竟如何命名,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录Java中的工具类究竟如何命名?先来几个例子几种命名方式的比较到底如何命名 ?总结Java中的工具类究竟如何命名?先来几个例子JD

MySQL 中的 JSON 查询案例详解

《MySQL中的JSON查询案例详解》:本文主要介绍MySQL的JSON查询的相关知识,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录mysql 的 jsON 路径格式基本结构路径组件详解特殊语法元素实际示例简单路径复杂路径简写操作符注意MySQL 的 J

Go语言开发实现查询IP信息的MCP服务器

《Go语言开发实现查询IP信息的MCP服务器》随着MCP的快速普及和广泛应用,MCP服务器也层出不穷,本文将详细介绍如何在Go语言中使用go-mcp库来开发一个查询IP信息的MCP... 目录前言mcp-ip-geo 服务器目录结构说明查询 IP 信息功能实现工具实现工具管理查询单个 IP 信息工具的实现服

MySQL索引的优化之LIKE模糊查询功能实现

《MySQL索引的优化之LIKE模糊查询功能实现》:本文主要介绍MySQL索引的优化之LIKE模糊查询功能实现,本文通过示例代码给大家介绍的非常详细,感兴趣的朋友一起看看吧... 目录一、前缀匹配优化二、后缀匹配优化三、中间匹配优化四、覆盖索引优化五、减少查询范围六、避免通配符开头七、使用外部搜索引擎八、分

MySql match against工具详细用法

《MySqlmatchagainst工具详细用法》在MySQL中,MATCH……AGAINST是全文索引(Full-Textindex)的查询语法,它允许你对文本进行高效的全文搜素,支持自然语言搜... 目录一、全文索引的基本概念二、创建全文索引三、自然语言搜索四、布尔搜索五、相关性排序六、全文索引的限制七

基于Java实现回调监听工具类

《基于Java实现回调监听工具类》这篇文章主要为大家详细介绍了如何基于Java实现一个回调监听工具类,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录监听接口类 Listenable实际用法打印结果首先,会用到 函数式接口 Consumer, 通过这个可以解耦回调方法,下面先写一个

使用Python构建一个Hexo博客发布工具

《使用Python构建一个Hexo博客发布工具》虽然Hexo的命令行工具非常强大,但对于日常的博客撰写和发布过程,我总觉得缺少一个直观的图形界面来简化操作,下面我们就来看看如何使用Python构建一个... 目录引言Hexo博客系统简介设计需求技术选择代码实现主框架界面设计核心功能实现1. 发布文章2. 加

JS+HTML实现在线图片水印添加工具

《JS+HTML实现在线图片水印添加工具》在社交媒体和内容创作日益频繁的今天,如何保护原创内容、展示品牌身份成了一个不得不面对的问题,本文将实现一个完全基于HTML+CSS构建的现代化图片水印在线工具... 目录概述功能亮点使用方法技术解析延伸思考运行效果项目源码下载总结概述在社交媒体和内容创作日益频繁的