python火车票查询系统的实现与总结

想法是用python编写一个车票查询工具,可以很好的锻炼一下自己python的网络编程能力。

1、第一步设计这个API的接口:
这里写图片描述
根据12306的设计我的API接口也要有出发站,目的站,车次类型,日期四部分。

车次类型:
-g 高铁
-d 动车
-t 特快
-k 快速
-z 直达
需要用到的库:

    requests,使用 Python 访问 HTTP 资源的必备库。
    docopt,Python3 命令行参数解析工具。
    prettytable, 格式化信息打印工具,能让你像 MySQL 那样打印数据。
    colorama,命令行着色工具

因为我这机子上python版本众多,但默认python版本为2.7,python3的pip3、settools没有安装。
下面安装:

sudo apt-get install python3-setuptools
sudo easy_install3 pip requests  docopt prettytable colorama

安装这三个库:

sudo pip3 install 

新建tickets.py 文件,代码如下:

# coding: utf-8      
""" 命令行火车票查看器
Usage:
        tickets [-gdtzk] <from> <to> <date>
Options:
        -h,--help   显示帮助菜单
        -g          高铁
        -d          动车
        -t          特快
        -k          快速
        -z          直达  
Example:
        tickets -gd 沈阳 北京 2017-03-01
"""
#command-line-interface
from docopt import docopt
def cli():
    arguments=docopt(__doc__)
    print(arguments)
if __name__ == "__main__":
    cli()

“# coding: utf-8 ”是为了避免输出中文乱码的一种编码方式,确立输出文件为在python3.x之后没有这个问题。
这里写图片描述
接下来是python命令行解析工具docopt的固定格式,这个工具简洁方便。
“if name == “main“: cli()” 这就代码的作用是当直接运行tickets.py 时会打印命令行参数提示信息。
每个模块都有内置方法name,当直接被调用时为main,被导入使用时为他的文件名。所以这里执行cli().
效果:
这里写图片描述
2.网站应答分析
打开12306,随便输入时间和地点,最好浏览器为火狐装载firebug插件。点击查询:

消息头信息:注意这个URl:https://kyfw.12306.cn/otn/leftTicket/后加了四个参数,始发站,目的站,时间,和是否半价票。
这里写图片描述

在抓取的请求和应答中,找到如下图这样的报文,他的参数有始发站,目的站,时间,这样的才是我们要在python中想要获取的数据。但是站点名都是缩写,想想你登12306查火车票, 你输入的是汉字吧, 而我们发现了请求的URL中是车站代码, 这样我们就知道汉字到代码的转换是在前端(JS)完成的, 那么前端源码中肯定有转换对应表.

这里写图片描述

这个响应信息就是我们想要的:
这里写图片描述

我们要通过查看源代码找找这个缩写和站点名是怎样对应的。

这里写图片描述
看看:/otn/resources/js/framework/station_name.js?station_version=1.8997,源是从这里来的,我们也要从这里获取站名对应表,这就是前面说的转换对应表。
新建一个teststation.py用来获取站名代码如下:

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

url = 'https://kyfw.12306.cn/otn/resources/js/framework/station_name.js?station_version=1.8997'
response = requests.get(url,verify=False)
station = re.findall(u'([\u4e00-\u9fa5]+)\|([A-Z]+)',response.text)
pprint(dict(station),indent=4)

re模块是一个用来检索的一个模块,导入他我们可以把与之匹配的信息通过pprint方法打印出来。requests模块是http请求必用的模块。
之后导入到stations.py 文件中:

python3.5 teststations.py > stations.py

这里边执行我用了python3.5,因为默认的python 为2.7版本,对输出中文不支持。
效果:
对输出的字典文件加个stations名。
这里写图片描述
现在,用户输入车站的中文名,我们就可以直接从这个字典中获取它的字母代码了。
3、数据的获取

# coding: utf-8

"""命令行火车票查看器

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

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

Example:
    tickets 北京 上海 2016-10-10
    tickets -dg 成都 南京 2016-10-10
"""
#command- line -interface
from docopt import docopt
from stations import stations
import requests
def cli():
    arguments = docopt(__doc__)
    from_station = stations.get(arguments['<from>'])
    to_station = stations.get(arguments['<to>'])
    date = arguments['<date>']
    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())
if __name__ == "__main__":
    cli()

这段代码大意就是把tickets.py 的参数值赋给三个变量,然后赋给由第二遍得到的URl中,再由get()函数接受数据,打印其JSON格式。
效果是这样的:
这里写图片描述
分析一下我们可以看到其实得到的数据是四个字典,第三个字典中有嵌套了字典列表:

这里写图片描述

4、 解析数据

代码如下:
这是根据现在12306网站写的接口,它的接口经常变动。第三步得到的数据形式也总在变化。

# coding: utf-8

"""命令行火车票查看器

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

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

Example:
    tickets 北京 上海 2016-10-10
    tickets -dg 成都 南京 2016-10-10
"""
from docopt import docopt
from stations import stations
import requests
from prettytable import PrettyTable

