12306搶票腳本開發(fā)(五)中文火車站名到火車站代號的轉(zhuǎn)換


文章地址 :

12306搶票腳本開發(fā)(一)提綱
12306搶票腳本開發(fā)(二)解析火車站代號并分析查詢的HTTP請求
12306搶票腳本開發(fā)(三)實現(xiàn)一個簡單的查詢腳本
12306搶票腳本開發(fā)(四)完善上節(jié)課的代碼并面向?qū)ο?/a>
12306搶票腳本開發(fā)(五)更友好的使用方式
12306搶票腳本開發(fā)(六)更友好的時間輸入方式
12306搶票腳本開發(fā)(七)將前幾節(jié)課的成果結(jié)合起來實現(xiàn)一個完整的工具


簡介 :

為了能讓上節(jié)課的代碼能適合更多的人使用 , 這里需要做幾件事 :

1. 能解析火車站中文名
2. 能解析更友好的時間
3. 默認查詢的是成人票 , 當(dāng)用戶指定要查詢學(xué)生票的時候才查詢學(xué)生票
4. 將交互的方式做以調(diào)整 (接收命令行參數(shù))

首先看第一個功能 : 解析火車站的中文名

我們首先將之前分析中用到的那個保存中文名和代號的文件下載到本地
然后嘗試解析這個文件 , 并和用戶的輸入進行匹配 , 這里為了方便就先編寫一個簡單的函數(shù)

下載這個文件 : 
https://kyfw.12306.cn/otn/resources/js/framework/station_name.js?station_version=1.8997
我們可以看到這個 URL 是帶參數(shù)的 , 就是說 , 隨著時間的推移
火車站的數(shù)據(jù)可能會有更新 , 因此這里需要用一個版本信息來控制下載的文件
這里的版本是 station_version=1.8997
那么我們將來的程序運行的時候應(yīng)該要保證每一次的這個文件都是最新的
因此我們首先要獲取最新的版本號 , 然后再根據(jù)版本號去下載這個文件
我們知道 HTML 中可以引用外部的 JS 代碼 , 需要將這個 JS 文件的 URL 填寫在 : 
<script>標簽的 src 屬性中 , 當(dāng)瀏覽器解析到這個標簽的時候 , 就會發(fā)起一個 HTTP 請求來向服務(wù)器請求這個文件
那么只要我們能得到主頁的 HTML , 解析這個 HTML 文檔 , 去尋找鏈接 station_name.js 的 script 標簽
這個標簽的 src 屬性就是我們要請求的文件 , 這個時候就可以保證使用的火車站的信息是和 12306 官網(wǎng)是一致的了

tools.py

#!/usr/bin/env python
# encoding:utf-8

import requests
import bs4
import logging

def getStationNamesVersion():
    '''
    獲取 station_names.js 這個文件最新的版本號
    '''
    logging.captureWarnings(True)
    url = "https://kyfw.12306.cn/otn/"
    station_name_version = "" # 先初始化為 0 , 防止沒有獲取到的時候不能正常返回
    response = requests.get(url, verify=False)
    content = response.text.encode("UTF-8")
    soup = bs4.BeautifulSoup(content, "html.parser")
    scripts = soup.findAll("script")
    srcs = [] # 保存 HTML 中所有的 script 標簽的 src 屬性
    for i in scripts:
        try: # 這里使用 try 是因為有的 script 標簽并沒有 src 這個屬性
            src = i['src']
            srcs.append(src)
        except:
            pass
    for i in srcs: # 這里設(shè)計地比較有擴展性 , 如果還要獲取別的某個文件的版本 , 只需要在循環(huán)中添加判斷即可
        if "station_name" in i: # 找到含有 station_names 的一條 src
            station_name_version = i.split("station_version=")[1] # 截取版本號
            # print "成功獲取到車站信息版本 :" , station_name_version # 打印日志
    return station_name_version

