不知不覺,一年一度的春運搶票大幕已經拉開,想快速搶到回家的車票嗎?作爲程序員,這些技術手段,你一定要知道。
爲了讓大家更快捷更便利的搶火車票,各種各樣的搶票軟件應需而生,這類軟件大部分都是付費搶票的機制。
作爲程序員,如何用技術手段搶到回家的票?來看看用 Python 寫的搶票腳本。
手把手教你用 Python 搶票回家過年
環境介紹
windows 8.1
python3.6.1
firefox插件 geckodriver.exe
操作步驟
引入要的模塊
from selenium import webdriver #控制瀏覽器
from selenium.webdriver.common.keys import Keys #用於給元素賦值
import time #時間模塊
from selenium.webdriver.support.select import Select #控制下拉框模塊
from selenium.webdriver.common.by import By #尋找元素模塊
from selenium.webdriver.support.ui import WebDriverWait #“顯示等待”模塊
from selenium.webdriver.support import expected_conditions as EC #等待條件模塊
登陸模塊
首先需要選擇使用的瀏覽器,此處以 firefox 爲例,下載:geckodriver.exe 。
提到的 stations.txt 可以直接看這個:
車站信息:
https://kyfw.12306.cn/otn/resources/js/framework/station_name.js?station_version=1.9042
將 geckodriver.exe 放到 python.exe 同級目錄下即可(如果有報錯的情況下,放一個該文件到與 firefox.exe 同級目錄下,並添加環境變量)
#可以用input,也可以直接放入到後面的用戶名、密碼輸入框中
#可以利用標準輸入進行批量的操作,此處以個人搶票操作爲例
# username = str(input('請輸入你的用戶名:'))
# password = str(input('請輸入你的密碼:')) #這兩行可以暫時忽略
browser = webdriver.Firefox() #驅動firefox瀏覽器
browser.get("https://kyfw.12306.cn/otn/login/init") #啓動瀏覽器後進入該鏈接下
browser.find_element_by_id('username').clear()
browser.find_element_by_id('username').send_keys(‘xxxxx’) #xxxxx更換爲用戶名
browser.find_element_by_id('password').send_keys(‘xxxxx’) #xxxxx更換爲密碼
time.sleep(10) #此時驗證碼自行點擊,該處設置10秒延遲,可以自己設置
try:
browser.find_element_by_id('loginSub').click() #點擊登陸操作,該id爲登陸按鈕
#或者 browser.find_element_by_link_text('登陸').click() #標籤顯示的名稱
except:
browser.find_element_by_class_name('touclick-bgimg touclick-reload touclick-reload-normal').click() #try中驗證碼輸入點錯了會在此處刷新一次
time.sleep(20) #第二次輸入驗證碼前等待20秒,可以自己設置,第一次輸入無誤直接跳過
browser.find_element_by_id('loginSub').click() #重新輸入驗證碼後的點擊登陸
跳轉模塊
#python學習羣592539176
#默認跳轉到首頁
time.sleep(2) #此處一般無需設置時間等待,調試代碼時使用
clickReserve = browser.find_element_by_link_text('車票預訂').click() #跳轉到車票預定頁面,該頁面可以查詢票
time.sleep(2) #出發地點和到達地點設置
#此處value值爲出發時刻的地點,BJP表示北京,更改value值在頁面上不加載,基本不耗時間,從頁面中也看不到出發地和目的地
#此處內容以爬取,保存在stations.txt中,每行表示一個地址,打開文檔ctrl + F查找即可
jsf = 'var a = document.getElementById("fromStation");a.value = "BJP"' #此處將BJP更換爲你需要的出發地址,value值在以爬取到stations.txt中,自行查看
browser.execute_script(jsf)
jst = 'var a = document.getElementById("toStation");a.value = "LZJ"' #終點,同上方法
browser.execute_script(jst)
js = "document.getElementById('train_date').removeAttribute('readonly')" #時間選擇時默認爲只讀,通過JS移除只讀屬性
browser.execute_script(js) #執行JS語句
browser.find_element_by_id('train_date').clear() #時間元素中默認有提示字,需要先清空
browser.find_element_by_id('train_date').send_keys('2018-02-01') #按照改格式輸入需要查詢的時間
search = browser.find_element_by_id('query_ticket').click() #輸入好信息時點擊查詢,該處存在成人票和學生票,默認是成人票,如果購買,對學生票處執行以下語句即可:
#browser.find_element_by_id('xxxx').click() #對於id還是class或其它自行選擇,[可以查看此處](http://blog.51cto.com/12376665/2052278)
開始購票
此處,就是點擊預定的操作,我在這裏只是舉一個方法例子,也可以通過不斷點擊直到成功(這樣可以避免網站倒計時和實際時間的時間差影響,但是不知道 12306 在搶票時對不斷快速訪問有沒有限制)。
start_time = "Thu Jan 04 08:00:00 2018" #首先設置需要搶票的時間
b = time.mktime(time.strptime(start_time,"%a %b %d %H:%M:%S %Y")) print(time.strftime("%a %b %d %H:%M:%S %Y", time.localtime(b)) ) #此處是爲了調試代碼使用,可忽略,不影響使用
a = float(b)-time.time() #利用自己設置的時間減去當前時間的時間戳
time.sleep(a) #上一步驟得出的秒數就是需要等待搶票的時間
try: #此處本來有try中的部分就夠了,WebDriverWait已有相應等待重複訪問機制,默認爲0.5秒試驗一次,except中添加是爲了以防萬一
WebDriverWait(browser,10).until(EC.presence_of_element_located((By.ID, "ticket_2400000Z550L"))) #查找需要預定的車次的id,直到出現,10表示共等待10秒
ticket = browser.find_element_by_xpath('//tr[@id="ticket_2400000Z550L"]/td[13]/a').click() #點擊預定按鈕except:
browser.find_element_by_id('query_ticket').click()
WebDriverWait(browser, 10).until(EC.presence_of_element_located((By.ID, "ticket_2400000Z550L")))
ticket = browser.find_element_by_xpath('//tr[@id="ticket_2400000Z550L"]/td[13]/a').click()
"""
normalPassenger_8 數字表示該賬號下的第幾位,默認從0開始如果是第一個則爲normalPassenger_0
"""WebDriverWait(browser,10).until(EC.presence_of_element_located((By.ID, "normalPassenger_8")))
browser.find_element_by_id('normalPassenger_8').click() #id中的8表示賬號下第九位s = Select(browser.find_element_by_id('seatType_1'))
s.select_by_value('6') #此處value值看下方各個種類,6表示高級軟臥browser.find_element_by_id('submitOrder_id').click()
WebDriverWait(browser,10).until(EC.presence_of_element_located((By.ID, "qr_submit_id")))
browser.find_element_by_link_text('提交訂單')
browser.find_element_by_id('qr_submit_id').click()#-------------------------------------------------結束#硬座 1#硬臥 3#軟臥 4#高級軟臥 6#二等座 O(大寫字母)#一等座 M#商務座 9
總結
需要替換的地方:
-
用戶名,密碼。
-
起始地點和目的地的 value 值,查 stations.txt 修改即可。
-
出發時間。
-
自己選擇車次的 xpath 路徑,路徑不用變,變對應 id 即可。
-
勾選用戶的位置(如果只要一個用戶,默認用:normalPassenger_0)。
-
所選座位類別,默認爲有票的類別裏最便宜的種類。
其餘的在測試中都相同,沒有發現有變化,在使用前,可以測試一下代碼,測試是注意註釋掉提交訂單的代碼(下單有取消限制,每天好像只能取消三次),測試時網速正常。
有人說用瀏覽器執行速度會慢,確實對於可以直接識別驗證碼的腳本而言,沒有界面的會更快一些,但是實際上所用時間爲預定開始到結束,相同網絡下,代碼執行時間是要快於人工操作的,
另外,時間可以研究一下,之前研究過某寶的時間,秒殺時間是要比北京時間提前一點幾秒的,感覺全國各地有微小時間差的。
完整腳本示例
#python學習羣592539176
#python3.6.1#data:2018-01-03#author:LGC247CG"""
說明:
1.該腳本主要是提供一個實現思路,實現方法有很多,可以優化的地方也有很多,觸發機制也可以自己設置,代碼以壓縮到最短,只是爲了讓大家都可以看明白
2.正常網絡狀況下,不設置指定時間時,從點擊確認驗證碼到下單基本上1秒左右,所以速度上還是沒問題的
3.由於同時勾選多人和單人使用所需時間基本相同,希望該方法只用於技術交流,請勿作爲黃牛使用
4.在作爲技術交流的情況下,如果驗證碼可以實現將可以完全實現自動搶票:
--1>驗證碼有一定規律和數量,可以利用腳本獲取所有圖片,並加上相應標籤
--2>將頁面的文字和標籤相匹配,再將圖片進行相似度計算,對對應圖片進行點擊操作
--3>或是訓練深度學習的圖片識別模型,通過算法識別
"""from selenium import webdriverfrom selenium.webdriver.common.keys import Keysimport timefrom selenium.webdriver.support.select import Selectfrom selenium.webdriver.common.by import Byfrom selenium.webdriver.support.ui import WebDriverWaitfrom selenium.webdriver.support import expected_conditions as EC
browser = webdriver.Firefox()
browser.get("https://kyfw.12306.cn/otn/login/init")
browser.find_element_by_id('username').clear()
browser.find_element_by_id('username').send_keys('xxxxxxx')
browser.find_element_by_id('password').send_keys('xxxxxxx')
time.sleep(10)try:
browser.find_element_by_id('loginSub').click()except:
browser.find_element_by_class_name('touclick-bgimg touclick-reload touclick-reload-normal').click()
time.sleep(15)
browser.find_element_by_id('loginSub').click()#跳轉到車票預定頁面time.sleep(2)
clickReserve = browser.find_element_by_link_text('車票預訂').click()#出發地點和到達地點設置WebDriverWait(browser,10).until(EC.presence_of_element_located((By.ID, "fromStation")))
jsf = 'var a = document.getElementById("fromStation");a.value = "BJP"'browser.execute_script(jsf)
jst = 'var a = document.getElementById("toStation");a.value = "LZJ"'browser.execute_script(jst)
js = "document.getElementById('train_date').removeAttribute('readonly')"browser.execute_script(js)
browser.find_element_by_id('train_date').clear()
browser.find_element_by_id('train_date').send_keys('2018-02-02')
search = browser.find_element_by_id('query_ticket').click()#對於時間,我一直覺得網站計算的時間和自己獲取的時間差一秒左右,這個根據不同環境自己測試start_time = "Thu Jan 04 10:00:00 2018" #首先設置需要搶票的時間b = time.mktime(time.strptime(start_time,"%a %b %d %H:%M:%S %Y"))
print(time.strftime("%a %b %d %H:%M:%S %Y", time.localtime(b)) ) #此處是爲了調試代碼使用,可忽略,不影響使用a = float(b)-time.time() #利用自己設置的時間減去當前時間的時間戳time.sleep(a) #上一步驟得出的秒數就是需要等待搶票的時間browser.find_element_by_id('query_ticket').click() #時間到了先點擊查詢刷新一下,以防找不到元素try:
WebDriverWait(browser,10).until(EC.presence_of_element_located((By.ID, "ticket_2400000Z550L")))
ticket = browser.find_element_by_xpath('//tr[@id="ticket_2400000Z550L"]/td[13]/a').click()except:
browser.find_element_by_id('query_ticket').click()
WebDriverWait(browser, 10).until(EC.presence_of_element_located((By.ID, "ticket_250000K8880L")))
ticket = browser.find_element_by_xpath('//tr[@id="ticket_250000K8880L"]/td[13]/a').click()"""
normalPassenger_8 數字表示該賬號下的第幾位,默認從0開始如果是第一個則爲normalPassenger_0
"""WebDriverWait(browser,10).until(EC.presence_of_element_located((By.ID, "normalPassenger_8")))
browser.find_element_by_id('normalPassenger_8').click()
s = Select(browser.find_element_by_id('seatType_1'))
s.select_by_value('6')
browser.find_element_by_id('submitOrder_id').click()
WebDriverWait(browser,10).until(EC.presence_of_element_located((By.ID, "qr_submit_id")))
browser.find_element_by_link_text('提交訂單')#browser.find_element_by_id('qr_submit_id').click()