前言:
由于12306官网最近增加了验证码难度,由原先的滑块验证码修改为手机号验证,奈何本人技术不够,只能选择放弃,转而选择了较为简单的去哪儿网站。
使用技术:
1、pickle,用于保存和读取登录获取的cookie
2、webdriver,自动化检测模拟浏览器
3、configparser,用于读取配置文件,用户可以在配置文件输入自己的账号密码、车次信息,方便程序自动读取
4、smtp邮件服务,用于抢票完成后发送邮件提醒用户尽快付款
5、win10定时任务,在放票的前一分钟启动脚本
源码展示
import os
import pickle
from datetime import datetime
from selenium import webdriver
from selenium.webdriver import ActionChains
from selenium.webdriver.common.by import By
import time
import configparser
import smtplib
from email.mime.text import MIMEText
start_time=datetime.now()
driver=webdriver.Chrome()
config_path=os.path.join(os.path.dirname(os.path.abspath(__file__)),'config.ini')
config=configparser.ConfigParser()
config.read(config_path,'utf8')
#登录
def login():
driver.get("https://user.qunar.com/passport/login.jsp?ret=https%3A%2F%2Fwww.qunar.com%2F")
driver.find_element(By.CLASS_NAME,'passwordTab').click()
username=config.get('qunar','username')
driver.find_element(By.ID, 'username').send_keys(username)
password=config.get('qunar','password')
driver.find_element(By.ID, 'password').send_keys(password)
driver.find_element(By.ID,'agreement').click()
driver.find_element(By.XPATH,'//*[@id="app"]/div/div[2]/div/div[1]/div[3]/div/div[3]/span').click()
# 滑动验证码
# 定位验证码的头位置元素
picture_start = driver.find_element(By.XPATH,'//*[@id="app"]/div/div[2]/div/div[1]/div[3]/div/div[5]/div/div/div[3]/div[3]')
# # 移动到相应的位置,并左键鼠标按住往右边拖
ActionChains(driver).move_to_element(picture_start).click_and_hold(picture_start).move_by_offset(500,0).release().perform()
# # 判断是否登录成功,进入主页
time.sleep(1)
if driver.current_url != 'https://www.qunar.com/' :
raise ValueError("账号或密码错误!")
print('开始模拟登录')
login()
# 保存登录获取的cookie
time.sleep(2)
filepath=os.path.join(os.path.dirname(os.path.abspath(__file__)),'cookies.pkl')
if os.path.exists(filepath) :
with open(filepath,'w') as file:
pass
pickle.dump(driver.get_cookies(),open(filepath,'wb'))
print('登录成功,开始查询车次列表')
#搜索火车票
start=config.get('train','from')
stop=config.get('train','to')
date=config.get('train','date')
#读取cookie保持登录状态
cookies=pickle.load(open(filepath,'rb'))
for cookie in cookies:
driver.add_cookie(cookie)
driver.get('https://train.qunar.com/stationToStation.htm?fromStation=%s&toStation=%s&date=%s'%(start,stop,date))
time.sleep(2)
#实体类,车次信息
class Train:
def __init__(self,train,start,stop,start_time,endtime,duration,price):
self.train=train
self.start=start
self.stop=stop
self.start_time=start_time
self.endtime=endtime
self.duration=duration
self.price=price
def __str__(self):
return ('车次信息:%s\t发站:%s\t到站:%s\t发时间:%s\t到时间:%s\t运行时间:%s\t票价:%s' % (
self.train, self.start, self.stop, self.start_time, self.endtime, self.duration, self.price))
#车票列表
desirable_time=config.get('train','time')
try:
desirable_hour=desirable_time.split(":")[0]
desirable_min=desirable_time.split(":")[1]
except Exception as e:
desirable_hour=desirable_time.split(":")[0]
desirable_min=desirable_time.split(":")[1]
min_time=datetime(2023,8,21,int(desirable_hour),int(desirable_min),0)
def choose_train():
tbody = driver.find_element(By.CLASS_NAME, 'tbody')
lists = tbody.find_elements(By.CLASS_NAME, 'js_listinfo')
for list in lists:
# 车次
train=list.find_element(By.CLASS_NAME,'train').text
#发站
start=list.find_element(By.CLASS_NAME,'start').text.split("\n")[1]
#到站
stop=list.find_element(By.CLASS_NAME,'end').text.split('\n')[1]
#发时间
start_time=list.find_element(By.CLASS_NAME,'startime').text
start_hour=start_time.split(":")[0]
start_min=start_time.split(":")[1]
#到时间
endtime=list.find_elements(By.TAG_NAME,'time')[1].text
#运行时间
duration=list.find_element(By.CLASS_NAME,'duration').text
#票价
price=list.find_elements(By.CLASS_NAME,'price')[0].text
#计算时间差
train_time=datetime(2023,8,21,int(start_hour),int(start_min),00)
diff=(train_time - min_time).total_seconds()
if diff>=0:
train_info= Train(train, start, stop, start_time, endtime, duration, price)
print('正在尝试购买:',train_info)
#购买车票
purchase=list.find_elements(By.TAG_NAME,'a')[0]
# 购买按钮,有则进入购买页面,无则进入下一循环
if purchase.text.__contains__('买'):
try:
driver.execute_script('arguments[0].click();',purchase)
time.sleep(2)
except Exception as e:
# 余票不足,点击后会自动刷新页面
print('余票不足,尝试下一车次......')
time.sleep(2)
choose_train()
#登录12306
driver.find_element(By.ID,'gotoLogin12306Btn').click()
name=config.get('12306_info','username')
driver.find_element(By.NAME,'uname').send_keys(name)
password=config.get('12306_info','password')
driver.find_element(By.NAME,'upwd').send_keys(password)
driver.find_element(By.CSS_SELECTOR,'a[class="loginBtn js-login12306"]').click()
time.sleep(5)
#选择乘客
passengers=config.get('train','passengers')
choices=driver.find_elements(By.NAME,'checkboxmutiple')
for choice in choices:
if passengers.__contains__(choice.get_attribute('data-name')):
choice.click()
#取票联系人
contract_name=config.get('train','contact_name')
driver.find_element(By.NAME,'contact_name').send_keys(contract_name)
contract_phone=config.get('train','contact_phone')
driver.find_element(By.NAME,'contact_phone').send_keys(contract_phone)
driver.find_element(By.ID,'fillOrder_eTicketNormalSubmit').submit()
print('抢购完成!等待出单中......')
# 发送邮箱提醒
sender = '[email protected]' # 发件人邮箱
receiver = config.get('email','receiver') # 收件人邮箱
# 配置邮件内容
body='抢票完成!\n'+train_info.__str__()+'\n请尽快前往支付!'
message = MIMEText(body) # body
message['subject'] = '抢票完成!' # subject
message['From'] = sender
message['To'] = receiver
# 连接服务器
smtp_server = smtplib.SMTP('smtp.qq.com')
smtp_server.ehlo()
smtp_server.starttls()
smtp_server.ehlo()
smtp_server.login(sender, 'fnwlmuviedjbebdc') # smtp密码
smtp_server.sendmail(sender, receiver, message.as_string())
smtp_server.quit()
break
elif purchase.text.__contains__('约'):
print('未到售票时间,刷新页面......')
driver.refresh()
time.sleep(1)
choose_train()
else:
print('该车次暂无余票,尝下一车次......')
continue
# 循环判断是否有可购买车票,循环结束刷新页面
choose_train()
end_time=datetime.now()
print('用时:',(end_time-start_time))