Python之12306余票查詢

簡單的Python學(xué)習(xí),用Python完成一個(gè)12306余票查詢~

Python之12306余票查詢

參考資料來自Python 實(shí)現(xiàn)火車票查詢工具

需要用到的第三方庫

  • requests,使用 Python 訪問 HTTP 資源的必備庫。

  • docopt,Python3 命令行參數(shù)解析工具。

  • prettytable, 格式化信息打印工具,能讓你像 MySQL 那樣打印數(shù)據(jù)。

效果圖

屏幕快照 2018-10-05 下午5.12.19.png

分析12306接口請求

屏幕快照 2018-10-05 下午8.05.36.png

這是一個(gè)標(biāo)準(zhǔn)的12306查詢余票界面,本次Demo使用的瀏覽器是Safari,首先打開Safari的開發(fā)選項(xiàng)
屏幕快照 2018-10-05 下午8.07.18.png
屏幕快照 2018-10-05 下午8.08.58.png

在網(wǎng)頁檢查器中我們刷新網(wǎng)頁,可以觀察到下面這個(gè)接口~


屏幕快照 2018-10-05 下午8.10.08.png

單獨(dú)復(fù)制url打開就能發(fā)現(xiàn)這就是12306余票數(shù)據(jù)請求接口,分析一下這個(gè)接口的類型是Get,請求參數(shù)是

  • leftTicketDTO.train_date: 2018-10-30

  • leftTicketDTO.from_station: BJP

  • leftTicketDTO.to_station: LYF

  • purpose_codes: ADULT

hoho~,萬里長征第一步走的還是比較順利的,下面就用我們的python程序來請求這個(gè)接口看看~

Requests請求接口

import requests

url = 'https://kyfw.12306.cn/otn/leftTicket/queryA?leftTicketDTO.train_date=2018-10-30&leftTicketDTO.from_station=BJP&leftTicketDTO.to_station=LYF&purpose_codes=ADULT'
r= requests.get(url)
print(r.json())

關(guān)于requests庫,我剛接觸python覺得應(yīng)該是現(xiàn)在網(wǎng)絡(luò)交互中必選庫吧,上述代碼就請求12306的接口并且輸出結(jié)果為json

{
    "data":{
        "flag":"1",
        "map":{
            "BXP":"北京西",
            "GLF":"關(guān)林",
            "LLF":"洛陽龍門",
            "LYF":"洛陽"
        },
        "result":Array[18]
    },
    "httpstatus":200,
    "messages":"",
    "status":true
}

數(shù)據(jù)結(jié)構(gòu)如上,我們只需要result中的結(jié)果就ok,python也可以很方便的解析json這一點(diǎn)我覺得比java舒服一些。

r= requests.get(url)
print(r.json()['data']['result'])

我們現(xiàn)在已經(jīng)有了數(shù)據(jù),接下來則是把我們轉(zhuǎn)換過的結(jié)果顯示出來,萬里長征第二步~

PrettyTable庫

PrettyTable 是python中的一個(gè)第三方庫,可用來生成美觀的ASCII格式的表格,十分實(shí)用。以下為官方介紹:

A simple Python library for easily displaying tabular data in a visually appealing ASCII table format.PrettyTable is a simple Python library designed to make it quick and easy to represent tabular data in visually appealing ASCII tables. It was inspired by the ASCII tables used in the PostgreSQL shell psql. PrettyTable allows for selection of which columns are to be printed, independent alignment of columns (left or right justified or centred) and printing of “sub-tables” by specifying a row range.

使用方法也是非常簡單,先寫個(gè)小小的Demo

pt = PrettyTable()
pt.field_names=['測試1','測試2','測試3','測試4','測試5']
pt.add_row(['測試結(jié)果1','測試結(jié)果2', '測試結(jié)果3', '測試結(jié)果4','測試結(jié)果5'])
print(pt)

+-----------+-----------+-----------+-----------+-----------+
|   測試1   |   測試2   |   測試3   |   測試4   |   測試5   |
+-----------+-----------+-----------+-----------+-----------+
| 測試結(jié)果1 | 測試結(jié)果2 | 測試結(jié)果3 | 測試結(jié)果4 | 測試結(jié)果5 |
+-----------+-----------+-----------+-----------+-----------+