def getUrlForStationNames(station_name_version):
    '''
    構(gòu)建用于下載 station_names.js 這個文件的地址
    '''
    return "https://kyfw.12306.cn/otn/resources/js/framework/station_name.js?station_version=" + station_name_version

def downloadFile(url, filename):
    '''
    下載文件并保存到本地
    '''
    logging.captureWarnings(True)
    f = open(filename, "a");
    f.write(requests.get(url, verify=False).text.encode("UTF-8"))
    f.close()

然后我們來實現(xiàn)一個測試上述函數(shù)的腳本 :

#!/usr/bin/env python
# encoding:utf-8

import tools
import os


# 獲取官網(wǎng)的這個文件的版本
print "正在獲取官網(wǎng)的火車站信息文件版本..."
station_names_version = tools.getStationNamesVersion()
print "獲取成功 !"
print "官網(wǎng)版本號 : [",(station_names_version),"]"

# 比對本地文件
print "正在獲取本地緩存文件文件名..."
local_file_name = ""
local_file_version = ""
for filename in os.listdir("./"):
    if filename.endswith("_station_names.dat"):
        local_file_name = filename
if local_file_name != "":
    print "獲取成功 ! 本地文件名 : [", local_file_name, "]"
    print "正在解析本地文件版本號..."
    local_file_version = local_file_name.split("_")[0]
    print "本地版本號 : [", local_file_version, "]"
else:
    print "本地沒有緩存文件 , 準備開始下載..."

# 下載文件 , 保存文件名以版本開始 (便于下次運行的時候比對)
if local_file_version == "":
        print "官網(wǎng)火車站文件更新 , 正在下載..."
        tools.downloadFile(tools.getUrlForStationNames(tools.getStationNamesVersion()), station_names_version+"_"+"station_names.dat")
else:
    if local_file_version != station_names_version:
        print "官網(wǎng)火車站文件更新 , 正在下載..."
        tools.downloadFile(tools.getUrlForStationNames(tools.getStationNamesVersion()), station_names_version+"_"+"station_names.dat")
    else:
        print "本地文件已最新 , 直接使用!"

# 讀取文件
print "正在讀取文件..."
station_names = open("./" + station_names_version + "_" + "station_names.dat", "r")
content = station_names.read()
content = content[20:-2] # 去掉多余的 js 關(guān)鍵字 , 只提取出字符串內(nèi)容
print "正在解析站點信息..."
stations = content.split("@")
print "解析成功 ! 總站點數(shù) : [ " + str(len(stations)) + " ]"

運行效果 :

Paste_Image.png

現(xiàn)在我們已經(jīng)能下載這個文件了 , 我們接下來就要解析這個文件 :
根據(jù)上幾節(jié)課的分析 , 我們已經(jīng)知道了 :

1. 這是一個 js 文件 , 其中只定義了一個字符串變量 , 而我們只需要關(guān)注這個字符串 , 因此需要對這個 js 文件的內(nèi)容進行處理
2. 所有的火車站之間用 '@' 分隔
3. 每一單獨的火車站的字段應(yīng)該是有 6 個 , 每一個之間都以 '|' 分隔
4. 其中 : ("@bjb|北京北|VAP|beijingbei|bjb|0")
    字段0 : 火車站名稱漢語拼音首字母
    字段1 : 火車站名稱漢語
    字段2 : 在查票的時候火車站的代碼 (比如說 : 上海的代碼即為 SHH)
    字段3 : 火車站名稱漢語拼音
    字段4 : 火車站名稱漢語拼音首字母 (模糊匹配 : 比如說輸入 北京南站 , 那么有可能也有 北京站 的信息)
    字段5 : 火車站編號(數(shù)字的序號 , 應(yīng)該是鐵道部或者網(wǎng)站自己定義的 , 應(yīng)該是用于唯一標識某一個火車站)

那么我們接下來要實現(xiàn)的代碼的功能就是 : 輸入火車站名稱漢語(字段1) , 能返回火車站查詢時用的代碼(字段2)
開始寫吧~

#!/usr/bin/env python
# encoding:utf-8

import tools
import os


