閑來無事,做一個好玩的東西吧,也不是很難,也沒啥技術難度,做來玩玩。做一個查詢火車票的吧,再加上一個能循環查詢的功能,這樣就實現了簡單的刷票程序。那就開始吧!
首先打開 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/