忘記12306!用 Python3 實現(xiàn)自己的火車票查看器!
課程簡介:使用 Python3 抓取 12306 網(wǎng)站信息提供一個命令行的火車票查詢工具。通過該項目的實現(xiàn),可以熟悉 Python3 基礎及網(wǎng)絡編程,以及 docopt,requests,prettytable 等庫的使用。

項目由小蝸牛發(fā)布在實驗樓,項目在線練習地址:Python3 實現(xiàn)火車票查詢工具,可以直接在教程中下載代碼使用demo。
一、實驗簡介
當你想查詢一下火車票信息的時候,你還在上 12306 官網(wǎng)嗎?或是打開你手機里的 APP?
下面讓我們來用 Python 寫一個命令行版的火車票查看器, 只要在命令行敲一行命令就能獲得你想要的火車票信息!如果你剛掌握了Python基礎,這將是個不錯的小練習。
1.1 知識點
Python3 基礎知識的綜合運用
docopt、requests?及?prettytable?庫的使用
1.2 效果截圖
二、接口設計
一個應用寫出來最終是要給人使用的,哪怕只是給你自己使用。
所以,首先應該想想你希望怎么使用它?讓我們先給這個小應用起個名字吧,既然及查詢票務信息,那就叫它?tickets?好了。
我們希望用戶只要輸入出發(fā)站,到達站以及日期就讓就能獲得想要的信息,比如要查看8月25號上海-北京的火車余票, 我們只需輸入:
$?tickets?shanghai?beijing?2016-08-25
注意:?由于實驗樓環(huán)境中無法輸入中文,所以我們的參數(shù)設計為拼音的形式,在這里思考下使用拼音是否有什么弊端?
對這一接口進行抽象得到:
$?tickets?from?to?date
另外,火車有各種類型,高鐵、動車、特快、快速和直達,我們希望可以提供選項只查詢特定的一種或幾種的火車,所以,我們應該有下面這些選項:
-g 高鐵
-d 動車
-t 特快
-k 快速
-z 直達
這幾個選項應該能被組合使用,所以,最終我們的接口應該是這個樣子的:
$?tickets?[-gdtkz]?from?to?date
接口已經(jīng)確定好了,剩下的就是實現(xiàn)它了。
三、代碼實現(xiàn)
首先安裝一下實驗需要用到的庫:
$?sodo?pip?install?requests?prettytable?docopt
requests, 不用不多介紹了吧,使用 Python 訪問 HTTP 資源的必備庫。
docopt, Python3 命令行參數(shù)解析工具。
prettytable, 格式化信息打印工具,能讓你像 MySQL 那樣打印數(shù)據(jù)。
3.1 解析參數(shù)
Python有很多寫命令行參數(shù)解析工具,如?argparse,?docopt,?click,這里我們選用的是?docopt?這個簡單易用的工具。docopt?可以按我們在文檔字符串中定義的格式來解析參數(shù),比如我們在?tickets.py:
注意:?實驗樓中無法輸入中文,參數(shù)后的中文可以使用拼音代替。
#?coding:?utf-8"""Train?tickets?query?via?command-line.?Usage:?tickets?[-gdtkz]?
下面我們運行一下這個程序:
$?python3?tickets.py?beijing?shanghai?2016-08-25
我們得到下面的結(jié)果:
3.2 獲取數(shù)據(jù)
參數(shù)已經(jīng)解析好了,下面就是如何獲取數(shù)據(jù)了,這也是最主要的部分。首先我們打開?12306,進入余票查詢頁面,如果你使用 Chrome,那么按?F12?打開開發(fā)者工具,選中?Network?一欄,在查詢框鐘我們輸入?上海?到?北京,日期?2016-08-25, 點擊查詢,我們在調(diào)試工具發(fā)現(xiàn),查詢系統(tǒng)實際上請求了這個URL:
https://kyfw.12306.cn/otn/lcxxcx/query?purpose_codes=ADULT&queryDate=2016-07-01&from_station=SHH&to_station=BJP
并且返回的是JSON格式的數(shù)據(jù)!
接下來問題就簡單了,我們只需要構(gòu)建請求URL然后解析返回的Json數(shù)據(jù)就可以了。但是我們發(fā)現(xiàn),URL里面?from_station?和?to_station?并不是漢字或者拼音,而是一個代號,而我們想要輸入的是漢字或者拼音,我們要如何獲取代號呢?我們打開網(wǎng)頁源碼看看有沒有什么發(fā)現(xiàn)。
果然,我們在網(wǎng)頁里面找到了這個鏈接:https://kyfw.12306.cn/otn/resources/js/framework/station_name.js?station_version=1.8955?這里面貌似是包含了所有車站的中文名,拼音,簡寫和代號等信息。但是這些信息擠在一起,而我們只想要車站的拼音和大寫字母的代號信息,怎么辦呢?
正則表達式就是答案,我們寫個小腳本來匹配提取出想要的信息吧, 在parse_station.py中:
#?coding:?utf-8import?reimport?requestsfrom?pprint?import?pprint url?=?'https://kyfw.12306.cn/otn/resources/js/framework/station_name.js?station_version=1.8955'text?=?requests.get(url,?verify=False) stations?=?re.findall(r'([A-Z]+)\|([a-z]+)',?text) stations?=?dict(stations) stations?=?dict(zip(stations.values(),?stations.keys())) pprint(stations,?indent=4)
注意,上面的正則表達式匹配出的結(jié)果轉(zhuǎn)為字典后,字典的鍵是大寫字母大號,這顯然不是我們想要的結(jié)果,于是,我們通過一個變換將鍵值反過來。 我們運行這個腳本,它將以字典的形式返回所有車站和它的大寫字母代號, 我們將結(jié)果重定向到?stations.py中,
$?python3?parse_station.py?>?stations.py
我們?yōu)檫@個字典加名字,stations, 最終,stations.py文件是這樣的:
現(xiàn)在,用戶輸入車站的中文名,我們就可以直接從這個字典中獲取它的字母代碼了:
...from?stations?import?stationsdef?cli(): ????arguments?=?docopt(__doc__) ????from_staion?=?stations.get(arguments['
萬事俱備,下面我們來請求這個URL獲取數(shù)據(jù)吧!這里我們使用?requests?這個庫, 它提供了非常簡單易用的接口,
...import?requestsdef?cli(): ????...????#?添加verify=False參數(shù)不驗證證書 ????r?=?requests.get(url,?verify=False)????print(r.json())
從結(jié)果中,我們可以觀察到,與車票有關的信息需要進一步提取:
def?cli(): ????... ????r?=?requsets.get(url); ????rows?=?r.json()['data']['datas']
3.3 解析數(shù)據(jù)
我們封裝一個簡單的類來解析數(shù)據(jù):
from?prettytable?import?PrettyTableclass?TrainCollection(object): ????#?顯示車次、出發(fā)/到達站、?出發(fā)/到達時間、歷時、一等坐、二等坐、軟臥、硬臥、硬座 ????header?=?'train?station?time?duration?first?second?softsleep?hardsleep?hardsit'.split()????def?__init__(self,?rows): ????????self.rows?=?rows????def?_get_duration(self.row): ????????"""?獲取車次運行時間?""" ????????duration?=?row.get('lishi').replace(':',?'h')?+?'m' ????????if?duration.startswith('00'):????????????return?duration[4:]????????if?duration.startswith('0'):????????? ???????????return?duration[1:]????????return?duration????@property ????def?trains(self): ????????for?row?in?self.rows: ????????????train?=?[????????????????#?車次 ????????????????row['station_train_code'],????????????????#?出發(fā)、到達站 ????????????????'\n'.join([row['from_staion_name'],?row['to_station_name']]),????????????????#?出發(fā)、到達時間 ????????????????'\n'.join([row['start_time'],?row['arrive']]),????????????????#?歷時 ????????????????self._get_duration(row),????????????????#?一等坐 ????????????????row['zy_num'],????????????????#?二等坐 ????????????????row['ze_num'],????????????????#?軟臥 ????????????????row['rw_num'],????????????????#?軟坐 ????????????????row['yw_num'],????????????????#?硬坐 ????????????????row['yz_num'] ????????????]????????????yield?train????def?pretty_print(self): ????????"""?數(shù)據(jù)已經(jīng)獲取到了,剩下的就是提取我們要的信息并將它顯示出來。?`prettytable`這個庫可以讓我們它像MySQL數(shù)據(jù)庫那樣格式化顯示數(shù)據(jù)。?""" ????????pt?=?PrettyTable()????????#?設置每一列的標題 ????????pt._set_field_names(self.header)????????for?train?in?self.trains: ????????????pt.add_row(train) ????????print(pt)
3.4 顯示結(jié)果
最后,我們將上述過程進行匯總并將結(jié)果輸出到屏幕上:
...class?TrainCollection: ????... ????...def?cli(): ????arguments?=?docopt(__doc__) ????from_staion?=?stations.get(arguments['
3.5 最后一米
至此, 程序的主體已經(jīng)完成了, 但是上面打印出的結(jié)果是黑白的,很是乏味,我們來給它添加顏色吧:
def?colored(color,?text): ????table?=?{????????'red':?'\033[91m',????????'green':?'\033[92m',????????#?no?color ????????'nc':?'\033[0' ????} ????cv?=?table.get(color) ????nc?=?table.get('nv')????return?''.join([cv,?text,?nc])
修改一下程序,將出發(fā)車站與出發(fā)時間顯示為紅色, 將到達車站與到達時間顯示為綠色:
...'\n'.join([colored('green',?row['from_staion_name']) ???????????colored('red',?row['to_station_name'])]),'\n'.join([colored('green',?row['start_time']) ???????????colored('red',?row['arrive_time'])]), ...
四、總結(jié)
本課程使用 Python3 抓取 12306 網(wǎng)站信息提供一個命令行的火車票查詢工具。通過該項目的實現(xiàn),可以學習并實踐 Python3 基礎及網(wǎng)絡編程,以及 docopt,requests,prettytable 等庫的使用。
感興趣的同學可以實現(xiàn)更多擴展功能:
顯示商務坐, 無坐
添加參數(shù)支持,用戶可以指定火車類型
支持更多的時間格式,如:20161010
本項目的完整代碼及demo,可在實驗樓查看并在線完成,立即【開始實驗】
更多Python經(jīng)典項目:Python全部 - 課程
本文轉(zhuǎn)載自異步社區(qū)。
網(wǎng)絡 高性能計算 Web應用防火墻 WAF
版權(quán)聲明:本文內(nèi)容由網(wǎng)絡用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔相應法律責任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實的內(nèi)容,請聯(lián)系我們jiasou666@gmail.com 處理,核實后本網(wǎng)站將在24小時內(nèi)刪除侵權(quán)內(nèi)容。
版權(quán)聲明:本文內(nèi)容由網(wǎng)絡用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔相應法律責任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實的內(nèi)容,請聯(lián)系我們jiasou666@gmail.com 處理,核實后本網(wǎng)站將在24小時內(nèi)刪除侵權(quán)內(nèi)容。