Python爬取12306實現火車票查詢
介紹:
除了官方的12306網站,其他的很多網站都提供了購買查詢的功能,像攜程的鐵友,途牛等等。這些網站他們盈利大都是通過廣告的收入,以及通過購票帶動的網站內酒店、景點額外收入,他們的網站介面友好型優於12306,使得在購票入口中佔據了一定份額。但是歸根到底,所有購票的APP、網站都是拿的12306的基礎資料,或者說是12306的介面,只是做了前端介面的設計。在程式設計師的眼中,有介面就能創造世界(然而這只是我的一種臆想)。本文就是通過查詢介面,在命令列視窗中實現火車票查詢。
完整思路和處理過程:
首先要拿到查詢的介面就要採用抓包的方式,常用的抓包工具:瀏覽器自帶的檢查功能,FIddler抓包工具(功能更強大)
但是在URL中的出發地和目的地都是字母,那我們必須要拿到所有的車站資訊列表才能構造URL請求,在網頁Sources和網頁原始碼中分別尋找station的檔案
對station進行解析:parse_station.py
通過“>”,來重定向輸出的內容,通過“>>”將輸出的內容追加到檔案中,這裡我們通過 $ python parse_station.py > station.py
# coding: utf-8 import re import requests from pprint import pprint def main(): url = 'https://kyfw.12306.cn/otn/resources/js/framework/station_name.js?station_version=1.8971' # 傳送get請求,不判斷證書 response = requests.get(url, verify=False) # 使用正則表示式提取所有的站點:漢字和大寫代號 stations = dict(re.findall(u'([\u4e00-\u9fa5]+)\|([A-Z]+)', response.text)) # 轉換成字典就是為了將漢字站點和字母代號分開且有一一對應關係:鍵-->值 pprint(stations.keys()) pprint(stations.values()) if __name__ == '__main__': main()
在station.py 中建立鍵值之間的雙向關係
def get_name(telecode):
return names[telecodes.index(telecode)]
def get_telecode(name):
return telecodes[names.index(name)]
這裡先對提取資料的程式碼中涉及到的點進行總結:
1.兩種格式化輸出的方式:%和format
In [7]: "%s,%d"%("kzc",18)
Out[7]: 'kzc,18'
In [8]: '{0},{1}'.format('kzc',18)
Out[8]: 'kzc,18'
2.用到的庫
requests:爬蟲必用的傳送請求,獲取HTML網頁內容的庫
docopt:命令列解析工具,可以根據自定義的文件描述,自動生成解析器
prettytable:能讓你的資料像MySQL的命令列顯示資料的格式一樣
colorama:命令列著色庫
# coding: utf-8
"""命令列火車票檢視器:Usage Options為docopt庫固定格式
Usage:
tickets [-dgktz] <from> <to> <date>
Options:
-h, --help 檢視幫助
-d 動車
-g 高鐵
-k 快速
-t 特快
-z 直達
Examples:
tickets 上海 北京 2017-10-10
tickets -dg 成都 南京 2017-10-10
"""
from docopt import docopt
import requests
from prettytable import PrettyTable
from colorama import Fore
import stations
def cli():
arguments = docopt(__doc__,version='ticket 1.0')
from_station = stations.get_telecode(arguments.get('<from>'))
to_station = stations.get_telecode(arguments.get('<to>'))
date = arguments.get('<date>')
# 列表推導式,得到的是查詢車次型別的集合
options = ''.join([key for key,value in arguments.items() if value is True])
print(options)
url = ('https://kyfw.12306.cn/otn/leftTicket/query?'
'leftTicketDTO.train_date={}&'
'leftTicketDTO.from_station={}&'
'leftTicketDTO.to_station={}&'
'purpose_codes=ADULT').format(date,from_station,to_station)
r = requests.get(url, verify=False)
# print(r.json())
# requests得到的是一個json格式的物件,r.json()轉化成python字典格式資料來提取,所有的車次結果result
raw_trains = r.json()['data']['result']
pt = PrettyTable()
pt._set_field_names("車次 車站 時間 經歷時 一等座 二等座 軟臥 硬臥 硬座 無座".split())
for raw_train in raw_trains:
# split切割之後得到的是一個列表
data_list = raw_train.split("|")
train_no = data_list[3]
initial = train_no[0].lower()
# print(train_no[0])
# 判斷是否是查詢特定車次的資訊
if not options or initial in options:
from_station_code = data_list[6]
to_station_code = data_list[7]
from_station_name = ''
to_station_name = ''
start_time = data_list[8]
arrive_time = data_list[9]
time_duration = data_list[10]
first_class_seat = data_list[31] or "--"
second_class_seat = data_list[30] or "--"
soft_sleep = data_list[23] or "--"
hard_sleep = data_list[28] or "--"
hard_seat = data_list[29] or "--"
no_seat = data_list[33] or "--"
pt.add_row([
# 對特定文字新增顏色
train_no,
'\n'.join([Fore.GREEN + stations.get_name(from_station_code) + Fore.RESET, Fore.RED + stations.get_name(to_station_code) + Fore.RESET]),
'\n'.join([Fore.GREEN + start_time + Fore.RESET,Fore.RED + arrive_time + Fore.RESET]),
time_duration,
first_class_seat,
second_class_seat,
soft_sleep,
hard_sleep,
hard_seat,
no_seat
])
print(pt)
if __name__ == '__main__':
cli()
參考來源:https://www.shiyanlou.com/courses/623/labs/2072/document最後效果圖:很像火車站電子螢幕的效果