python實戰之通過爬蟲實現火車票查詢


前言:

    學了挺近的python了,一直在初級徘徊不前,想着應該找點實戰性的案例來操練一下,以便熟悉各模塊的使用;在網上找到了一些有關通過爬蟲實現火車票查詢的,就拿來參考練練手了。

    最終想要的實現效果就是用戶通過在命令行輸入相關的命令,然后將查詢到的車次信息打印輸出到屏幕上。命令格式:tickets [-gdtkz] <from> <to> <date> ;並且用戶可以通過輸入[-gdtkz]參數去篩選想要查找的車次類型,默認不添加參數時候輸出全部車次。此次用到的模塊有docopt、prettytable、re、urllib3、requests,其中:

    docopt 模塊:是在 python 中引入了一種針對命令行參數的形式語言模塊,在代碼的最開頭使用 """ """ 文檔注釋的形式寫出符合要求的文檔,就會自動生成對應的 parse。

    prettytable模塊:是 python 中的一個第三方庫,可用來生成美觀的 ASCII 格式的表格,這里主要是用來將爬取到的車次信息按照 ASCII 格式打印到屏幕。

    re模塊:是python的標准庫中表示正則表達式的模塊,用來對爬取到的車次數據進行篩選匹配,得到我們最終想要的數據。

    requests模塊:是用 python 語言編寫的基於 urllib 采用 Apache2 Licensed 開源協議的 HTTP 庫,主要就是用它來獲取12306網站車次信息。

    urllib3模塊:詳解請參考 https://www.cnblogs.com/lincappu/p/12801817.html,這里是因為 requests 模塊在訪問 HTTPS 網站設置移除SSL認證參數 “verify=False” 后,會提示 “InsecureRequestWarning” 警告,在請求代碼前加入 “requests.packages.urllib3.disable_warnings()” 就可以過濾警告。

 

效果截圖:

 

下面就來說一下實現的步驟:

    打開12306網站查詢北京到上海的火車票,並且開啟瀏覽器開發者工具界面,然后找到“Network-XHR”選項,選中左下方框中的鏈接,其中右邊“Headers”框下方中“Request URL”顯示的鏈接就是我們要找的12306火車票查詢URL。

    將其復制出來分析發現,我們只需要修改train_date、from_station和to_station這三個固定參數的值就可以查詢到我們想要的列車信息了,其中train_date是列車的日期,from_station和to_station分別是首發站和終點站,但是from_station和to_station的值卻不是我們常見的中文車站名,分析對比后可以確定它是中文車站的英文編號。因此,我們需要先找到全部站點的英文編號數據。

    經過查找12306頁面發現“station_name.js?station_version=1.9163”行對應的“Response”數據應該是我們需要的數據。

    那么我們就把“Headers”的“Request URL”鏈接地址復制出來貼到瀏覽器上去查看一下,看看是不是我們想要的數據。“https://kyfw.12306.cn/otn/resources/js/framework/station_name.js?station_version=1.9163”

    查看了上面的數據,的確是我們想要的數據,並且這些數據是有一定的規律的,都是通過“|”分隔,這樣我們在用正則去匹配想要的數據時候就比較容易了。好了,既然想要的數據都已經拿到了,那么我們就開始編寫代碼把我們想要的數據提取出來,下面我直接把代碼和執行結果貼出來吧。

 1 #!/usr/bin/env python3
 2 # -*- coding: utf-8 -*-
 3 
 4 import re
 5 import urllib3, requests    # python 訪問 HTTP 資源的必備庫
 6 from pprint import pprint    # 打印出任何python數據結構類和方法的模塊
 7 
 8 
 9 url = "https://kyfw.12306.cn/otn/resources/js/framework/station_name.js?station_version=1.9163"
