提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
随着春节的临近,买票成为一大难题,因此自己写一个简单的买票小程序就变得很有意思了
提示:以下是本篇文章正文内容,下面案例可供参考
一、程序的整体的架构
在书写程序之前,我们应该要先来了解一下我们在12306买票的时候需要进行什么样的流程,我们对车票进行查询后,如果有票,那么我们就进行登录,登录成功后就进行买票了。大概的流程图如下:
有了这个流程图之后,我们就可以对我们的程序进行一些模块的设计了
二、代码书写
1.UI设计
通过designer来设计UI,此次共设计了三个UI
?
2.查询模块
查询模块的书写需要注意的是,我们用requests拿到地址后,还要有12306查票时需要提交的信息
#12306车票地址
tikes="https://kyfw.12306.cn/otn/leftTicket/query"
#需要提交的信息,用字典进行封装
query_params = {
"leftTicketDTO.train_date": tran_date,
"leftTicketDTO.from_station": from_station,
"leftTicketDTO.to_station": to_station,
"purpose_codes": purpose_code
}
如果还是没法拿到数据,那么我们可以在加一个头,将自己伪装成为一个浏览器(我使用的Chrome浏览器)
headrs = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36',
'Cookie': '_uab_collina=163689535304202042315973; JSESSIONID=C89EB44A4BDC97B8EB9A4E7955FCA94F; route=9036359bb8a8a461c164a04f8f50b252; BIGipServerotn=2280128778.24610.0000; RAIL_EXPIRATION=1637210970420; RAIL_DEVICEID=FBb96qPa1M1EqO-Nj9rP-_kseasv05MVTorGDOCS6EBOT-Dei-zT7_dxLz5I-ktyJJ_ZQeD8pa4BniUQSmBh2bNykBQh_ATcwDA0JEB3yQpJ59wbHORgQ9Y-rzNJoJnVhj2mGRtpgQJno_SPSGDhLreIOTfiZ5mc; guidesStatus=off; highContrastMode=defaltMode; cursorStatus=off; _jc_save_fromStation=%u5317%u4EAC%2CBJP; _jc_save_toStation=%u5929%u6D25%2CTJP; _jc_save_fromDate=2021-11-14; _jc_save_toDate=2021-11-14; _jc_save_wfdc_flag=dc'}
?拿到的是json数据,因此我们需要将数据进行解析
resp = requests.get(API.QUERY_tikes, params=query_params, headers=headrs)
resp.encoding = resp.apparent_encoding
接下来就是对数据进行简单的处理
items = resp.json()['data']['result']
train_dicts = []
for item in items:
trainDict = {}
trainInfo = item.split('|')
if trainInfo[11] == 'Y': # 是否可以预定
trainDict['secret_str'] = trainInfo[0] # 车次密文字符串(下订单时使用)
trainDict['train_num'] = trainInfo[2] # 车次编号 24000000Z30L
trainDict['train_name'] = trainInfo[3] # 车次名称,如D352
trainDict['from_station_code'] = trainInfo[6] # 出发地电报码
trainDict['to_station_code'] = trainInfo[7] # 目的地地电报码
trainDict['from_station_name'] = code2station[trainInfo[6]] # 出发地名称 手动实现 北京
trainDict['to_station_name'] = code2station[trainInfo[7]] # 目的地名称 手动实现 上海
trainDict['start_time'] = trainInfo[8] # 出发时间
trainDict['arrive_time'] = trainInfo[9] # 到达时间
trainDict['total_time'] = trainInfo[10] # 总用时
trainDict['left_ticket'] = trainInfo[12] # 余票 wrlmtI6BmBd8izygoiCBbpr3%2B%2BGKdIk1SHpJdJ1f6w1p%2FhGF
trainDict['train_date'] = trainInfo[13] # 火车日期 20190121
trainDict['train_location'] = trainInfo[15] # P4 后期用
trainDict["vip_soft_bed"] = trainInfo[21] # 高级软卧
trainDict['other_seat'] = trainInfo[22] # 其他
trainDict["soft_bed"] = trainInfo[23] # 软卧
trainDict["no_seat"] = trainInfo[26] # 无座
trainDict["hard_bed"] = trainInfo[28] # 硬卧
trainDict['hard_seat'] = trainInfo[29] # 硬座
trainDict["second_seat"] = trainInfo[30] # 二等座
trainDict["first_seat"] = trainInfo[31] # 一等座
trainDict["business_seat"] = trainInfo[32] # 商务座
trainDict["move_bed"] = trainInfo[33] # 动卧
查询模块基本到这就结束了,接下来就是抢票模块
3.抢票模块
在抢票模块中加了一个如果选择了某一座位类型的车次之后,查询模块会筛选数据,具体代码如下:
if seattype == None:
train_dicts.append(trainDict)
else:
print(seattype)
key = config.seat_type_map_dic[seattype]
print(key)
print(trainDict[key])
if trainDict[key] == "有" or trainDict[key].isdigit():
train_dicts.append(trainDict)
print(trainDict)
完成的查询代码
def que_titkes(cls, tran_date, from_station, to_station, purpose_code, seattype=None):
code2station = APItool.get_all_stations_revers()
query_params = {
"leftTicketDTO.train_date": tran_date,
"leftTicketDTO.from_station": from_station,
"leftTicketDTO.to_station": to_station,
"purpose_codes": purpose_code
}
headrs = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36',
'Cookie': '_uab_collina=163689535304202042315973; JSESSIONID=C89EB44A4BDC97B8EB9A4E7955FCA94F; route=9036359bb8a8a461c164a04f8f50b252; BIGipServerotn=2280128778.24610.0000; RAIL_EXPIRATION=1637210970420; RAIL_DEVICEID=FBb96qPa1M1EqO-Nj9rP-_kseasv05MVTorGDOCS6EBOT-Dei-zT7_dxLz5I-ktyJJ_ZQeD8pa4BniUQSmBh2bNykBQh_ATcwDA0JEB3yQpJ59wbHORgQ9Y-rzNJoJnVhj2mGRtpgQJno_SPSGDhLreIOTfiZ5mc; guidesStatus=off; highContrastMode=defaltMode; cursorStatus=off; _jc_save_fromStation=%u5317%u4EAC%2CBJP; _jc_save_toStation=%u5929%u6D25%2CTJP; _jc_save_fromDate=2021-11-14; _jc_save_toDate=2021-11-14; _jc_save_wfdc_flag=dc'}
resp = requests.get(API.QUERY_tikes, params=query_params, headers=headrs)
resp.encoding = resp.apparent_encoding
items = resp.json()['data']['result']
train_dicts = []
for item in items:
trainDict = {}
trainInfo = item.split('|')
if trainInfo[11] == 'Y': # 是否可以预定
trainDict['secret_str'] = trainInfo[0] # 车次密文字符串(下订单时使用)
trainDict['train_num'] = trainInfo[2] # 车次编号 24000000Z30L
trainDict['train_name'] = trainInfo[3] # 车次名称,如D352
trainDict['from_station_code'] = trainInfo[6] # 出发地电报码
trainDict['to_station_code'] = trainInfo[7] # 目的地地电报码
trainDict['from_station_name'] = code2station[trainInfo[6]] # 出发地名称 手动实现 北京
trainDict['to_station_name'] = code2station[trainInfo[7]] # 目的地名称 手动实现 上海
trainDict['start_time'] = trainInfo[8] # 出发时间
trainDict['arrive_time'] = trainInfo[9] # 到达时间
trainDict['total_time'] = trainInfo[10] # 总用时
trainDict['left_ticket'] = trainInfo[12] # 余票 wrlmtI6BmBd8izygoiCBbpr3%2B%2BGKdIk1SHpJdJ1f6w1p%2FhGF
trainDict['train_date'] = trainInfo[13] # 火车日期 20190121
trainDict['train_location'] = trainInfo[15] # P4 后期用
trainDict["vip_soft_bed"] = trainInfo[21] # 高级软卧
trainDict['other_seat'] = trainInfo[22] # 其他
trainDict["soft_bed"] = trainInfo[23] # 软卧
trainDict["no_seat"] = trainInfo[26] # 无座
trainDict["hard_bed"] = trainInfo[28] # 硬卧
trainDict['hard_seat'] = trainInfo[29] # 硬座
trainDict["second_seat"] = trainInfo[30] # 二等座
trainDict["first_seat"] = trainInfo[31] # 一等座
trainDict["business_seat"] = trainInfo[32] # 商务座
trainDict["move_bed"] = trainInfo[33] # 动卧
if seattype == None:
train_dicts.append(trainDict)
else:
print(seattype)
key = config.seat_type_map_dic[seattype]
print(key)
print(trainDict[key])
if trainDict[key] == "有" or trainDict[key].isdigit():
train_dicts.append(trainDict)
print(trainDict)
return train_dicts
在查询模块中,我把代码封装到了一个类的函数中,因此在抢票模块中,我们定时器设置,只需要定时的执行查询模块,查询模块执行完成后,会对相应的作为数据进行判断,如果有座位,那么就会进入后续的一个登录的界面,输入自己账号和密码后,就进入selenium网页自动进行登录和抢票。
下面是定时器的函数:
self.timer=QTimer(self)
self.timer.timeout.connect(self.buy_tikit)
登录模块的代码:
def checklogin(self):
self.account = self.comboBox.currentText()
self.password = self.password_le.text()
self.cursor.execute(self.sql, (self.account, self.password))
sel_qiang = selinu_qiang.Qiangpiao(self.from_station, self.to_station, self.depart_time, self.train_num,
self.passenger, self.account, self.password)
sel_qiang.run()
4.selenium网页买票
该模块我参考的是以下大佬的博客
????????最新12306抢票爬虫_阿牛的博客-CSDN博客_12306爬虫自动抢票
经过简单的修改和UI传参,实现了多个模块之间的联系
具体的代码如下:
import time
import datetime
from PyQt5.QtWidgets import QMessageBox
from selenium import webdriver
from selenium.webdriver.chrome.webdriver import WebDriver
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver import ChromeOptions
import Login_Pane
class Qiangpiao():
def __init__(self,from_station,to_station,depart_time,train_num,passenger,username,password):
self.login_url = 'https://kyfw.12306.cn/otn/resources/login.html'
self.init_my_url = 'https://kyfw.12306.cn/otn/view/index.html'
self.order_url = 'https://kyfw.12306.cn/otn/confirmPassenger/initDc'
self.train_num = train_num
self.passenger = passenger
self.userna=username
self.password=password
self.Login_ti=Login_Pane.LoginPane
#获取当前月份
self.now_month = datetime.date.today().month
print(self.now_month)
def _login(self):
option = ChromeOptions()
option.add_experimental_option('excludeSwitches', ['enable-automation'])
self.driver = webdriver.Chrome(options=option)
self.driver.maximize_window()
# 使用selenium打开登陆页面
self.driver.get('https://kyfw.12306.cn/otn/resources/login.html')
script = 'Object.defineProperty(navigator,"webdriver",{get:()=>undefined,});'
self.driver.execute_script(script)
self.driver.maximize_window()
# 谷歌关于验证码的显示位置自己试的时候出现了错位的情况,所以多写了两个跳转刷新的操作
time.sleep(3)
self.driver.find_element_by_xpath('//*[@id="J-userName"]').send_keys("19102870304")
time.sleep(2)
self.driver.find_element_by_xpath('//*[@id="J-password"]').send_keys("tlmm123")
time.sleep(2)
self.driver.find_element_by_xpath('//*[@id="J-login"]').click()
time.sleep(3)
# 滑块验证
span = self.driver.find_element_by_xpath('//*[@id="nc_1_n1z"]')
time.sleep(1)
action = ActionChains(self.driver)
time.sleep(2)
action.click_and_hold(span)
action.drag_and_drop_by_offset(span, 300, 0).perform()
time.sleep(5)
# WebDriverWait(self.driver, 1000).until(EC.url_to_be(self.init_my_url))
print('登录成功!')
def _pop_window(self):
time.sleep(1)
self.driver.find_element_by_xpath('//*[@class="dzp-confirm"]/div[2]/div[3]/a').click()
def _enter_order_ticket(self):
action = ActionChains(self.driver)
element = self.driver.find_element_by_link_text('车票')
# 鼠标移动到 '车票' 元素上的中心点
action.move_to_element(element).perform()
# 点击'单程'
self.driver.find_element_by_xpath('//*[@id="J-chepiao"]/div/div[1]/ul/li[1]/a').click()
# 消除第二次弹窗
self.driver.find_element_by_link_text('确认').click()
def _search_ticket(self):
#出发地输入
self.driver.find_element_by_id("fromStationText").click()
self.driver.find_element_by_id("fromStationText").send_keys(self.from_station)
self.driver.find_element_by_id("fromStationText").send_keys(Keys.ENTER)
#目的地输入
self.driver.find_element_by_id("toStationText").click()
self.driver.find_element_by_id("toStationText").send_keys(self.to_station)
self.driver.find_element_by_id("toStationText").send_keys(Keys.ENTER)
#出发日期输入
self.driver.find_element_by_id("date_icon_1").click()
#等待查询按钮是否可用
WebDriverWait(self.driver,1000).until(EC.element_to_be_clickable((By.ID,"query_ticket")))
#执行点击事件
search_btn = self.driver.find_element_by_id("query_ticket")
search_btn.click()
#等待查票信息加载
WebDriverWait(self.driver, 1000).until(EC.presence_of_element_located((By.XPATH, '//*[@id="queryLeftTable"]/tr')))
def _order_ticket(self):
train_num_list = []
train_num_ele_list = self.driver.find_elements_by_xpath('//tr/td[1]/div/div[1]/div/a')
for t in train_num_ele_list:
train_num_list.append(t.text)
tr_list = self.driver.find_elements_by_xpath('//*[@id="queryLeftTable"]/tr[not(@datatran)]')
if self.train_num in train_num_list:
for tr in tr_list:
train_num = tr.find_element_by_xpath("./td[1]/div/div[1]/div/a").text
if self.train_num == train_num:
#动车二等座余票信息
text_1 = tr.find_element_by_xpath("./td[4]").text
# 火车二等座余票信息
text_2 = tr.find_element_by_xpath("./td[8]").text
if (text_1 == "有" or text_1.isdigit()) or (text_2 == "有" or text_2.isdigit()):
#点击预订按钮
order_btn = tr.find_element_by_class_name("btn72")
order_btn.click()
#等待订票页面
WebDriverWait(self.driver,1000).until(EC.url_to_be(self.order_url))
# 选定乘车人
self.driver.find_element_by_xpath(f'//*[@id="normal_passenger_id"]/li/label[contains(text(),"{self.passenger}")]').click()
print("选定乘车人")
#如果乘客是学生,对提示点击确定
if EC.presence_of_element_located((By.XPATH, '//div[@id="dialog_xsertcj"]')):
print("订单提交")
# 提交订单
# self.driver.find_element_by_id('submitOrder_id').click()
time.sleep(2)
# 点击确认订单
# self.driver.find_element_by_id('qr_submit_id').click()
self.Login_ti.tixing(self)
else:
# 提交订单
print("else订单提交")
self.driver.find_element_by_id('submitOrder_id').click()
self.Login_ti.tixing(self)
time.sleep(2)
# 点击确认
# self.driver.find_element_by_id('qr_submit_id').click()
print("购票成功!")
break
else:
print("二等座无票!")
else:
print("无此列车!")
def run(self):
#登录
self._login()
#消除登录后(第一次)的弹窗
self._pop_window()
#进入购票页面
self._enter_order_ticket()
#查票
self._search_ticket()
#订票
self._order_ticket()
#关闭浏览器
time.sleep(6)
self.driver.quit()
if __name__ == '__main__':
qiangpiao = Qiangpiao("成都","重庆","2021-12-15","G8502","赵沁","19102870304","tlmm123")
qiangpiao.run()
以下代码段是为了避免浏览器识别出我是一个程序的
option = ChromeOptions()
option.add_experimental_option('excludeSwitches', ['enable-automation'])
self.driver = webdriver.Chrome(options=option)
被识别出来是测试软件后,会出现这样的字样,这个时候是无法进行验证的,但是加上以上代码段后就不会被识别出来了
总结
通过对多个模块的书写和调试,程序终于能够成功运行和买票了,但是还有很多的bug,正在不断的修改中,会在后续进行一些简单的更新
|