# 獲取官網(wǎng)的這個文件的版本
print "正在獲取官網(wǎng)的火車站信息文件版本..."
station_names_version = tools.getStationNamesVersion()
print "獲取成功 !"
print "官網(wǎng)版本號 : [",(station_names_version),"]"

# 比對本地文件
print "正在獲取本地緩存文件文件名..."
local_file_name = ""
local_file_version = ""
for filename in os.listdir("./"):
    if filename.endswith("_station_names.dat"):
        local_file_name = filename
if local_file_name != "":
    print "獲取成功 ! 本地文件名 : [", local_file_name, "]"
    print "正在解析本地文件版本號..."
    local_file_version = local_file_name.split("_")[0]
    print "本地版本號 : [", local_file_version, "]"
else:
    print "本地沒有緩存文件 , 準備開始下載..."

# 下載文件 , 保存文件名以版本開始 (便于下次運行的時候比對)
if local_file_version == "":
        print "官網(wǎng)火車站文件更新 , 正在下載..."
        tools.downloadFile(tools.getUrlForStationNames(station_names_version), station_names_version+"_"+"station_names.dat")
else:
    if local_file_version != station_names_version:
        print "官網(wǎng)火車站文件更新 , 正在下載..."
        tools.downloadFile(tools.getUrlForStationNames(station_names_version), station_names_version+"_"+"station_names.dat")
    else:
        print "本地文件已最新 , 直接使用!"


def getStationCode(station_name):
    result = ""
    # 讀取文件
    print "正在讀取文件..."
    station_names = open("./" + station_names_version + "_" + "station_names.dat", "r")
    content = station_names.read()
    station_names.close()
    content = content[20:-2] # 去掉多余的 js 關(guān)鍵字 , 只提取出字符串內(nèi)容
    print "正在解析站點信息..."
    stations = content.split("@")[1:] # 由于這個文件開頭就是 '@' , 因此需要去掉第一個元素
    print "解析成功 ! 總站點數(shù) : [ " + str(len(stations)) + " ]"
    for station in stations:
        fields = station.split("|")
        # station_name_pinyin_simple = fields[0] 
        station_name_standard = fields[1] 
        station_code = fields[2] 
        # station_name_pinyin = fields[3] 
        # station_name_pinyin_simple_fuzz = fields[4] 
        # station_num = fields[5]
        if station_name == station_name_standard:
            result = station_code
            return result
    return result


print "[ 北京 ] -> [" + getStationCode("北京") + "]"
print "[ 上海 ] -> [" + getStationCode("上海") + "]"
print "[ 廣州 ] -> [" + getStationCode("廣州") + "]"
print "[ 深圳 ] -> [" + getStationCode("深圳") + "]"
print "[ 成都 ] -> [" + getStationCode("成都") + "]"
print "[ 哈爾濱 ] -> [" + getStationCode("哈爾濱") + "]"
print "[ 西安 ] -> [" + getStationCode("西安") + "]"

運行效果


Paste_Image.png

這里其實還有可以優(yōu)化的地方
比如說 :

1. 函數(shù)每調(diào)用一次就有一次 IO , 應(yīng)該優(yōu)化成只進行一次 IO
2. 暫時不支持模糊查詢
3. 暫時不支持通過拼音首字母查詢

關(guān)于問題 2 , 我們可以這樣解決 :

如果一個城市有好多個火車站 , 那么它們的命名一定是這樣的 : (例如 北京)
北京站
北京北站
北京南站
北京東站
北京西站
哈 , 有規(guī)律了吧 , 都是以北京開頭的
那么我們要實現(xiàn)這樣的查詢 , 只需要很簡單地將上述代碼中判斷函數(shù)參數(shù)和文件中的火車站名是否相等的函數(shù)改成 startswith
這樣就可以進行模糊查詢 , 但是這樣做的話 , 就需要遍歷完整個文件
而且需要返回一個列表

完整的代碼在下方 , 運行結(jié)果為 :

Paste_Image.png

代碼 :