10 requests.packages.urllib3.disable_warnings()    # requests模塊在訪問HTTPS網站時,如果設置移除SSL認證參數“verify=False”,執行代碼是會提示“InsecureRequestWarning”警告,再請求頁面時加入此段代碼可以屏蔽掉警告信息
11 r = requests.get(url, verify=False)    # 請求12306網站的所有城市的拼音和代號網頁,verify=False參數表示不驗證證書
12 # result = re.findall(r'([A-Z]+)\|([a-z]+)', r.text)    # 通過正則表達式來匹配車站中文拼音和英文編號對應的數據
13 result = re.findall(r"([\u4e00-\u9fa5]+)\|([A-Z]+)", r.text)    # 通過正則表達式來匹配車站中文名和英文編號對應的數據
14 stations = dict(result)    # 將獲取的數據轉成字典
15 # print(stations["上海虹橋"])     # 驗證用
16 """
17     請將下面輸出的結果保存到stations.py中,並在文件開頭添加一行:# coding=gbk
18     否則在調用stations.py文件時,會提示報錯。
19 """
20 print(stations.keys())
21 print(stations.values())

    執行結果如下:

    隨后將輸出的數據保存到另一個文件(stations.py)中,在文件開頭加上一句“# coding=gbk”,並在文件中定義兩函數進行中文名字和英文編碼的對應獲取,如下:

    車站中文名和英文編碼已經拿到了,接下來就可以開始爬取12306網頁的車次數據了,首先我們設計一下用戶調用的接口方式。按照前面所說的我們希望用戶只要輸入出發站、終點站和出發日期就能獲得想要的列車信息,例如要查看2020年11月6日的火車票信息,只需輸入如下:

$ tickets 北京 廣州 2020-11-06

    對其進行抽象可以得到接口如下:

$ tickets <from> <to> <date>

    另外,我們在12306頁面查詢火車票時候可以對車次類型進行篩選,例如選擇高鐵就只顯示當天高鐵的車次信息,同時選擇高鐵和動車就顯示高鐵和動車的車次信息,那么我們就要提供一個選項來查詢特定的一種或者幾種類型的火車,所有我們應該有下面這些選項:

  • -g 高鐵
  • -d 動車
  • -t 特快
  • -k 快速
  • -z 直達

    將這些選項和上面的接口組合起來,最終的接口的樣子應該是這樣:

$ tickets [-gdtkz] <from> <to> <date>

    下面我們直接貼出實現的代碼:

 1 #!/usr/bin/env python3
 2 # -*- coding: utf-8 -*-
 3 
 4 #!/usr/bin/env python3
 5 # -*- coding: utf-8 -*-
 6 
 7 """Train tickets query via command-line.
 8 
 9 Usage:
10     tickets [-gdtkz] <from> <to> <date>
11 
12 Options:
13     -h,--help    顯示幫助信息菜單
14     -g           高鐵
15     -d           動車
16     -t           特快
17     -k           快速
18     -z           直達
19 
20 Example:
21     tickets beijing shanghai 2020-11-05
22 """
23 
24 from docopt import docopt
25 # docopt 模塊是 python3 命令行參數解析工具
26 # docopt 模塊本質上是在 Python 中引入了一種針對命令行參數的形式語言,在代碼的最開頭使用 """ """文檔注釋的形式寫出符合要求的文檔,就會自動生成對應的 parse
27 # 所有出現在 Usage:(區分大小寫)和一個空行之間的文本都會被識別為一個命令組合, Usage 后的第一個字母將會被識別為這個程序的名字,所有命令組合的每一個部分(空格分隔)都會成為字典中的一個key
28 
29 
30 def cli():
31     """command-line interface"""
32     arguments = docopt(__doc__)
33     print(arguments)
34 
35 if __name__ == "__main__":
36     cli()

    通過命令行方式運行上面代碼,得到結果如下:

$ python tickets.py 北京 廣州 2020-11-06
$ python tickets.py -g 北京 廣州 2020-11-06

    接口已經實現了,接下來就是要獲取12306頁面的車次數據了,根據前面分析的只需要修改“https://kyfw.12306.cn/otn/leftTicket/query?leftTicketDTO.train_date=2020-11-06&leftTicketDTO.from_station=BJP&leftTicketDTO.to_station=SHH&purpose_codes=ADULT”鏈接中train_date、from_station和to_station參數的值就可以得到想要查詢的火車票信息。其中from_station和to_station參數的值是英文編號,需要根據用戶輸入的中文車站名去stations.py文件中找到對應的英文編號進行替換,因此需要import stations,然后通過requests模塊去抓取車次數據。實現代碼如下:

 1 #!/usr/bin/env python3
 2 # -*- coding: utf-8 -*-
 3 
 4 #!/usr/bin/env python3
 5 # -*- coding: utf-8 -*-
 6 
 7 """Train tickets query via command-line.
 8 
 9 Usage:
10     tickets [-gdtkz] <from> <to> <date>
11 
12 Options:
13     -h,--help    顯示幫助信息菜單
14     -g           高鐵
15     -d           動車
16     -t           特快
17     -k           快速
18     -z           直達
19 
20 Example:
21     tickets beijing shanghai 2020-11-05
22 """
23 
24 from docopt import docopt
25 # docopt 模塊是 python3 命令行參數解析工具
26 # docopt 模塊本質上是在 Python 中引入了一種針對命令行參數的形式語言,在代碼的最開頭使用 """ """文檔注釋的形式寫出符合要求的文檔,就會自動生成對應的 parse
27 # 所有出現在 Usage:(區分大小寫)和一個空行之間的文本都會被識別為一個命令組合, Usage 后的第一個字母將會被識別為這個程序的名字,所有命令組合的每一個部分(空格分隔)都會成為字典中的一個key
28 import re    # 正則表達式模塊
29 import stations
30 import urllib3, requests    # python 訪問 HTTP 資源的必備庫
31 
32 def cli():
33     """command-line interface"""
34     arguments = docopt(__doc__)
35     # print(arguments)
36     from_stion = stations.get_telecode(arguments["<from>"])    # 調用 get_telecode() 方法根據用戶輸入的起始車站中文名找到對應的英文編號
37     to_stion = stations.get_telecode(arguments["<to>"])    # 調用 get_telecode() 方法根據用戶輸入的終點車站中文名找到對應的英文編號
38     date = arguments["<date>"]    # 獲取用戶輸入的日期
39 
40     # 構建 URL
41     url = ("https://kyfw.12306.cn/otn/leftTicket/query?"
42            "leftTicketDTO.train_date={}&"
43            "leftTicketDTO.from_station={}&"
44            "leftTicketDTO.to_station={}&"
45            "purpose_codes=ADULT").format(date, from_stion, to_stion)
46     headers = {
47         # Cookie 的值自行替換一下,可以通過打開瀏覽器開發者模式復制過來
48         "Cookie": "_uab_collina=160395250285657341202147; JSESSIONID=7C56E896658518A4E5BF99889839D00C; _jc_save_wfdc_flag=dc; _jc_save_fromStation=%u5317%u4EAC%2CBJP; _jc_save_toStation=%u4E0A%u6D77%2CSHH; BIGipServerotn=1725497610.50210.0000; RAIL_EXPIRATION=1604632917257; RAIL_DEVICEID=DeBrCMshZyD9JIK2yazJV4op0oxRXXKpeio_Y27U75ZkWKFwOd6Q_i2JRVBJeN3Q9qQ7ybyTw4Vv3ImAEwdTAAh8XLXL6WGn3irR65rZyYeWtvToLkq8oVAprmAw6OPgPnqI9a9ItALNr0kFjzDkncjjGPINbqfa; BIGipServerpassport=770179338.50215.0000; route=c5c62a339e7744272a54643b3be5bf64; _jc_save_fromDate=2020-11-02; _jc_save_toDate=2020-11-01",
49         "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36"
50     }
51     requests.packages.urllib3.disable_warnings()    # 屏蔽 “InsecureRequestWarning” 警告
52     r = requests.get(url, headers=headers, verify=False)  # 通過 requests 模塊獲取頁面信息,verify=False 參數表示不進行證書驗證
53     raw_trains = r.json()['data']['result']
54     print(raw_trains)
55 
56 
57 if __name__ == "__main__":
58     cli()

    執行結果如下:

    根據獲取到的數據進行分析其車次信息中車次代號、始發站、終點站、出發時間、到達時間以及座位類別等應該是有分別對應的字段,再返回12306網站去查找發現“Sources”有相關的數據信息,如下所示:

    拿到這些信息之后,就開始和抓取到的車次數據以及12306頁面顯示的數據進行對比(這個過程是比較久的,需要有耐心)。我這邊抓取了很多車次的數據信息進行了對比,其中需要注意的是“商務座”和“特等座”12306頁面上雖然顯示在一起的,但是“Sources”對應的數據字段卻不是一樣的(還有我猜測二等座和二等包座的字段也可能不是一樣的,因為沒有數據去做比較,后面就忽略掉了),下面是我對比出來的結果截圖:

    找到了車次信息對應的字段,就開始把數據編排成我們想要的格式吧。這里使用PrettyTable庫來進行信息對齊表格美化(這個庫要注意大小寫),因為考慮到可以根據用戶輸入的參數“-gdtkz”來篩選車次數據,所有我們要通過用戶的輸入和火車類型進行判斷,並定義一個filtrate_train()方法去篩選用戶想查看相關的車次信息,下面是此次實戰的全部代碼:

  1 #!/usr/bin/env python3
  2 # -*- coding: utf-8 -*-
  3 
  4 """Train tickets query via command-line.
  5 
  6 Usage:
  7     tickets [-gdtkz] <from> <to> <date>
  8 
  9 Options:
 10     -h,--help    顯示幫助信息菜單
 11     -g           高鐵
 12     -d           動車
 13     -t           特快
 14     -k           快速
 15     -z           直達
 16 
 17 Example:
 18     tickets 北京 上海 2020-10-29
 19 """
 20 
 21 from docopt import docopt
 22 # docopt 模塊是 python3 命令行參數解析工具
 23 # docopt 模塊本質上是在 Python 中引入了一種針對命令行參數的形式語言,在代碼的最開頭使用 """ """文檔注釋的形式寫出符合要求的文檔,就會自動生成對應的 parse
 24 # 所有出現在 Usage:(區分大小寫)和一個空行之間的文本都會被識別為一個命令組合, Usage 后的第一個字母將會被識別為這個程序的名字,所有命令組合的每一個部分(空格分隔)都會成為字典中的一個key
 25 from prettytable import PrettyTable
 26 import re    # 正則表達式模塊
 27 import stations
 28 import urllib3, requests    # python 訪問 HTTP 資源的必備庫
 29 
 30 # 定義一個filtrate_train()函數,用來篩選查詢到列車車次的數據
 31 def filtrate_train(pt, data_list):
 32     station_train_code = data_list[3]    # 車次
 33     from_station_code = data_list[6]    # 起始站英文代號
 34     to_station_code = data_list[7]    # 終點站英文代號
 35     from_station_name = stations.get_name(from_station_code)    # 起始站中文名稱
 36     to_station_name = stations.get_name(to_station_code)    # 終點站中文名稱
 37     start_time = data_list[8]    # 出發時間
 38     arrive_time = data_list[9]    # 到達時間
 39     lishi = data_list[10]    # 歷時
 40     # 通過對比12306代碼和頁面上座位顯示結果分析出“商務座”和“特等座”對應的參數是不同的,cN[25]是特等座,cN[32]是商務座
 41     business_seat = data_list[25] or data_list[32] or "--"    # 商務座和特等座
 42     first_class_seat = data_list[31] or "--"    # 一等座
 43     second_class_seat = data_list[30] or "--"    # 二等座,查看12306頁面時,二等座下方有個“二等包座”,對比代碼應該是cN[27],但是沒有找到有對應數據暫時不寫上去
 44     advanced_soft_sleeper = data_list[21] or "--"    # 高級軟卧
 45     soft_sleeper = data_list[23] or "--"    # 軟卧
 46     bullet_sleeper = data_list[33] or "--"    # 動卧
 47     hard_sleeper = data_list[28] or "--"    # 硬卧
 48     soft_seat = data_list[24] or "--"    # 軟座,因為沒有查詢到有軟座的信息,對比了代碼參數,猜測cN[24]應該是軟座
 49     hard_seat = data_list[29] or "--"    # 硬座
 50     not_seat = data_list[26] or "--"    # 無座
 51     pt.add_row([
 52         station_train_code,  # 車次
 53         from_station_name,  # 起始站中文名稱
 54         to_station_name,  # 終點站中文名稱
 55         start_time,  # 出發時間
 56         arrive_time,  # 到達時間
 57         lishi,  # 歷時
 58         business_seat,  # 商務座和特等座
 59         first_class_seat,  # 一等座
 60         second_class_seat,  # 二等座
 61         advanced_soft_sleeper,  # 高級軟卧
 62         soft_sleeper,  # 軟卧
 63         bullet_sleeper,  # 動卧
 64         hard_sleeper,  # 硬卧
 65         soft_seat,  # 軟座
 66         hard_seat,  # 硬座
 67         not_seat  # 無座
 68     ])
 69     return pt
 70 
 71 def cli():
 72     """command-line interface"""
 73     arguments = docopt(__doc__)
 74     from_stion = stations.get_telecode(arguments["<from>"])
 75     to_stion = stations.get_telecode(arguments["<to>"])
 76     date = arguments["<date>"]
 77     # print(from_stion, to_stion, date)
 78 
 79     # 構建 URL
 80     url = ("https://kyfw.12306.cn/otn/leftTicket/query?"
 81            "leftTicketDTO.train_date={}&"
 82            "leftTicketDTO.from_station={}&"
 83            "leftTicketDTO.to_station={}&"
 84            "purpose_codes=ADULT").format(date, from_stion, to_stion)
 85     headers = {
 86         # Cookie的值可以通過打開瀏覽器的開發者模式復制過來
 87         "Cookie": "_uab_collina=160395250285657341202147; JSESSIONID=7C56E896658518A4E5BF99889839D00C; _jc_save_wfdc_flag=dc; _jc_save_fromStation=%u5317%u4EAC%2CBJP; _jc_save_toStation=%u4E0A%u6D77%2CSHH; BIGipServerotn=1725497610.50210.0000; RAIL_EXPIRATION=1604632917257; RAIL_DEVICEID=DeBrCMshZyD9JIK2yazJV4op0oxRXXKpeio_Y27U75ZkWKFwOd6Q_i2JRVBJeN3Q9qQ7ybyTw4Vv3ImAEwdTAAh8XLXL6WGn3irR65rZyYeWtvToLkq8oVAprmAw6OPgPnqI9a9ItALNr0kFjzDkncjjGPINbqfa; BIGipServerpassport=770179338.50215.0000; route=c5c62a339e7744272a54643b3be5bf64; _jc_save_fromDate=2020-11-02; _jc_save_toDate=2020-11-01",
 88         "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36"
 89     }
 90     requests.packages.urllib3.disable_warnings()
 91     r = requests.get(url, headers=headers, verify=False)    # verify=False參數表示不進行證書驗證
 92     raw_trains = r.json()['data']['result']
 93     # print(raw_trains)
 94     pt = PrettyTable()
 95     pt.field_names = '車次 起始站 終點站 出發時間 到達時間 歷時 商務(特等)座 一等座 二等座 高級軟卧 一等(軟)卧 動卧 二等(硬)卧 軟座 硬座 無座'.split()
 96     # print(pt)
 97     for raw_train in raw_trains:
 98         data_list = raw_train.split("|")
 99         if data_list[1] == "預訂":    # 因為有停運列車,需判定該車次列車是否可以預約
