php实现查询火车票

  闲来无事,做一个好玩的东西吧,也不是很难,也没啥技术难度,做来玩玩。做一个查询火车票的吧,再加上一个能循环查询的功能,这样就实现了简单的刷票程序。那就开始吧!

  首先打开 12306 的网页。他有一个输入开始地址和到达地址的 选择框,根据全国的火车站可以选择的,那么这部分数据是怎么来的呢?打开火狐的 firebug 我们可以看到这么一个js请求

  没错了,所有的候车站就在这里了。我们可以看到这个 地址是 https的,那么用file_get_contents,我们是无法获取数据的。但是不用怕,我们可以用curl来访问这个接口。看一下他的请求头:

  注意看,这里 他有一个 host 和 referer 以及 cookie,12306的网站我们不登录也是可以进行车票查询的,因此可以确定 cookie不是必要值,但是带上了 referer和host 显然要进行来源的验证,那么

我们可以伪造一个 这样的头去请求这个接口就可以得到数据了。看一下获取的函数,这个函数在后面的查询火车票的地方也可以用到:

 1 /**
 2      * 采集数据
 3      * @param $url
 4      * @param $decode
 5      */
 6     private function curlGet($url, $decode = true)
 7     {
 8         $ch = curl_init();
 9         $timeout = 5;
10         $header = [
11             'Accept:*/*',
12             'Accept-Charset:GBK,utf-8;q=0.7,*;q=0.3',
13             'Accept-Encoding:gzip,deflate,sdch',
14             'Accept-Language:zh-CN,zh;q=0.8,ja;q=0.6,en;q=0.4',
15             'Connection:keep-alive',
16             'Host:kyfw.12306.cn',
17             'Referer:https://kyfw.12306.cn/otn/lcxxcx/init',
18         ];
19         curl_setopt($ch, CURLOPT_URL, $url);
20         curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
21         curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
22         curl_setopt($ch, CURLOPT_ENCODING, "gzip"); //指定gzip压缩
23         curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // 跳过证书检查
24         curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);  // 从证书中检查SSL加密算法是否存在
25         curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
26         $result = curl_exec($ch);
27         curl_close($ch);
28 
29         $decode && $result = json_decode($result, true);
30 
31         return $result;
32     }

  还需要注意一点的 是,从请求头可以看到,数据进行了gzip压缩,这样我们如果不做处理的话,返回的数据会成为乱码,很麻烦,不过用curl很容易解决这个问题。

  数据得到了,那么根据规律很好拆分,首先根据 @ 查分一次,然后按照 | 查分一次即可。本项目中采用的是 boostrap-suggest, 这是一个非常强大且易用的输入联想

插件,我就降数据整理成 这个插件可以用的格式,代码如下:

 1  /**
 2      * 解析火车站信息
 3      */
 4     private function parseStation()
 5     {
 6         $url = 'https://kyfw.12306.cn/otn/resources/js/framework/station_name.js?station_version=1.8992';
 7         $station = $this->curlGet($url, false);
 8 
 9         if (empty($station)) {
10             throw new Exception('获取站点信息失败!');
11         }
12 
13         $delStr = "var station_names ='"; //需要截断的字符
14         $station = substr($station, strlen($delStr), strlen($station));
15 
16         $station = explode('@', $station);
17         $json = [
18             'message' => ''
19         ];
20 
21         foreach ($station as $key => $vo) {
22             if (empty($vo)) continue;
23 
24             $st = explode('|', $vo);
25             $json['value'][] = [
26                 'stationName' => $st['1'],
27                 'shortName' => $st['3'],
28                 'stationFlag' => $st['2']
29             ];
30         }
31         unset($station);
32 
33         file_put_contents(ROOT_PATH . '/data/station.json', json_encode($json));
34     }

  

  好了,车站信息我们有了,那么怎慢查询具体的火车票呢?我们再在 firebug中看看,12306用的是哪个接口。很快看到了这个接口:

https://kyfw.12306.cn/otn/lcxxcx/query?purpose_codes=ADULT&queryDate=2016-12-26&from_station=NJH&to_station=XCH,没错就是这个了。

头信息基本上跟获取站点的一样。purpose_codes表示你要查询的票的类型,默认为成人票。queryDate 为出发日期,from_station 开始站点编号,to_station:到达站点