通過field_names設(shè)置元數(shù)據(jù)(其實(shí)并不知道怎么形容。。。

然后不斷的add_row添加行,那么我們的12306也就可以這樣寫啦,

# coding: utf-8

import requests, datetime
from prettytable import PrettyTable


def _get_time(from_time, trains_time):
    try:
        time1 = datetime.datetime.strptime(from_time, "%H:%M")
        time2 = datetime.datetime.strptime(trains_time, "%H:%M")
    except ValueError as e:
        return '異常時(shí)間啦'
    hour = 0
    if time1.minute + time2.minute >= 60:
        hour = 1

    if time1.hour + time2.hour + hour > 24:
        return "次日到達(dá)"
    else:
        return "今日到達(dá)"


def _get_seat_count(count):
    if not str(count).strip():
        return '--'
    else:
        return count


url = 'https://kyfw.12306.cn/otn/leftTicket/queryA?leftTicketDTO.train_date=2018-10-30&leftTicketDTO.from_station=SHH&leftTicketDTO.to_station=LYF&purpose_codes=ADULT'
r = requests.get(url)
results = r.json()['data']['result']

header = '車次 車站 時(shí)間 歷時(shí) 商務(wù)座特等座 一等座 二等座 高級軟臥 軟臥 動臥 硬臥'.split()
pt = PrettyTable()
pt._set_field_names(header)
for raw_train in results:
    trains_info = str(raw_train).split('|')
    from_station = trains_info[6]
    to_station = trains_info[7]
    from_time = trains_info[8]
    to_time = trains_info[9]
    trains_time = trains_info[10]
    pt.add_row([trains_info[3], from_station + "\n" + to_station, from_time + "\n" + to_time,
                trains_time + "\n" + _get_time(from_time, trains_time),
                _get_seat_count(trains_info[32]), _get_seat_count(trains_info[31]),
                _get_seat_count(trains_info[30]), _get_seat_count(trains_info[21]),
                _get_seat_count(trains_info[23]), _get_seat_count(trains_info[33]),
                _get_seat_count(trains_info[28])])

print(pt)


+-------+----------+-------+----------+--------------+--------+--------+----------+------+------+------+
|  車次 |   車站   |  時(shí)間 |   歷時(shí)   | 商務(wù)座特等座 | 一等座 | 二等座 | 高級軟臥 | 軟臥 | 動臥 | 硬臥 |
+-------+----------+-------+----------+--------------+--------+--------+----------+------+------+------+
|  G427 |  北京西  | 06:20 |  03:26   |      11      |   2    |   有   |    --    |  --  |  --  |  --  |
|       | 洛陽龍門 | 09:46 | 今日到達(dá) |              |        |        |          |      |      |      |
|  G651 |  北京西  | 06:58 |  04:07   |      有      |   有   |   有   |    --    |  --  |  --  |  --  |
|       | 洛陽龍門 | 11:05 | 今日到達(dá) |              |        |        |          |      |      |      |
|  K269 |  北京西  | 07:00 |  10:47   |      --      |   --   |   --   |    --    |  有  |  --  |  有  |
|       |   洛陽   | 17:47 | 今日到達(dá) |              |        |        |          |      |      |      |
|  K507 |  北京西  | 07:12 |  10:05   |      --      |   --   |   --   |    --    |  10  |  --  |  有  |
|       |   洛陽   | 17:17 | 今日到達(dá) |              |        |        |          |      |      |      |
|  K817 |  北京西  | 08:01 |  11:11   |      --      |   --   |   --   |    --    |  9   |  --  |  有  |
|       |   洛陽   | 19:12 | 今日到達(dá) |              |        |        |          |      |      |      |
|  G671 |  北京西  | 08:16 |  04:12   |      9       |   有   |   有   |    --    |  --  |  --  |  --  |
|       | 洛陽龍門 | 12:28 | 今日到達(dá) |              |        |        |          |      |      |      |
|  G307 |  北京西  | 09:38 |  04:09   |      8       |   有   |   有   |    --    |  --  |  --  |  --  |
|       | 洛陽龍門 | 13:47 | 今日到達(dá) |              |        |        |          |      |      |      |
|  G655 |  北京西  | 09:43 |  04:17   |      19      |   有   |   有   |    --    |  --  |  --  |  --  |
|       | 洛陽龍門 | 14:00 | 今日到達(dá) |              |        |        |          |      |      |      |
|  G429 |  北京西  | 10:45 |  04:01   |      7       |   2    |   有   |    --    |  --  |  --  |  --  |
|       | 洛陽龍門 | 14:46 | 今日到達(dá) |              |        |        |          |      |      |      |
|  K267 |  北京西  | 13:22 |  11:23   |      --      |   --   |   --   |    --    |  無  |  --  |  有  |
|       |   關(guān)林   | 00:45 | 今日到達(dá) |              |        |        |          |      |      |      |
|  G673 |  北京西  | 14:35 |  04:17   |      8       |   有   |   有   |    --    |  --  |  --  |  --  |
|       | 洛陽龍門 | 18:52 | 今日到達(dá) |              |        |        |          |      |      |      |
|  G661 |  北京西  | 14:48 |  04:09   |      3       |   無   |   有   |    --    |  --  |  --  |  --  |
|       | 洛陽龍門 | 18:57 | 今日到達(dá) |              |        |        |          |      |      |      |
|  G663 |  北京西  | 15:45 |  03:54   |      8       |   有   |   有   |    --    |  --  |  --  |  --  |
|       | 洛陽龍門 | 19:39 | 今日到達(dá) |              |        |        |          |      |      |      |
|  T55  |  北京西  | 15:50 |  08:47   |      --      |   --   |   --   |    --    |  3   |  --  |  2   |
|       |   洛陽   | 00:37 | 今日到達(dá) |              |        |        |          |      |      |      |
|  Z75  |  北京西  | 15:57 |  07:05   |      --      |   --   |   --   |    --    |  2   |  --  |  無  |
|       |   洛陽   | 23:02 | 今日到達(dá) |              |        |        |          |      |      |      |
|  G665 |  北京西  | 16:00 |  04:11   |      7       |   有   |   有   |    --    |  --  |  --  |  --  |
|       | 洛陽龍門 | 20:11 | 今日到達(dá) |              |        |        |          |      |      |      |
|  T231 |  北京西  | 18:50 |  08:03   |      --      |   --   |   --   |    --    |  無  |  --  |  18  |
|       |   洛陽   | 02:53 | 次日到達(dá) |              |        |        |          |      |      |      |
| K1363 |  北京西  | 22:06 |  09:38   |      --      |   --   |   --   |    --    |  無  |  --  |  有  |
|       |   洛陽   | 07:44 | 次日到達(dá) |              |        |        |          |      |      |      |
+-------+----------+-------+----------+--------------+--------+--------+----------+------+------+------+

12306的接口格式我個(gè)人覺得有點(diǎn)非人類…本例用的組合方法可能并不是最優(yōu)方法,歡迎大家提供更好的方法~,萬里長征三步走完啦,最后一步就是如何動態(tài)的調(diào)整出發(fā)地和目的地。

docopt

一個(gè)參數(shù)解析庫,可以看下Python 參數(shù)解析庫 docopt 簡單使用圖文教程。

利用這個(gè)庫的便利性我們可以很方便的提取出想要的參數(shù)信息,

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

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

Options:
    -h,--help   顯示幫助菜單
    -g          高鐵
    -d          動車
    -t          特快
    -k          快速
    -z          直達(dá)

Example:
    tickets 北京 上海 2016-10-10
    tickets -dg 成都 南京 2016-10-10
"""

def cil():
    """command-line interface"""
    arguments = docopt(__doc__)
    from_station = arguments['<from>']
    to_station = arguments['<to>']
    date = arguments['<date>']
    print("from_station:" + from_station + "\nto_station:" + to_station + "\ndate:" + date)


if __name__ == '__main__':
    cil()
    
python tickets_test.py -dg 上海 洛陽 2018-10-31

from_station:上海
to_station:洛陽
date:2018-10-31

這里其實(shí)還有一個(gè)問題就是我們提取出來的都是中文,而12306接口需要的是縮寫,這里我們可以通過12306的另一個(gè)接口解決~

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

python parse_station.py > stations.py


stations = {'一間堡': 'YJT',
            '一面坡': 'YPB',
            '一面山': 'YST',
            '七臺河': 'QTB',
            '七甸': 'QDM',
            '七營': 'QYJ',
            '七里河': 'QLD',
            '萬樂': 'WEB',
            '萬發(fā)屯': 'WFB',
            '萬寧': 'WNQ',
            '萬州': 'WYW',
            '萬州北': 'WZE',
            '萬年': 'WWG',
            '萬源': 'WYY',
            '三義井': 'OYD',
            '三井子': 'OJT',
            '三亞': 'SEQ',
            '三元區(qū)': 'SMS',
            '三關(guān)口': 'OKJ',
            '三十家': 'SRD',
            '三十里堡': 'SST',
            '三原': 'SAY',
            '三合莊': 'SVP',
            .....
            }

所有的條件已經(jīng)集齊,我們走最后一步吧~

汽車人合體

def cil():
    """command-line interface"""
    arguments = docopt(__doc__)
    from_station = stations.get(arguments['<from>'])
    to_station = stations.get(arguments['<to>'])
    date = arguments['<date>']
    print("from_station:" + from_station + "\nto_station:" + to_station)
    # 構(gòu)建URL
    url = 'https://kyfw.12306.cn/otn/leftTicket/queryA?leftTicketDTO.train_date={}&leftTicketDTO.from_station={}&leftTicketDTO.to_station={}&purpose_codes=ADULT'.format(
        date, from_station, to_station
    )
    r = requests.get(url, verify=False)
    results = r.json()['data']['result']
    header = '車次 車站 時(shí)間 歷時(shí) 商務(wù)座特等座 一等座 二等座 高級軟臥 軟臥 動臥 硬臥'.split()
    pt = PrettyTable()
    pt._set_field_names(header)
    for raw_train in results:
        trains_info = str(raw_train).split('|')
        from_station = trains_info[6]
        to_station = trains_info[7]
        from_time = trains_info[8]
        to_time = trains_info[9]
        trains_time = trains_info[10]
        pt.add_row([trains_info[3], from_station + "\n" + to_station, from_time + "\n" + to_time,
                    trains_time + "\n" + _get_time(from_time, trains_time),
                    _get_seat_count(trains_info[32]), _get_seat_count(trains_info[31]),
                    _get_seat_count(trains_info[30]), _get_seat_count(trains_info[21]),
                    _get_seat_count(trains_info[23]), _get_seat_count(trains_info[33]),
                    _get_seat_count(trains_info[28])])

    print(pt)



if __name__ == '__main__':
    cil()

以上是本次Demo的完整調(diào)用,作為一個(gè)Android開發(fā)我覺得python還是挺有意思的~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容