前言
hello兄弟們,偷懶歸來了。別問為啥這么久沒更,問就是失蹤了
最近一直在學(xué)習(xí)Django以及爬蟲進(jìn)階之類的知識(shí),加上快期末了,一直沒有想起來自己還有一個(gè)賬號(hào)沒有更新,sorry啦言歸正傳,今天抽空把前面的文章升級(jí)了一下。這里先把整理好的代碼提前放給大家
from selenium.common import ElementNotInteractableException, ElementNotVisibleException
from selenium.webdriver import Chrome
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver import ActionChains
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.keys import Keys
from lxml import etree
import re
import pickle
import os
import time
# 定義登錄方法
def login(user, pwd):
login_choice = web.find_element(By.XPATH, '//*[@id="toolbar_Div"]/div[2]/div[2]/ul/li[1]')
# 點(diǎn)擊賬號(hào)密碼登錄方式
login_choice.click()
username = web.find_element(By.XPATH, '//*[@id="J-userName"]') # 向賬號(hào)框傳入賬號(hào)信息
passwd = web.find_element(By.XPATH, '//*[@id="J-password"]') # 向密碼框傳入密碼
username.click()
username.send_keys(user)
passwd.click()
passwd.send_keys(pwd)
# 定位到登錄按鈕并點(diǎn)擊
web.find_element(By.XPATH, '//*[@id="J-login"]').click()
# 設(shè)置顯示等待直到滑塊的span標(biāo)簽被定位到
WebDriverWait(web, 2, 0.5).until(EC.presence_of_element_located((By.ID, 'nc_1_n1z')))
span = web.find_element(By.ID, 'nc_1_n1z')
action = ActionChains(web)
action.click_and_hold(span).move_by_offset(300,
0).perform() # click_and_hold代表點(diǎn)擊并保持點(diǎn)擊動(dòng)作。move_by_offset(x, y),其中x代表水平移動(dòng)距離,y代表垂直移動(dòng)距離
WebDriverWait(web, 10, 1).until(EC.url_to_be('https://kyfw.12306.cn/otn/view/index.html'))
pickle.dump(web.get_cookies(), open('./12306cookies.pkl', 'wb'))
print('cookies保存成功')
def cookies_login():
cookies = pickle.load(open('./12306cookies.pkl', 'rb'))
for cookie in cookies:
cookie_dic = {
'domain': '.12306.cn',
'name': cookie.get('name'),
'value': cookie.get('value')
}
web.add_cookie(cookie_dic)
web.get('https://kyfw.12306.cn/otn/leftTicket/init')
print('cookies載入成功')
def get_ticket_info(start_city, end_city, date):
WebDriverWait(web, 2, 0.5).until(EC.url_to_be('https://kyfw.12306.cn/otn/leftTicket/init'))
web.find_element(By.ID, 'fromStationText').click() # 先定位到出發(fā)地輸入框點(diǎn)擊后再傳入?yún)?shù)
web.find_element(By.ID, 'fromStationText').send_keys(start_city, Keys.ENTER) # Keys庫可以模擬實(shí)現(xiàn)鍵盤上的功能鍵
web.find_element(By.ID, 'toStationText').click() # 目的地
web.find_element(By.ID, 'toStationText').send_keys(end_city, Keys.ENTER)
web.find_element(By.ID, 'train_date').clear() # 由于date頁面默認(rèn)當(dāng)天日期,所以先清空默認(rèn)內(nèi)容在輸入?yún)?shù)
web.find_element(By.ID, 'train_date').send_keys(date, Keys.ENTER)
web.find_element(By.ID, 'query_ticket').click() # 點(diǎn)擊查詢
def get_ticket_dic_info():
WebDriverWait(web, 2, 0.5).until(EC.presence_of_element_located((By.XPATH, '//*[@id="queryLeftTable"]/tr')))
tree = etree.HTML(web.page_source)
tick_list = tree.xpath('//*[@id="queryLeftTable"]/tr')
tr_dic = {}
for tr in tick_list:
if not tr.xpath('./td[1]/div/div[1]/div/a/text()'):
continue
else:
tr_num = '車次:' + tr.xpath('./td[1]/div/div[1]/div/a/text()')[0]
tr_id = '車輛ID為:' + tr.xpath('./@id')[0] + '|' # 添加標(biāo)識(shí)頭和分隔符便于觀看
tr_from_place = '出發(fā)地:' + tr.xpath('./td[1]/div/div[2]/strong[1]/text()')[0] + ' '
tr_get_place = '目的地:' + tr.xpath('./td[1]/div/div[2]/strong[2]/text()')[0] + ' '
tr_start_time = '出發(fā)時(shí)間:' + tr.xpath('./td[1]/div/div[3]/strong[1]/text()')[0] + ' ' # 列車發(fā)動(dòng)時(shí)間
tr_get_time = '到達(dá)時(shí)間:' + tr.xpath('./td[1]/div/div[3]/strong[2]/text()')[0] + ' ' # 列車到達(dá)目的地時(shí)間
if not tr.xpath('./td[2]/@aria-label'):
tr_shang_wu = 'Null'
else:
tr_shang_wu = '商務(wù)座:' + tr.xpath('./td[2]/@aria-label')[0] + ' ' # 商務(wù)座
if not tr.xpath('./td[3]/@aria-label'):
tr_yi_deng = 'Null'
else:
tr_yi_deng = '一等座:' + tr.xpath('./td[3]/@aria-label')[0] + ' ' # 一等座
if not tr.xpath('./td[4]/@aria-label'):
tr_er_deng = 'Null'
else:
tr_er_deng = '二等:' + tr.xpath('./td[4]/@aria-label')[0] + ' ' # 二等座
if not tr.xpath('./td[6]/@aria-label'):
tr_ruan_wo = 'Null'
else:
tr_ruan_wo = '軟臥:' + tr.xpath('./td[6]/@aria-label')[0] + ' ' # 軟臥
if not tr.xpath('./td[8]/@aria-label'):
tr_ying_wo = 'Null'
else:
tr_ying_wo = '硬臥:' + tr.xpath('./td[8]/@aria-label')[0] + ' ' # 硬臥
if not tr.xpath('./td[10]/@aria-label'):
tr_ying_zuo = 'Null'
else:
tr_ying_zuo = '硬座:' + tr.xpath('./td[10]/@aria-label')[0] + ' ' # 硬座
if not tr.xpath('./td[11]/@aria-label'):
tr_wu_zuo = 'Null'
else:
tr_wu_zuo = '無座:' + tr.xpath('./td[11]/@aria-label')[0] # 無座
tr_dic[tr_num] = tr_id + tr_from_place + tr_get_place + tr_start_time + tr_get_time + tr_shang_wu + \
tr_yi_deng + tr_er_deng + tr_ruan_wo + tr_ying_wo + tr_ying_zuo + tr_wu_zuo
return tr_dic
def chick_ticket(dic, id, count):
# print('*' * 15 + '查詢到的車次信息如下' + '*' * 30)
# print(str(dic).replace(',', '\n'))
# train_id = '車次:' + str(input('請(qǐng)輸入選擇的車次:\n')) # 車次:與輸入的車次num拼接為字典的key值
train_id = '車次:' + id
print('您選擇的車次為:', id)
while train_id in dic.keys():
tr_info = dic.get(train_id) # 根據(jù)key值信息獲取到其保存在value里的id與座位信息等
obj = re.compile(r'車輛ID為:(?P<id>.*?)出發(fā)地') # 利用正則獲取到車次id
result = obj.finditer(tr_info) # 此時(shí)獲取到的是迭代器,要重新獲取出來
for i in result:
tr_id = i.group('id').strip('|') # 由于獲取到的id中帶有分隔符|,因此剔除掉
time_now = time.strftime("%H:%M:%S", time.localtime()) # 刷新
if time_now == '17:33:05': # 設(shè)定搶票時(shí)間,一般為下午五點(diǎn):17:00:00
web.refresh()
web.find_element(By.ID, 'query_ticket').click() # 點(diǎn)擊重新查詢
WebDriverWait(web, 2, 0.1).until(EC.presence_of_element_located((By.XPATH, f'//*[@id="{tr_id}"]/td[13]/a')))
web.find_element(By.XPATH, f'//*[@id="{tr_id}"]/td[13]/a').click() # 根據(jù)id匹配到車次所在列并點(diǎn)擊末尾的預(yù)定按鈕
WebDriverWait(web, 2, 0.1).until(EC.url_to_be('https://kyfw.12306.cn/otn/confirmPassenger/initDc'))
if count == 1:
my_self = 'N' # 根據(jù)個(gè)人情況,如果有學(xué)生票選項(xiàng)的話就自己添加一個(gè)判斷,我這里默認(rèn)平時(shí)學(xué)生票為N,不選擇
web.find_element(By.ID, 'normalPassenger_0').click()
WebDriverWait(web, 2, 0.1).until(EC.presence_of_element_located((By.ID, 'dialog_xsertcj_cancel')))
if my_self == 'N':
web.find_element(By.ID, 'dialog_xsertcj_cancel').click()
else:
web.find_element(By.ID, 'dialog_xsertcj_ok').click()
web.find_element(By.ID, 'submitOrder_id').click()
WebDriverWait(web, 100, 0.5).until(EC.element_to_be_clickable((By.ID, 'qr_submit_id')))
# 由于Seenium自身的Bug,可能會(huì)導(dǎo)致確認(rèn)點(diǎn)擊操作無法正確執(zhí)行,所以?利用try不斷訪問
submit_button = web.find_element(By.ID, 'qr_submit_id')
try:
while submit_button:
try:
submit_button.click()
submit_button = web.find_element(By.ID, 'qr_submit_id')
except(ElementNotVisibleException, ElementNotInteractableException):
# 當(dāng)在此頁面見不到此元素,代表已進(jìn)入付款頁面
break
print('搶票成功,請(qǐng)盡快付款!')
except:
pass
if __name__ == '__main__':
opt = Options()
opt.add_experimental_option('excludeSwitches', ['enable-automation']) # 去除瀏覽器頂部顯示受自動(dòng)化程序控制
opt.add_experimental_option('detach', True) # 規(guī)避程序運(yùn)行完自動(dòng)退出瀏覽器
web = Chrome(options=opt)
web.get('https://kyfw.12306.cn/otn/resources/login.html')
# 解除瀏覽器特征識(shí)別selenium
script = 'Object.defineProperty(navigator,"webdriver", {get: () => false,});'
web.execute_script(script)
user = '' # 此處輸入賬號(hào)
pwd = '' # 此處輸入密碼
start_city = '' # 出發(fā)城市
end_city = '' # 目的城市
date = '' # 格式參照2023-05-17
t_id = '' # 想要選擇的車次id
person1 = '' # 這里根據(jù)自己賬號(hào)具體保存的個(gè)人信息選擇乘車人,一人就只需定義一個(gè)person,多人數(shù)以此累加
# person2 = ''
person_list = []
person_list.append(person1) # person_list.append(person1,person2,person3, ......)
person_count = len(person_list)
if not os.path.exists('./12306cookies.pkl'):
login(user, pwd)
web.refresh()
print('信息輸入成功,請(qǐng)重新運(yùn)行程序')
web.close()
else:
stat_info = os.stat('./12306cookies.pkl')
last_modify_time = time.time() - stat_info.st_mtime
# print(last_modify_time)
if last_modify_time > 1800: # 建議半小時(shí)刷新一次cookie
login(user, pwd)
web.refresh()
print('信息輸入成功,請(qǐng)重新運(yùn)行程序')
web.close()
else:
cookies_login() # 利用cookie實(shí)現(xiàn)快速登陸
get_ticket_info(start_city, end_city, date) # 獲取到查詢信息
tick_dic = get_ticket_dic_info() # 查詢信息封裝成字典傳遞給選票
??chick_ticket(tick_dic, t_id, person_count) # 進(jìn)行購票
代碼放上,那我們就開搞開搞
代碼更新
相較于第一版,我們進(jìn)行了重大更新,沒有看過之前文章的xdm可以去看看我的主頁哈。首先是在登錄方式上。老版的登錄方式過于繁瑣且耗時(shí)。聽取上次評(píng)論兄弟們的意見換成了cookie登錄。
首先我們先定義了一個(gè)判斷語句,檢索當(dāng)下目錄有沒有用戶的cookie文件。我命名為12306cookies.pkl。
if not os.path.exists('./12306cookies.pkl'):
login(user, pwd)
web.refresh()
print('信息輸入成功,請(qǐng)重新運(yùn)行程序')
web.close()
else:
stat_info = os.stat('./12306cookies.pkl')
last_modify_time = time.time() - stat_info.st_mtime
# print(last_modify_time)
if last_modify_time > 1800: # 建議半小時(shí)刷新一次cookie
login(user, pwd)
web.refresh()
print('信息輸入成功,請(qǐng)重新運(yùn)行程序')
web.close()
else:
cookies_login()
get_ticket_info(start_city, end_city, date)
tick_dic = get_ticket_dic_info()
chick_ticket(tick_dic, t_id, person_count)
如上面代碼所示,如果本地未檢測(cè)到cookie文件,首先會(huì)執(zhí)行賬號(hào)密碼登錄,這一塊采用的是老版的登錄方式,代碼沒有太大改動(dòng),只是在最后利用pickle保存了登錄后的用戶cookie信息
pickle.dump(web.get_cookies(), open('./12306cookies.pkl', 'wb'))
print('cookies保存成功')
我在這里設(shè)定的是獲取到cookie信息后,關(guān)閉程序退出。再次執(zhí)行時(shí)就會(huì)直接進(jìn)入到查詢界面
if not os.path.exists('./12306cookies.pkl'):
login(user, pwd)
web.refresh()
print('信息輸入成功,請(qǐng)重新運(yùn)行程序')
web.close()
在這里我再次進(jìn)行了一次判斷,利用時(shí)間函數(shù)判斷cookie文件修改的時(shí)間,超過半小時(shí)就需要重新賬號(hào)密碼登錄刷新cookie信息。
到此一切的目的都是為了在購票前半小時(shí)提前準(zhǔn)備好一切工作。
if not os.path.exists('./12306cookies.pkl'):
login(user, pwd)
web.refresh()
print('信息輸入成功,請(qǐng)重新運(yùn)行程序')
web.close()
else:
stat_info = os.stat('./12306cookies.pkl')
last_modify_time = time.time() - stat_info.st_mtime
# print(last_modify_time)
if last_modify_time > 1800: # 建議半小時(shí)刷新一次cookie
login(user, pwd)
web.refresh()
print('信息輸入成功,請(qǐng)重新運(yùn)行程序')
web.close()
當(dāng)前面所有步驟全部無誤時(shí),再次執(zhí)行程序就可以直接cookie登錄了
cookies_login() # 利用cookie實(shí)現(xiàn)快速登陸
get_ticket_info(start_city, end_city, date) # 獲取到查詢信息
tick_dic = get_ticket_dic_info() # 查詢信息封裝成字典傳遞給選票
??chick_ticket(tick_dic, t_id, person_count) # 進(jìn)行購票
有關(guān)cookie登錄功能的代碼如下,主要都是固定模板,沒什么難度
def cookies_login():
cookies = pickle.load(open('./12306cookies.pkl', 'rb'))
for cookie in cookies:
cookie_dic = {
'domain': '.12306.cn',
'name': cookie.get('name'),
'value': cookie.get('value')
}
web.add_cookie(cookie_dic)
web.get('https://kyfw.12306.cn/otn/leftTicket/init') # 直接進(jìn)如查詢界面
print('cookies載入成功')
下面就是在我看來改動(dòng)最大的地方
購票函數(shù)
def chick_ticket(dic, id, count):
# print('*' * 15 + '查詢到的車次信息如下' + '*' * 30)
# print(str(dic).replace(',', '\n'))
# train_id = '車次:' + str(input('請(qǐng)輸入選擇的車次:\n')) # 車次:與輸入的車次num拼接為字典的key值
train_id = '車次:' + id
print('您選擇的車次為:', id)
while train_id in dic.keys():
tr_info = dic.get(train_id) # 根據(jù)key值信息獲取到其保存在value里的id與座位信息等
obj = re.compile(r'車輛ID為:(?P<id>.*?)出發(fā)地') # 利用正則獲取到車次id
result = obj.finditer(tr_info) # 此時(shí)獲取到的是迭代器,要重新獲取出來
for i in result:
tr_id = i.group('id').strip('|') # 由于獲取到的id中帶有分隔符|,因此剔除掉
time_now = time.strftime("%H:%M:%S", time.localtime()) # 刷新當(dāng)前時(shí)間
if time_now == '17:33:05': # 設(shè)定搶票時(shí)間,一般為下午五點(diǎn):17:00:00
web.refresh()
web.find_element(By.ID, 'query_ticket').click() # 點(diǎn)擊重新查詢
WebDriverWait(web, 2, 0.1).until(EC.presence_of_element_located((By.XPATH, f'//*[@id="{tr_id}"]/td[13]/a')))
web.find_element(By.XPATH, f'//*[@id="{tr_id}"]/td[13]/a').click() # 根據(jù)id匹配到車次所在列并點(diǎn)擊末尾的預(yù)定按鈕
WebDriverWait(web, 2, 0.1).until(EC.url_to_be('https://kyfw.12306.cn/otn/confirmPassenger/initDc'))
if count == 1:
my_self = 'N' # 根據(jù)個(gè)人情況,如果有學(xué)生票選項(xiàng)的話就自己添加一個(gè)判斷,我這里默認(rèn)平時(shí)學(xué)生票為N,不選擇
web.find_element(By.ID, 'normalPassenger_0').click()
WebDriverWait(web, 2, 0.1).until(EC.presence_of_element_located((By.ID, 'dialog_xsertcj_cancel')))
if my_self == 'N':
web.find_element(By.ID, 'dialog_xsertcj_cancel').click()
else:
web.find_element(By.ID, 'dialog_xsertcj_ok').click()
web.find_element(By.ID, 'submitOrder_id').click()
WebDriverWait(web, 100, 0.5).until(EC.element_to_be_clickable((By.ID, 'qr_submit_id')))
# 由于Seenium自身的Bug,可能會(huì)導(dǎo)致確認(rèn)點(diǎn)擊操作無法正確執(zhí)行,所以?利用try不斷訪問
submit_button = web.find_element(By.ID, 'qr_submit_id')
try:
while submit_button:
try:
submit_button.click()
submit_button = web.find_element(By.ID, 'qr_submit_id')
except(ElementNotVisibleException, ElementNotInteractableException):
# 當(dāng)在此頁面見不到此元素,代表已進(jìn)入付款頁面
break
print('搶票成功,請(qǐng)盡快付款!')
except:
pass
可以看到每一行基本都有我的注釋,正文里面就不再解釋啦。主要闡述下設(shè)計(jì)思路。
在代碼中設(shè)定了搶票時(shí)間,利用while不斷循環(huán)。實(shí)現(xiàn)了只要在搶票前30分鐘運(yùn)行了此程序后,就可以放心不去管了。當(dāng)時(shí)間到了設(shè)定的購票時(shí)間時(shí)。程序會(huì)迅速發(fā)送請(qǐng)求。為了速度,我這里并沒有設(shè)計(jì)選座,追求直接發(fā)送購票請(qǐng)求。畢竟是為了能夠搶到想要的車票,座位什么的也就不太重要了。
當(dāng)然,就像我注釋所說的那樣,每個(gè)人的情況都不同,有學(xué)生票的情況,也有想購買多張票的情況。在我的代碼中只是為了滿足我的需求而設(shè)計(jì)的。大家如果想要應(yīng)用于實(shí)際的話建議自己修改程序。目前為止我的測(cè)試都是在訪問量不算大的情況下實(shí)現(xiàn)的。等后面有機(jī)會(huì)了還會(huì)根據(jù)實(shí)際情況進(jìn)行進(jìn)一步的修改。到此,關(guān)于12306登錄的文章就暫時(shí)不會(huì)再更新啦。大家有興趣的話可以自己在接著研究研究吧。文章來源:http://www.zghlxwxcb.cn/news/detail-455265.html
水文結(jié)束,接著潛水。拜拜。文章來源地址http://www.zghlxwxcb.cn/news/detail-455265.html
到了這里,關(guān)于爬蟲練習(xí)-12306自動(dòng)購票升級(jí)版的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!