100             initial = data_list[3][0].lower()    # 獲取車次代號,g:高鐵,d:動車,t:特快,k:快速,z:直達
101             if not arguments["-g"] and not arguments["-d"] and not arguments["-t"] and not arguments["-k"] and not arguments["-z"]:
102                 filtrate_train(pt, data_list)
103             elif arguments["-g"] and initial == "g":
104                 filtrate_train(pt, data_list)
105             elif arguments["-d"] and initial == "d":
106                 filtrate_train(pt, data_list)
107             elif arguments["-t"] and initial == "t":
108                 filtrate_train(pt, data_list)
109             elif arguments["-k"] and initial == "k":
110                 filtrate_train(pt, data_list)
111             elif arguments["-z"] and initial == "z":
112                 filtrate_train(pt, data_list)
113     print(pt)
114 
115 if __name__ == "__main__":
116     cli()

    代碼執行結果截圖:

    同時對比12306查詢到的車次信息結果截圖:

    最后貼上參考鏈接:https://blog.csdn.net/qq_39380075/article/details/79841339?utm_medium=distribute.pc_aggpage_search_result.none-task-blog-2~all~first_rank_v2~rank_v28-5-79841339.nonecase&utm_term=%E5%88%A9%E7%94%A8python%E5%AE%9E%E7%8E%B012306%E7%88%AC%E8%99%AB&spm=1000.2123.3001.4430


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM