利用Python实现命令行版的火车票查看器


Posted in Python onAugust 05, 2016

接口设计

一个应用写出来最终是要给人使用的,哪怕只是给你自己使用。所以,首先应该想想你希望怎么使用它?让我们先给这个小应用起个名字吧,既然及查询票务信息,那就叫它tickets好了。我们希望用户只要输入出发站,到达站以及日期就让就能获得想要的信息,所以tickets应该这样被使用:

$ tickets from to date

另外,火车有各种类型,高铁、动车、特快、快速和直达,我们希望可以提供选项只查询特定的一种或几种的火车,所以,我们应该有下面这些选项:

     -g 高铁

     -d 动车

     -t 特快

     -k 快速

     -z 直达

这几个选项应该能被组合使用,所以,最终我们的接口应该是这个样子的:

$ tickets [-gdtkz] from to date

接口已经确定好了,剩下的就是实现它了。

开发环境

写Python程序的一个良好实践是使用virtualenv这个工具建一个虚拟的环境。我们的程序使用Python3开发,下面在你的工作目录下建一个文件夹tickets,进去创建一个虚拟环境:

$ virtualenv -p /usr/bin/python3 venv

通过下面的命令激活它:

$ . venv/bin/activate

解析参数

Python有很多写命令行应用的工具,如argparse, docopt, options…这里,我们选用docopt这个简单易用的工具,我们先安装它:

$ pip3 install docopt

docopt可以按我们在文档字符串中定义的格式来解析参数,在tickets.py中:

# coding: utf-8

"""Train tickets query via command-line.

Usage:
  tickets [-gdtkz] <from> <to> <date>

Options: 
  -h,--help    显示帮助菜单
  -g        高铁
  -d        动车
  -t        特快
  -k        快速
  -z        直达

Example:
  tickets 南京 北京 2016-07-01
  tickets -dg 南京 北京 2016-07-01
"""
from docopt import docopt

def cli():
  """command-line interface""" 
  arguments = docopt(__doc__)
  print(arguments)

if __name__ == '__main__': 
  cli()

下面我们运行一下这个程序:

$ python3 tickets.py 上海 北京 2016-07-01

我们得到下面的参数解析结果:

{‘-d': False, ‘-g': False, ‘-k': False, ‘-t': False, ‘-z': False, ‘<date>': ‘2016-07-01', ‘<from>': ‘上海', ‘<to>': ‘北京'}

获取数据

参数已经解析好了,下面就是如何获取数据了,这也是最主要的部分。首先我们打开12306,进入余票查询页面,如果你使用chrome,那么按F12打开开发者工具,选中Network一栏,在查询框钟我们输入上海到北京,日期2016-07-01, 点击查询,我们在调试工具发现,查询系统实际上请求了这个URL

https://kyfw.12306.cn/otn/lcxxcx/query?purpose_codes=ADULT&queryDate=2016-07-01&from_station=SHH&to_station=BJP

并且返回的是Json格式的数据!接下来问题就简单了,我们只需要构建请求URL然后解析返回的Json数据就可以了。但是我们发现,URL里面from_stationto_station并不是汉字,而是一个代号,而用户输入的是汉字,我们要如何获取代号呢?我们打开网页源码看看有没有什么发现。

啊哈!果然,我们在网页里面找到了这个链接:点我。这里面貌似是包含了所有车站的中文名,拼音,简写和代号等信息, 我们在项目目录下将它保存为stations.html。但是这些信息挤在一起,而我们只想要中文名和大写字母的代号信息,怎么办呢?

BINGO!正则表达式,我们写个小脚本来匹配提取出想要的信息吧, 在parse.py中:

# coding: utf-8
import re
from pprint import pprint

with open('stations.html', 'r') as f:
  text = f.read()
  stations = re.findall(u'([\u4e00-\u9fa5]+)\|([A-Z]+)', text)
  pprint(dict(stations), indent=4)

我们运行这个脚本,它将以字典的形式返回所有车站和它的大写字母代号, 我们将结果重定向到stations.py中,

$ python3 parse.py > stations.py

我们为这个字典加名字,stations, 最终,stations.py文件是这样的:

stations = {  
  '一间堡': 'YJT',
  '一面坡': 'YPB',
   ... 
  '龙镇': 'LZA',
  '龙骨甸': 'LGM'
}

现在,用户输入车站的中文名,我们就可以直接从这个字典中获取它的字母代码了:

...
from stations import stations

def cli():
  arguments = docopt(__doc__)
  from_staion = stations.get(arguments['<from>'])
  to_station = stations.get(arguments['<to>'])
  date = arguments['<date>']
  # 构建URL
  url = 'https://kyfw.12306.cn/otn/lcxxcx/query?purpose_codes=ADULT&queryDate={}&from_station={}&to_station={}'.format(
    date, from_staion, to_station
)

万事俱备,下面我们来请求这个URL获取数据吧!这里我们使用requests这个库, 先安装它:

$ pip3 install requests

它提供了非常简单易用的接口,

...
import requests

def cli():
  ...
  # 添加verify=False参数, 不验证证书
  r = requests.get(url, verify=False)
  print(r.json())

从结果中,我们可以观察到,与车票有关的信息需要进一步提取:

def cli():
  ...
  r = requsets.get(url);
  rows = r.json()['data']['datas']

显示结果

数据已经获取到了,剩下的就是提取我们要的信息并将它显示出来。prettytable这个库可以让我们它像MySQL数据库那样格式化显示数据。

$ pip3 install prettytable

这样使用它:

...
from prettytable import PrettyTable

def cli():
  ...
  headers = '车次 车站 时间 历时 商务 一等 二等 软卧 硬卧 软座 硬座 无座'.split()
  pt = PrettyTable()
  pt._set_field_names(headers)
  for row in rows:
  # 从row中根据headers过滤信息, 然后调用pt.add_row()添加到表中
    ...
  print(pt)

总结

以上就是用Python写一个命令行版的火车票查看器的全部内容,对于Python学习是个不错的例子,希望能对大家学习Python有所帮助。

Python 相关文章推荐
Python实现将不规范的英文名字首字母大写
Nov 15 Python
python中返回矩阵的行列方法
Apr 04 Python
Python把csv数据写入list和字典类型的变量脚本方法
Jun 15 Python
python 重命名轴索引的方法
Nov 10 Python
pycham查看程序执行的时间方法
Nov 29 Python
Python pycharm 同时加载多个项目的方法
Jan 17 Python
python3实现小球转动抽奖小游戏
Apr 15 Python
Python多线程threading模块用法实例分析
May 22 Python
django中嵌套的try-except实例
May 21 Python
利用python清除移动硬盘中的临时文件
Oct 28 Python
python中pyplot基础图标函数整理
Nov 10 Python
教你用Python写一个植物大战僵尸小游戏
Apr 25 Python
Python处理JSON数据并生成条形图
Aug 05 #Python
用Python解决计数原理问题的方法
Aug 04 #Python
快速入手Python字符编码
Aug 03 #Python
Python采用Django制作简易的知乎日报API
Aug 03 #Python
利用Python实现图书超期提醒
Aug 02 #Python
Python正规则表达式学习指南
Aug 02 #Python
Python实现SMTP发送邮件详细教程
Mar 02 #Python
You might like
php下删除一篇文章生成的多个静态页面
2010/08/08 PHP
PHP获取本周所有日期或者最近七天所有日期的方法
2018/06/20 PHP
使用Laravel中的查询构造器实现增删改查功能
2019/09/03 PHP
如何用javascript控制上传文件的大小
2006/10/26 Javascript
Jquery 点击按钮显示和隐藏层的代码
2011/07/25 Javascript
判断文档离浏览器顶部的距离的方法
2014/01/08 Javascript
JavaScript中5种调用函数的方法
2015/03/12 Javascript
基于jquery实现的树形菜单效果代码
2015/09/06 Javascript
学习JavaScript设计模式之策略模式
2016/01/12 Javascript
基于JQuery打造无缝滚动新闻步骤详解
2016/03/31 Javascript
BootStrap智能表单demo示例详解
2016/06/13 Javascript
jQuery实现右键菜单、遮罩等效果代码
2016/09/27 Javascript
Node.js利用debug模块打印出调试日志的方法
2017/04/25 Javascript
详解JavaScript中return的用法
2017/05/08 Javascript
详解在Vue中如何使用axios跨域访问数据
2017/07/07 Javascript
Vue 创建组件的两种方法小结(必看)
2018/02/23 Javascript
原生js实现form表单序列化的方法
2018/08/02 Javascript
对Vue beforeRouteEnter 的next执行时机详解
2018/08/25 Javascript
JS实现的input选择图片本地预览功能示例
2018/08/29 Javascript
webpack实现一个行内样式px转vw的loader示例
2018/09/13 Javascript
VUE项目实现主题切换的多种方法
2020/11/26 Vue.js
[43:51]2014 DOTA2国际邀请赛中国区预选赛 Dream Times VS TongFu
2014/05/22 DOTA
Tornado服务器中绑定域名、虚拟主机的方法
2014/08/22 Python
Python基于DES算法加密解密实例
2015/06/03 Python
Python批量创建迅雷任务及创建多个文件
2016/02/13 Python
在pytorch中查看可训练参数的例子
2019/08/18 Python
python中@property和property函数常见使用方法示例
2019/10/21 Python
详解从Django Allauth中进行登录改造小结
2019/12/18 Python
关于python 的legend图例,参数使用说明
2020/04/17 Python
python爬虫判断招聘信息是否存在的实例代码
2020/11/20 Python
css3中transition属性详解
2014/09/02 HTML / CSS
英语专业毕业生自我鉴定
2013/11/09 职场文书
初中美术教学反思
2014/01/29 职场文书
80后职场人的职业生涯规划
2014/03/08 职场文书
保护环境标语
2014/06/09 职场文书
2015学校师德师风工作总结
2015/04/22 职场文书