tools.py

#!/usr/bin/env python
# encoding:utf-8

import requests
import bs4
import logging

def getStationNamesVersion():
    '''
    獲取 station_names.js 這個文件最新的版本號
    '''
    logging.captureWarnings(True)
    url = "https://kyfw.12306.cn/otn/"
    station_name_version = "" # 先初始化為 0 , 防止沒有獲取到的時候不能正常返回
    response = requests.get(url, verify=False)
    content = response.text.encode("UTF-8")
    soup = bs4.BeautifulSoup(content, "html.parser")
    scripts = soup.findAll("script")
    srcs = [] # 保存 HTML 中所有的 script 標簽的 src 屬性
    for i in scripts:
        try: # 這里使用 try 是因為有的 script 標簽并沒有 src 這個屬性
            src = i['src']
            srcs.append(src)
        except:
            pass
    for i in srcs: # 這里設(shè)計地比較有擴展性 , 如果還要獲取別的某個文件的版本 , 只需要在循環(huán)中添加判斷即可
        if "station_name" in i: # 找到含有 station_names 的一條 src
            station_name_version = i.split("station_version=")[1] # 截取版本號
            # print "成功獲取到車站信息版本 :" , station_name_version # 打印日志
    return station_name_version

def getUrlForStationNames(station_name_version):
    '''
    構(gòu)建用于下載 station_names.js 這個文件的地址
    '''
    return "https://kyfw.12306.cn/otn/resources/js/framework/station_name.js?station_version=" + station_name_version

def downloadFile(url, filename):
    '''
    下載文件并保存到本地
    '''
    logging.captureWarnings(True)
    f = open(filename, "a");
    f.write(requests.get(url, verify=False).text.encode("UTF-8"))
    f.close()

test.py

#!/usr/bin/env python
# encoding:utf-8

import tools
import os


# 獲取官網(wǎng)的這個文件的版本
print "正在獲取官網(wǎng)的火車站信息文件版本..."
station_names_version = tools.getStationNamesVersion()
print "獲取成功 !"
print "官網(wǎng)版本號 : [",(station_names_version),"]"

# 比對本地文件
print "正在獲取本地緩存文件文件名..."
local_file_name = ""
local_file_version = ""
for filename in os.listdir("./"):
    if filename.endswith("_station_names.dat"):
        local_file_name = filename
if local_file_name != "":
    print "獲取成功 ! 本地文件名 : [", local_file_name, "]"
    print "正在解析本地文件版本號..."
    local_file_version = local_file_name.split("_")[0]
    print "本地版本號 : [", local_file_version, "]"
else:
    print "本地沒有緩存文件 , 準備開始下載..."

# 下載文件 , 保存文件名以版本開始 (便于下次運行的時候比對)
if local_file_version == "":
        print "官網(wǎng)火車站文件更新 , 正在下載..."
        tools.downloadFile(tools.getUrlForStationNames(station_names_version), station_names_version+"_"+"station_names.dat")
else:
    if local_file_version != station_names_version:
        print "官網(wǎng)火車站文件更新 , 正在下載..."
        tools.downloadFile(tools.getUrlForStationNames(station_names_version), station_names_version+"_"+"station_names.dat")
    else:
        print "本地文件已最新 , 直接使用!"


def getStationCodes(station_name):
    results = []
    # 讀取文件
    print "正在讀取文件..."
    station_names = open("./" + station_names_version + "_" + "station_names.dat", "r")
    content = station_names.read()
    station_names.close()
    content = content[20:-2] # 去掉多余的 js 關(guān)鍵字 , 只提取出字符串內(nèi)容
    print "正在解析站點信息..."
    stations = content.split("@")[1:] # 由于這個文件開頭就是 '@' , 因此需要去掉第一個元素
    print "解析成功 ! 總站點數(shù) : [ " + str(len(stations)) + " ]"
    for station in stations:
        fields = station.split("|")
        # station_name_pinyin_simple = fields[0] 
        station_name_standard = fields[1] 
        station_code = fields[2] 
        # station_name_pinyin = fields[3] 
        # station_name_pinyin_simple_fuzz = fields[4] 
        # station_num = fields[5]
        if station_name_standard.startswith(station_name):
            
            results.append({"station_code":station_code, "station_name":station_name_standard})
    return results

