介绍:
除了官方的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
最后效果图:很像火车站电子屏幕的效果