class TrainsCollection:
    header = '车次 车站 时间 历时 一等 二等 软卧 硬卧 硬座 无座'.split()

    def __init__(self, available_trains, options):
        """
                      查询到的火车班次集合

                     :param available_trains: 一个列表, 包含可获得的火车班次, 每个
                                              火车班次是一个字典
                     :param options: 查询的选项, 如高铁, 动车, etc...

                  """
        self.available_trains = available_trains
        self.options = options
    def _get_duration(self,raw_train):
        duration = raw_train.get('lishi').replace(':','小时')+ '分'
        if duration.startswith('00'):
            return duration[4:]
        if duration.startswith('0'):
            return duration[1:]
        return duration
    @property
    def trains(self):

        train_no = self.available_trains['station_train_code']
        initial = train_no[0].lower()
        if not self.options or initial in self.options:
            train = [
                train_no,
                '\n'.join([self.available_trains['from_station_name'],
                            self.available_trains['to_station_name']]),
                '\n'.join([self.available_trains['start_time'],
                            self.available_trains['arrive_time']]),
                self._get_duration(self.available_trains),
                self.available_trains['zy_num'],
                self.available_trains['ze_num'],
                self.available_trains['rw_num'],
                self.available_trains['yw_num'],
                self.available_trains['yz_num'],
                self.available_trains['wz_num'],
                ]
            yield train

    def pretty_print(self):
        pt = PrettyTable()
        pt._set_field_names(self.header)
        for train in self.trains:
            pt.add_row(train)
        print(pt)



#command- line -interface

def cli():
    arguments = docopt(__doc__)
    from_station = stations.get(arguments['<from>'])
    to_station = stations.get(arguments['<to>'])
    date = arguments['<date>']

    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)



    # 获取参数
    options = ''.join([
                          key for key, value in arguments.items() if value is True
                          ])

    r = requests.get(url, verify=False)

    rows = r.json()['data']
    for row in rows:
        available_trains = row['queryLeftNewDTO']
    #print (available_trains)
    TrainsCollection(available_trains, options).pretty_print()

if __name__ == "__main__":
    cli()

“ @property”
Python中有一个被称为属性函数(property)的小概念,它可以做一些有用的事情。能做以下几点:

将类方法转换为只读属性
重新实现一个属性的setter和getter方法

使用属性函数的最简单的方法之一是将它作为一个方法的装饰器来使用。这可以让你将一个类方法转变成一个类属性。当我需要做某些值的合并时,我发现这很有用。其他想要获取它作为方法使用的人,发现在写转换函数时它很有用。

rows = r.json()

这条代码的后的导出文件:

{
'messages': [],
 'validateMessagesShowId': '_validatorMessage',
 'data': [
    {'queryLeftNewDTO':
         {'controlled_train_flag': '1',
         'yw_num': '--', 
        'end_station_name': '杭州',
         'from_station_name': '沈阳北', 
        'yz_num': '--',
         'gr_num': '--',
         'rz_num': '--', 
        'start_station_name': '哈尔滨', 
        'train_no': '010000Z17804',
         'location_code': 'B2',
         'swz_num': '--', 
        'from_station_telecode': 'SBT',
         'lishiValue': '5999',
         'wz_num': '--',
         'start_province_code': '06',
         'tz_num': '--',...}]} 

之后使用json函数来匹配字典中的[‘data’]关键字,我们看一下匹配后的导出文件:

 rows = r.json()['data']
[
    {'queryLeftNewDTO':
         {'controlled_train_flag': '1',
         'yw_num': '--', 
        'end_station_name': '杭州',
         'from_station_name': '沈阳北', 
        'yz_num': '--',
         'gr_num': '--',
         'rz_num': '--', 
        'start_station_name': '哈尔滨', 
        'train_no': '010000Z17804',
         'location_code': 'B2',
         'swz_num': '--', 
        'from_station_telecode': 'SBT',
         'lishiValue': '5999',
         'wz_num': '--',
         'start_province_code': '06',
         'tz_num': '--', 
        'station_train_code': 'Z178',
         'to_station_telecode': 'ZJH',
         'rw_num': '--', 
        'controlled_train_message': '列车运行图调整,暂停发售', 
        'from_station_no': '05',
         'yp_info': '', 
        'arrive_time': '24:00',...]

这是一个列表,我们用for来检索:


    for row in rows:
        available_trains = row['queryLeftNewDTO']

这是我们要的信息了

{'day_difference': '0', 
'end_station_telecode': 'AOH',
 'lishiValue': '527', 
'controlled_train_flag': '0', 
'start_city_code': '0034', 
'yb_num': '--', 
'lishi': '08:47', 
'ze_num': '有',
 'is_support_card': '1', 
'tz_num': '无', 
'start_station_name': '哈尔滨西',
 'station_train_code': 'G1202', 
'yz_num': '--', 
'end_station_name': '上海虹桥', 
'rz_num': '--',...}

效果图:
这里写图片描述

  • 3
    点赞
  • 52
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值