编号。只要我们拼装成这个样子,很容易获取到数据:

 1 /**
 2      * 入口函数
 3      */
 4     public function run()
 5     {
 6         if (is_null($this->fromStation) || is_null($this->toStation))
 7             throw new Exception('起始站不能为空!');
 8         is_null($this->date) && $date = date('Y-m-d');
 9 
10         $url = 'https://kyfw.12306.cn/otn/lcxxcx/query?purpose_codes=ADULT&queryDate=' . $this->date . '&from_station=';
11         $url .= $this->fromStation . '&to_station=' . $this->toStation;
12 
13         $ticketInfo = $this->curlGet($url);
14         return $ticketInfo;
15     }

  

  每步的代码看到了,那么整个类如下:

  1 <?php
  2 
  3 /**
  4  * author: NickBai
  5  * createTime: 2016/12/26 0026 上午 9:11
  6  *
  7  */
  8 class Tickets
  9 {
 10     public $fromStation = null;
 11     public $toStation = null;
 12     public $date = null;
 13 
 14     public function __construct($fromStation = null, $toStation = null, $date = null)
 15     {
 16         if (!file_exists(ROOT_PATH . '/data/station.json')) {
 17             $this->parseStation();
 18         }
 19 
 20         $this->fromStation = $fromStation;
 21         $this->toStation = $toStation;
 22         $this->date = $date;
 23     }
 24 
 25     /**
 26      * 入口函数
 27      */
 28     public function run()
 29     {
 30         if (is_null($this->fromStation) || is_null($this->toStation))
 31             throw new Exception('起始站不能为空!');
 32         is_null($this->date) && $date = date('Y-m-d');
 33 
 34         $url = 'https://kyfw.12306.cn/otn/lcxxcx/query?purpose_codes=ADULT&queryDate=' . $this->date . '&from_station=';
 35         $url .= $this->fromStation . '&to_station=' . $this->toStation;
 36 
 37         $ticketInfo = $this->curlGet($url);
 38         return $ticketInfo;
 39     }
 40 
 41     /**
 42      * 解析火车站信息
 43      */
 44     private function parseStation()
 45     {
 46         $url = 'https://kyfw.12306.cn/otn/resources/js/framework/station_name.js?station_version=1.8992';
 47         $station = $this->curlGet($url, false);
 48 
 49         if (empty($station)) {
 50             throw new Exception('获取站点信息失败!');
 51         }
 52 
 53         $delStr = "var station_names ='"; //需要截断的字符
 54         $station = substr($station, strlen($delStr), strlen($station));
 55 
 56         $station = explode('@', $station);
 57         $json = [
 58             'message' => ''
 59         ];
 60 
 61         foreach ($station as $key => $vo) {
 62             if (empty($vo)) continue;
 63 
 64             $st = explode('|', $vo);
 65             $json['value'][] = [
 66                 'stationName' => $st['1'],
 67                 'shortName' => $st['3'],
 68                 'stationFlag' => $st['2']
 69             ];
 70         }
 71         unset($station);
 72 
 73         file_put_contents(ROOT_PATH . '/data/station.json', json_encode($json));
 74     }
 75 
 76     /**
 77      * 采集数据
 78      * @param $url
 79      * @param $decode
 80      */
 81     private function curlGet($url, $decode = true)
 82     {
 83         $ch = curl_init();
 84         $timeout = 5;
 85         $header = [
 86             'Accept:*/*',
 87             'Accept-Charset:GBK,utf-8;q=0.7,*;q=0.3',
 88             'Accept-Encoding:gzip,deflate,sdch',
 89             'Accept-Language:zh-CN,zh;q=0.8,ja;q=0.6,en;q=0.4',
 90             'Connection:keep-alive',
 91             'Host:kyfw.12306.cn',
 92             'Referer:https://kyfw.12306.cn/otn/lcxxcx/init',
 93         ];
 94         curl_setopt($ch, CURLOPT_URL, $url);
 95         curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
 96         curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
 97         curl_setopt($ch, CURLOPT_ENCODING, "gzip"); //指定gzip压缩
 98         curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // 跳过证书检查
 99         curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);  // 从证书中检查SSL加密算法是否存在
100         curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
101         $result = curl_exec($ch);
102         curl_close($ch);
103 
104         $decode && $result = json_decode($result, true);
105 
106         return $result;
107     }
108 
109 }

  代码组织结构如下:

 

  前台,依旧是采用 H+和layui 进行修改的页面,效果如下:

 

设置好 刷新频率,就可以愉快的刷票了。

代码分享地址:http://pan.baidu.com/s/1o8Apa5c

线上体验地址:http://www.baiyf.com/

 

posted @ 2016-12-26 17:00  NickBai  阅读(1624)  评论(0编辑  收藏  举报