🌀riba2534's Blog
凡事有交代,件件有着落,事事有回音
12306火车票余票查询器

12306火车票余票查询器

今天写了一个12306火车票余票查询器的爬虫,在这里记录一下过程.

本博客同步更新:http://blog.csdn.net/riba2534

首先先看一下最终效果:

比如想查9月2日从西安—北京动车特快的余票

tickets.py -dg 西安 北京 2017-09-02

效果预览:

首先我们梳理一下用到的工具:

  1. Python3.x(必备)
  2. requests库,用来进行http请求的访问
  3. docopt库,用来实现命令行参数处理(使用方法)
  4. prettytable,使信息以好看的表格形式呈现出来
  5. colorama,用来设置命令行中显示的颜色

前期准备

我们的主程序就叫做tickets.py,因为我们是用的带参数的形式实现程序,所以我们要先进行参数的声明: 我们先:from docopt import docopt

"""命令行火车余票查询器
Usage:
    tickets [-gdtkz] <from> <to> <date>

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

这些信息会存储在__doc__中,docopt会对这个信息进行解析然后返回我们需要的信息,解析的代码如下:

解析URL

既然要写爬虫,那么首先肯定是要对URL进行解析了,我们要爬取的是12306的网站,那么我们首先找到查询余票的网站:

https://kyfw.12306.cn/otn/leftTicket/init

我们会看到这个: 然后进行抓包,我们会发现一个GET请求: 也就是这个链接:

https://kyfw.12306.cn/otn/leftTicket/query?leftTicketDTO.train_date=2017-09-01&leftTicketDTO.from_station=XAY&leftTicketDTO.to_station=BJP&purpose_codes=ADULT

我们通过观察就会发现这个请求里面有4个参数:

  • leftTicketDTO.train_date=2017-09-01
  • leftTicketDTO.from_station=XAY
  • leftTicketDTO.to_station=BJP
  • purpose_codes=ADULT

我们通过观察就可以知道,这四个属性分别对应着:查询的时间,出发的站点,结束的站点,票的种类

现在发现了一个问题,出发站点和结束站点的值为什么都是英文,这些字母肯定表示的是城市的名称,那么我们现在就要去寻找这些城市的代码,我们查看网页源代码 发现了一个JS文件,打开以后会发现 这个页面竟然包含着全部的站点名称和对应的代码,所以我们只需要把这些东西解析一下,就可以得到车站和对应的代码了.

所以我们就有了思路了,我们应该先用requests来获取这个页面,然后再用正则表达式来把对应的信息解析一下,然后进行一些处理就好了,关于正则表达式的使用,不会的童鞋还是去百度百度吧~ 根据网页上面的信息,我们可以写出如下正则表达式:

([\u4e00-\u9fa5]+)\|([A-Z]+)

先匹配汉字,然后后面匹配字母,中间有一个分隔符,完整的代码.

我们建一个文件parser_stations.py解析:

使用方法是运行这个文件,然后把这个文件的结果重定向到一个新文件:

python parser_stations.py > stations.py

然后对新的文件进行处理:

这样我们就可以通过stations.py里面的两个函数进行获取操作了,这样的话我们就可以构造出我们需要的URL

1
2
3
4
5
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)

解析火车票信息

我们现在已经获取到了url,我们现在就可以对其访问,还是用requests来访问我们构造好的URL,比如我们构造的是2017-09-02从西安到北京的火车,那么url是:https://kyfw.12306.cn/otn/leftTicket/query?leftTicketDTO.train_date=2017-09-02&leftTicketDTO.from_station=XAY&leftTicketDTO.to_station=BJP&purpose_codes=ADULT 我们进入后会看到: 很明显,这是一个json信息,我们用requests自带的json解析器来进行解析,会得到一个列表:

1
2
3
r=requests.get(url,verify=False,headers=headers)
r.encoding=r.apparent_encoding
raw_trains=r.json()['data']['result'] 

这个列表里面:

  • 6,7元素代表起点和终点的名称
  • 8,9元素代表这趟列车出发和到达的时间
  • 10号元素代表,列车的用时
  • 31代表一等座,30代表二等座
  • 23,28,29代表软卧,硬卧,硬座
  • 26代表无座的信息

我们知道了每个元素代表的信息,只需要把它们呈现出来就可以了,我们先创建一个prettytable对象:

1
2
pt=PrettyTable() #初始化一个prettytable对象
pt._set_field_names('车次 车站 时间 历时 一等座 二等座 软卧 硬卧 硬座 无座'.split())

然后弄好表格的标题后,插入列:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
for raw_train in raw_trains:                   #对每一趟列车进行解析
    data_list=raw_train.split('|')
    train_no=data_list[3]
    initial=train_no[0].lower()  #获取首字母,表示车次
    if not options or initial in options:   #如果没有设置options或者首字母在options里面
        from_station_code=data_list[6]
        to_station_code=data_list[7]
        start_time=data_list[8]
        arrive_time=data_list[9]
        time_duration=data_list[10]
        first_class_seat=data_list[31] if data_list[31] else '--'
        second_class_seat=data_list[30] if data_list[30] else '--'
        soft_sleep=data_list[23] if data_list[23] else '--'
        hard_sleep=data_list[28] if data_list[28] else '--'
        hard_seat=data_list[29] if data_list[29] else '--'
        no_seat=data_list[26] if data_list[26] else '--'
        pt.add_row([
                    Fore.YELLOW + train_no + Fore.RESET,
                    '\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)

在这里面用了colorama来对命令行里面的字符进行上色,相关用法去百度找找就行了~


代码长得比较丑陋,还请不要嫌弃,主要作为练手之用,所以懒得重构代码了,相关的源代码已经上传至Github,欢迎提出问题和Bug…

(^▽^)


最后修改于 2017-09-02

知识共享许可协议
本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。