前言
hello兄弟们,偷懒归来了。别问为啥这么久没更,问就是失踪了
最近一直在学习Django以及爬虫进阶之类的知识,加上快期末了,一直没有想起来自己还有一个账号没有更新,sorry啦言归正传,今天抽空把前面的文章升级了一下。这里先把整理好的代码提前放给大家
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]')
# 点击账号密码登录方式
login_choice.click()
username = web.find_element(By.XPATH, '//*[@id="J-userName"]') # 向账号框传入账号信息
passwd = web.find_element(By.XPATH, '//*[@id="J-password"]') # 向密码框传入密码
username.click()
username.send_keys(user)
passwd.click()
passwd.send_keys(pwd)
# 定位到登录按钮并点击
web.find_element(By.XPATH, '//*[@id="J-login"]').click()
# 设置显示等待直到滑块的span标签被定位到
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代表点击并保持点击动作。move_by_offset(x, y),其中x代表水平移动距离,y代表垂直移动距离
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() # 先定位到出发地输入框点击后再传入参数
web.find_element(By.ID, 'fromStationText').send_keys(start_city, Keys.ENTER) # Keys库可以模拟实现键盘上的功能键
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页面默认当天日期,所以先清空默认内容在输入参数
web.find_element(By.ID, 'train_date').send_keys(date, Keys.ENTER)
web.find_element(By.ID, 'query_ticket').click() # 点击查询
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] + '|' # 添加标识头和分隔符便于观看
tr_from_place = '出发地:' + 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 = '出发时间:' + tr.xpath('./td[1]/div/div[3]/strong[1]/text()')[0] + ' ' # 列车发动时间
tr_get_time = '到达时间:' + tr.xpath('./td[1]/div/div[3]/strong[2]/text()')[0] + ' ' # 列车到达目的地时间
if not tr.xpath('./td[2]/@aria-label'):
tr_shang_wu = 'Null'
else:
tr_shang_wu = '商务座:' + tr.xpath('./td[2]/@aria-label')[0] + ' ' # 商务座
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('请输入选择的车次:\n')) # 车次:与输入的车次num拼接为字典的key值
train_id = '车次:' + id
print('您选择的车次为:', id)
while train_id in dic.keys():
tr_info = dic.get(train_id) # 根据key值信息获取到其保存在value里的id与座位信息等
obj = re.compile(r'车辆ID为:(?P<id>.*?)出发地') # 利用正则获取到车次id
result = obj.finditer(tr_info) # 此时获取到的是迭代器,要重新获取出来
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': # 设定抢票时间,一般为下午五点:17:00:00
web.refresh()
web.find_element(By.ID, 'query_ticket').click() # 点击重新查询
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() # 根据id匹配到车次所在列并点击末尾的预定按钮
WebDriverWait(web, 2, 0.1).until(EC.url_to_be('https://kyfw.12306.cn/otn/confirmPassenger/initDc'))
if count == 1:
my_self = 'N' # 根据个人情况,如果有学生票选项的话就自己添加一个判断,我这里默认平时学生票为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,可能会导致确认点击操作无法正确执行,所以利用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):
# 当在此页面见不到此元素,代表已进入付款页面
break
print('抢票成功,请尽快付款!')
except:
pass
if __name__ == '__main__':
opt = Options()
opt.add_experimental_option('excludeSwitches', ['enable-automation']) # 去除浏览器顶部显示受自动化程序控制
opt.add_experimental_option('detach', True) # 规避程序运行完自动退出浏览器
web = Chrome(options=opt)
web.get('https://kyfw.12306.cn/otn/resources/login.html')
# 解除浏览器特征识别selenium
script = 'Object.defineProperty(navigator,"webdriver", {get: () => false,});'
web.execute_script(script)
user = '' # 此处输入账号
pwd = '' # 此处输入密码
start_city = '' # 出发城市
end_city = '' # 目的城市
date = '' # 格式参照2023-05-17
t_id = '' # 想要选择的车次id
person1 = '' # 这里根据自己账号具体保存的个人信息选择乘车人,一人就只需定义一个person,多人数以此累加
# 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('信息输入成功,请重新运行程序')
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: # 建议半小时刷新一次cookie
login(user, pwd)
web.refresh()
print('信息输入成功,请重新运行程序')
web.close()
else:
cookies_login() # 利用cookie实现快速登陆
get_ticket_info(start_city, end_city, date) # 获取到查询信息
tick_dic = get_ticket_dic_info() # 查询信息封装成字典传递给选票
chick_ticket(tick_dic, t_id, person_count) # 进行购票
代码放上,那我们就开搞开搞
代码更新
相较于第一版,我们进行了重大更新,没有看过之前文章的xdm可以去看看我的主页哈。首先是在登录方式上。老版的登录方式过于繁琐且耗时。听取上次评论兄弟们的意见换成了cookie登录。
首先我们先定义了一个判断语句,检索当下目录有没有用户的cookie文件。我命名为12306cookies.pkl。
if not os.path.exists('./12306cookies.pkl'):
login(user, pwd)
web.refresh()
print('信息输入成功,请重新运行程序')
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: # 建议半小时刷新一次cookie
login(user, pwd)
web.refresh()
print('信息输入成功,请重新运行程序')
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)
如上面代码所示,如果本地未检测到cookie文件,首先会执行账号密码登录,这一块采用的是老版的登录方式,代码没有太大改动,只是在最后利用pickle保存了登录后的用户cookie信息
pickle.dump(web.get_cookies(), open('./12306cookies.pkl', 'wb'))
print('cookies保存成功')
我在这里设定的是获取到cookie信息后,关闭程序退出。再次执行时就会直接进入到查询界面
if not os.path.exists('./12306cookies.pkl'):
login(user, pwd)
web.refresh()
print('信息输入成功,请重新运行程序')
web.close()
在这里我再次进行了一次判断,利用时间函数判断cookie文件修改的时间,超过半小时就需要重新账号密码登录刷新cookie信息。
到此一切的目的都是为了在购票前半小时提前准备好一切工作。
if not os.path.exists('./12306cookies.pkl'):
login(user, pwd)
web.refresh()
print('信息输入成功,请重新运行程序')
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: # 建议半小时刷新一次cookie
login(user, pwd)
web.refresh()
print('信息输入成功,请重新运行程序')
web.close()
当前面所有步骤全部无误时,再次执行程序就可以直接cookie登录了
cookies_login() # 利用cookie实现快速登陆
get_ticket_info(start_city, end_city, date) # 获取到查询信息
tick_dic = get_ticket_dic_info() # 查询信息封装成字典传递给选票
chick_ticket(tick_dic, t_id, person_count) # 进行购票
有关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') # 直接进如查询界面
print('cookies载入成功')
下面就是在我看来改动最大的地方
购票函数
def chick_ticket(dic, id, count):
# print('*' * 15 + '查询到的车次信息如下' + '*' * 30)
# print(str(dic).replace(',', '\n'))
# train_id = '车次:' + str(input('请输入选择的车次:\n')) # 车次:与输入的车次num拼接为字典的key值
train_id = '车次:' + id
print('您选择的车次为:', id)
while train_id in dic.keys():
tr_info = dic.get(train_id) # 根据key值信息获取到其保存在value里的id与座位信息等
obj = re.compile(r'车辆ID为:(?P<id>.*?)出发地') # 利用正则获取到车次id
result = obj.finditer(tr_info) # 此时获取到的是迭代器,要重新获取出来
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': # 设定抢票时间,一般为下午五点:17:00:00
web.refresh()
web.find_element(By.ID, 'query_ticket').click() # 点击重新查询
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() # 根据id匹配到车次所在列并点击末尾的预定按钮
WebDriverWait(web, 2, 0.1).until(EC.url_to_be('https://kyfw.12306.cn/otn/confirmPassenger/initDc'))
if count == 1:
my_self = 'N' # 根据个人情况,如果有学生票选项的话就自己添加一个判断,我这里默认平时学生票为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,可能会导致确认点击操作无法正确执行,所以利用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):
# 当在此页面见不到此元素,代表已进入付款页面
break
print('抢票成功,请尽快付款!')
except:
pass
可以看到每一行基本都有我的注释,正文里面就不再解释啦。主要阐述下设计思路。
在代码中设定了抢票时间,利用while不断循环。实现了只要在抢票前30分钟运行了此程序后,就可以放心不去管了。当时间到了设定的购票时间时。程序会迅速发送请求。为了速度,我这里并没有设计选座,追求直接发送购票请求。毕竟是为了能够抢到想要的车票,座位什么的也就不太重要了。
当然,就像我注释所说的那样,每个人的情况都不同,有学生票的情况,也有想购买多张票的情况。在我的代码中只是为了满足我的需求而设计的。大家如果想要应用于实际的话建议自己修改程序。目前为止我的测试都是在访问量不算大的情况下实现的。等后面有机会了还会根据实际情况进行进一步的修改。到此,关于12306登录的文章就暂时不会再更新啦。大家有兴趣的话可以自己在接着研究研究吧。文章来源:https://www.toymoban.com/news/detail-455265.html
水文结束,接着潜水。拜拜。文章来源地址https://www.toymoban.com/news/detail-455265.html
到了这里,关于爬虫练习-12306自动购票升级版的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!