def printStationInfo(station_info):
    for result in station_info:
        print "[ %s ] -> [ %s ]" % (result["station_name"], result["station_code"])

print "=" * 18 + " [ 北京 ] " + "=" * 18
printStationInfo(getStationCodes("北京"))

print "=" * 18 + " [ 上海 ] " + "=" * 18
printStationInfo(getStationCodes("上海"))

print "=" * 18 + " [ 天津 ] " + "=" * 18
printStationInfo(getStationCodes("天津"))

print "=" * 18 + " [ 成都 ] " + "=" * 18
printStationInfo(getStationCodes("成都"))

print "=" * 18 + " [ 哈爾濱 ] " + "=" * 18
printStationInfo(getStationCodes("哈爾濱"))

print "=" * 18 + " [ 西安 ] " + "=" * 18
printStationInfo(getStationCodes("西安"))

現(xiàn)在我們已經(jīng)可以完成從中文的火車站名到火車站代號的轉(zhuǎn)換了
也就是說 , 我們最開始提出的問題中的問題 1 已經(jīng)解決
這樣的話 , 其實我們大部分的內(nèi)容已經(jīng)完成了, 這就已經(jīng)可以制作成一個很友好的小工具了
如果再能配合 Linux 的定時任務(wù)就可以實現(xiàn)對火車票進行輪詢的功能
當(dāng)然在查到火車票的時候要對用戶進行通知 , 這我們會在以后的課程中進行介紹


總結(jié) :

本節(jié)課我們主要實現(xiàn)了 : 中文火車站名到火車站代號的轉(zhuǎn)換
既可以進行精確的查詢
也可以進行模糊的查詢


預(yù)告 :

下節(jié)課我們來實現(xiàn)讓程序能接受更多格式的時間輸入 , 感謝大家的支持~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異供炼,居然都是意外死亡一屋,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進店門袋哼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來冀墨,“玉大人,你說我怎么就攤上這事涛贯》碳危” “怎么了?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵弟翘,是天一觀的道長虫腋。 經(jīng)常有香客問我,道長稀余,這世上最難降的妖魔是什么悦冀? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮睛琳,結(jié)果婚禮上盒蟆,老公的妹妹穿的比我還像新娘。我一直安慰自己师骗,他們只是感情好历等,可當(dāng)我...
    茶點故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著丧凤,像睡著了一般募闲。 火紅的嫁衣襯著肌膚如雪步脓。 梳的紋絲不亂的頭發(fā)上愿待,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天,我揣著相機與錄音靴患,去河邊找鬼仍侥。 笑死,一個胖子當(dāng)著我的面吹牛鸳君,可吹牛的內(nèi)容都是我干的农渊。 我是一名探鬼主播,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼或颊,長吁一口氣:“原來是場噩夢啊……” “哼砸紊!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起囱挑,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤醉顽,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后平挑,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體游添,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡系草,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了唆涝。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片找都。...
    茶點故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖廊酣,靈堂內(nèi)的尸體忽然破棺而出能耻,到底是詐尸還是另有隱情,我是刑警寧澤亡驰,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布嚎京,位于F島的核電站,受9級特大地震影響隐解,放射性物質(zhì)發(fā)生泄漏鞍帝。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一煞茫、第九天 我趴在偏房一處隱蔽的房頂上張望帕涌。 院中可真熱鬧,春花似錦续徽、人聲如沸蚓曼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽纫版。三九已至,卻和暖如春客情,著一層夾襖步出監(jiān)牢的瞬間其弊,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工膀斋, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留梭伐,地道東北人。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓仰担,卻偏偏與公主長得像糊识,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子摔蓝,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,802評論 2 345

推薦閱讀更多精彩內(nèi)容