简易 Python 脚本查询嵊泗船票
夏天來了,這顆躁動的心啊,想去嵊泗玩幾天~
現在上海去嵊泗要上微信公眾號或者官網買票,工作日還好,但是周末了不太容易搶到票了,又不能沒事就刷手機(這太沒有程序員范兒了)。
所以,看看能不能用 Python 寫個爬蟲腳本定時幫我搜索呢?
不想看羅里吧嗦分析的可以直接跳到文末。
1. 請求接口分析
1.1 URL
分析了一下網站,發現是用Vue寫的;接口設計也很簡單粗暴,非常好懂,同時發現沒有任何反爬措施,可能也根本不需要吧。
購票頁面看購票頁面里,票是分兩種類型的——載人,以及載車(順帶載人)。
這是因為,有的客船(如高速客船)只支持載人,開得快,但是票價稍微貴上5~15塊;有的客船(如客滾船)同時支持載人、載車(方便自駕游),所以開得慢一些,但是票價也便宜一點。
看后端接口的 URL,也能發現兩者查詢余票的接口是不一樣的:
- 載人:https://www.ssky123.com/api/v2/line/ship/enq
- 載車:https://www.ssky123.com/api/v2/line/ferry/enq
1.2 傳參
盡管是兩個接口,但是傳遞的參數都是一樣的
傳遞參數- startDate: 出發日期,String
- startPortNo: 起點碼頭編號,Int
- endPortNo: 終點碼頭編號,Int
碼頭編號可以通過航線查詢接口 https://www.ssky123.com/api/v2/line/port/all 獲取到,真的是很方便了……
航線接口- startPortList: 起點碼頭編號列表
- endPortList: 終點碼頭編號列表
- lineList: 有效航線列表
這里只要看 lineList 就可以了,因為這個結果不僅告訴我們起點碼頭編號和終點碼頭編號,還告訴我們兩兩之間是否有可達的航線。
1.3 Header
這里是我最想吐槽的,token 的值居然是 undefined,這是徹底放棄抵抗的意思吧,我估計連 User-Agent 都不用模擬,接口也是可以調通的(事實證明確實如此)。
Header2. 返回結果分析
我隨便以沈家灣為起點,泗礁為終點,查詢2020年5月21號的剩余票數,接口部分截圖如下:
接口部分數據這個 pubCurrentCount 應該就是我們要的剩余票數了,localPrice 代表了船票單價,但是 originPrice 不知是合意,真的貴。。
這里要稍微注意的是,兩種船票的余票數據,是放在不同字段下的——對于查詢乘客的余票,我們看的是 seatClasses 列表里的結果;對于查詢載車的余票,我們要看 driverSeatClass 列表。
腳本
簡單的看完以后,就可以快速地寫個腳本啦。
import requests import time import sys import osdef get_lines(start, end):"""基于最新的航線數據,查詢目標航線是否存在,并獲取起點和終點碼頭編號:param start: 起點碼頭名稱,中文:param end: 終點碼頭名稱,中文"""lines = requests.get('https://www.ssky123.com/api/v2/line/port/all').json()['data']['lineList']startPortNum, endPortNum = None, Nonefor line in lines:if start in line['startPortName'] and end in line['endPortName']:startPortNum, endPortNum = line['startPortNum'], line['endPortNum']breakif startPortNum and endPortNum:print('起點: ' + start + str(startPortNum))print('終點: ' + end + str(endPortNum))else:print('未找到本航線')return startPortNum, endPortNumdef get_sale_info(startPortNum, endPortNum, with_car, date):"""查詢余票信息,返回各開船時間下的有效剩余船票(若無票則不記錄):param startPortNum: 起點碼頭編號:param endPortNum: 終點碼頭編號:param with_car: 是否開車上船:param date: 出發日期"""url = 'https://www.ssky123.com/api/v2/line/ferry/enq' if with_car else 'https://www.ssky123.com/api/v2/line/ship/enq'data = {'endPortNo': endPortNum, 'startDate': date, 'startPortNo': startPortNum}result = requests.post(url, json=data).json()data = {}for info in result['data']:class_name = 'driverSeatClass' if with_car else 'seatClasses' # 字段名sail_time = info['sailTime'] # 開船時間left_num = sum([cls['pubCurrentCount'] for cls in info[class_name]]) # 剩余票數if left_num > 0:data[sail_time] = left_numreturn datadef main(start, end, date, with_car=False, max_search=10000, stop_when_find=True):"""主體腳本,基于用戶輸入進行循環:param start: 起點碼頭名稱:param end: 起點碼頭名稱:param date: 出發日期,String 格式(如 '2020-05-22' ),如果要一次搜索多個日期,則用列表(如 ['2020-05-22', ...]):param with_car: 是否開車上船:param max_search: 最大循環次數:param stop_when_find: 發現有余票后,是否停止循環"""# 查詢航線是否存在startPortNum, endPortNum = get_lines(start, end)if not startPortNum or not endPortNum:returncount = 0while True:find = False # 是否找到dates = [date] if isinstance(date, str) else datefor date_ in dates:data = get_sale_info(startPortNum, endPortNum, with_car, date_)count += 1if data:find = Truefor sail_time, left_num in data.items():text = sail_time + ' 還有 ' + str(left_num) + ' 張票'# os.system('say ' + text)print(text)# 是否停止搜索if find and stop_when_find or count >= max_search:breakelse:sys.stdout.write('第 ' + str(count) + ' 次搜索完畢\r')time.sleep(30)if __name__ == '__main__':main(start='沈家灣',end='泗礁',date=['2020-05-21'],with_car=False, # 是否帶車stop_when_find=True, # 是否找到就停止)上面我注釋了一行代碼:os.system('say ' + text),這是執行蘋果系統的命令,調用系統聲音來提示我,防止我沒有及時看到 print 的結果,不同操作系統的提示方式不一樣,所以我就先注釋掉了。
運行一下,完美~
總結
以上是生活随笔為你收集整理的简易 Python 脚本查询嵊泗船票的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: chfs下载地址 CuteHttpFil
- 下一篇: 团队程序设计天梯赛-3.19排位赛总结