**思路:**1.selenium 定位登录12306;
2.登录后跳转至index.html页,自动输入行程+日期,选择"高铁/动车",点击"查询"按钮;
3.跳转至购票页https://kyfw.12306.cn/otn/leftTicket/init?linktypeid=dc&fs=%E5%8C%97%E4%BA%AC%E5%8C%97,VAP&ts=%E6%AD%A6%E6%B1%89,WHN&date=2021-01-30&flag=N,Y,Y,用selenium定位到
车次列表详细信息,循环遍历车次列表信息,判断余票(本次实例仅判断了二等座余票),如果有则点击"预定"按钮;
4.跳转至乘客确认页,选择购票乘客;
5.乘客选择完弹出div浮层,选择"1D"、“1F” 座位,点击"确认"按钮后完成购票。
注:因12306购票,每天只能取消3次,所以最后一个点击"确认"按钮慎用。
**代码:**共2个py文件(buy_tickets_by_selenium.py、captcha.py)
buy_tickets_by_selenium.py文件为主要核心流程
captcha.py文件为验证码实现流程+滑动解锁功能
**总结:**本例仅实现购票流程,但是真正意义上要抢票,用selenium需认真考虑。做的过程当中发现selenium存在稳定性问题,同样代码今天运行ok,明天运行也许就挂了,这个跟网络、页面加载等等都会有关。还有些页面元素加载超级慢,需强制等待几秒,这又影响了抢票效率。所以建议用requests写这样速度上应该会有提升,在requests写时需考虑浏览器cookie及购票页的url拼接,因为购票页url做了编码及额外参数拼接(例如你输入北京北参数,url参数拼接时是北京北,VAP,VAP是额外加的),这时就需要把12306源码下载下来分析具体js。目前来说用requests实现,还需要再研究研究! buy_tickets_by_selenium.py代码:
from selenium import webdriver
from captcha import Code
import time
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
class buy_ticket:
def __init__(self,driver,login_url,user_name,password,fromStation,toStationText,train_date,passenger_list):
self.driver=driver
self.login_url=login_url
self.user_name=user_name
self.password=password
self.fromStation=fromStation
self.toStationText=toStationText
self.train_date=train_date
self.passenger_list=passenger_list
def login(self):#登录
self.driver.get(login_url)
time.sleep(0.5)#登录首页有2个div进行切换,一个是二维码登录div,一个是账号登录div。默认显示二维码登录div,隐藏账号登录div.
#如果不在此等0.5s可能导致二维码登录div正在加载状态时,点击了账号登录div,此时账号登录div与二维码登录div会同时重叠展现
self.driver.find_element_by_xpath("//div[@class='login-box']/ul/li[2]/a").click()
self.driver.find_element_by_id("J-userName").send_keys(self.user_name)
self.driver.find_element_by_id("J-password").send_keys(self.password)
c=Code(self.driver)
c.main()
time.sleep(3)
self.driver.find_element_by_xpath("//div[@class='dzp-confirm']/div[2]/div[3]/a").click()
self.driver.find_element_by_xpath("//li[@id='J-index']/a").click()
self.ticket_index()
def ticket_index(self):#跳转到购票首页
#输入起始地
WebDriverWait(self.driver, 10).until(
EC.presence_of_element_located((By.ID, 'fromStationText'))
)
self.driver.find_element_by_id("fromStationText").click()
self.driver.find_element_by_id("fromStationText").send_keys(self.fromStation)
self.driver.find_element_by_id("fromStationText").send_keys(Keys.ENTER)
#输入目的地
WebDriverWait(self.driver, 10).until(
EC.presence_of_element_located((By.ID, 'toStationText'))
)
self.driver.find_element_by_id("toStationText").click()
self.driver.find_element_by_id("toStationText").send_keys(self.toStationText)
self.driver.find_element_by_id("toStationText").send_keys(Keys.ENTER)
#输入日期
js="document.getElementById('train_date').removeAttribute('readonly')"#将日期的只读属性去掉便于下面输入日期
self.driver.execute_script(js)
WebDriverWait(self.driver, 10).until(
EC.presence_of_element_located((By.ID, 'train_date'))
)
self.driver.find_element_by_id("train_date").clear()#清空默认日期值
self.driver.find_element_by_id("train_date").send_keys(self.train_date)
time.sleep(0.5)
js1="document.querySelector('body > div.cal-wrap').style.display='none'"
self.driver.execute_script(js1)
#点击高铁/动车
WebDriverWait(self.driver,10).until(
EC.presence_of_element_located((By.ID,'isHighDan'))
)
self.driver.find_element_by_id('isHighDan').click()
#点击"查询"按钮
WebDriverWait(self.driver, 10).until(
EC.presence_of_element_located((By.ID, 'search_one'))
)
self.driver.find_element_by_id("search_one").click()
self.check_tickets()
def check_tickets(self):#跳转至购票页检查二等座是否有票,有票开始购买
time.sleep(5)
all_handles=self.driver.window_handles
self.driver.switch_to.window(all_handles[-1])
trs=self.driver.find_elements_by_xpath("//div[@id='t-list']/table/tbody[1]/tr")
for tr in trs:
left_ticket = tr.find_element_by_xpath('//td[4]').text
if left_ticket == '有' or left_ticket.isdigit:
orderBtn = tr.find_element_by_class_name('btn72')
orderBtn.click()
time.sleep(2)
self.confirm_Passenger()
break
def confirm_Passenger(self):#乘客确认页,选择要购票乘客
all_handles = self.driver.window_handles
self.driver.switch_to.window(all_handles[-1])
li_list=self.driver.find_elements_by_xpath("//ul[@id='normal_passenger_id']/li")
for li in li_list:
passenger=li.find_element_by_xpath('./label').text
for pger in self.passenger_list:
if passenger==pger:
li.find_element_by_xpath("./label").click()
self.driver.find_element_by_id("submitOrder_id").click()
time.sleep(1)
self.driver.find_element_by_xpath("//div[@id='erdeng1']/ul[2]/li[1]/a").click()#二等座D座位
self.driver.find_element_by_xpath("//div[@id='erdeng1']/ul[2]/li[2]/a").click()#二等座F座位
# 点击"确认"按钮
WebDriverWait(self.driver, 10).until(
EC.presence_of_element_located((By.ID, 'qr_submit_id'))
)
self.driver.find_element_by_id("qr_submit_id").click()
if __name__=="__main__":
options = webdriver.ChromeOptions() # 设置浏览器属性
options.add_argument(
'user-agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36 Edg/86.0.622.69"') # 添加UA属性
options.add_experimental_option("excludeSwitches", ["enable-automation"]) # 设置开发者模式启动,该模式下webdriver属性为正常值
options.add_experimental_option('useAutomationExtension', False)
driver = webdriver.Chrome(options=options)
driver.maximize_window()
driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
"source": """
Object.defineProperty(navigator, 'webdriver', {
get: () => undefined
})
"""
}) # 增加反爬机制,防止被12306识别出来用的是selenium
login_url="https://kyfw.12306.cn/otn/resources/login.html"
bt=buy_ticket(driver,login_url,"username","password","北京","武汉","2021-01-30",["passenger1","passenger2"])
bt.login()
captcha.py代码:
import time
import base64
import requests
import numpy as np
from selenium.webdriver import ActionChains
from selenium.webdriver.support import wait
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
class Code():
def __init__(self, browser):
self.browser = browser
self.verify_url = 'http://littlebigluo.qicp.net:47720/'#验证码识别网址,返回识别结果
#获取验证码图片
def get_captcha(self):
element = self.browser.find_element_by_class_name('imgCode')
#time.sleep(0.5)
img = base64.b64decode(element.get_attribute('src')[len('data:image/jpg;base64,'):])
with open('captcha.png', 'wb') as f:
f.write(img)
#验证码解析
def parse_img(self):
pic_name = 'captcha.png'
# 打开保存到本地的验证码图片
files={'pic_xxfile':(pic_name,open(pic_name,'rb'),'image/png')}
response = requests.post(self.verify_url, files=files)
try:
num = response.text.split('<B>')[1].split('<')[0]
except IndexError: #验证码没识别出来的情况
print('验证码未能识别!重新识别验证码...')
return
try:
if int(num):
print('验证码识别成功!图片位置:%s' % num)
return [int(num)]
except ValueError:
try:
num = list(map(int,num.split()))
print('验证码识别成功!图片位置:%s' % num)
return num
except ValueError:
print('验证码未能识别')
return
#识别结果num都以列表形式返回,方便后续验证码的点击
#还有可能验证码没能识别出来
#实现验证码自动点击
def move(self):
num = self.parse_img()
if num:
try:
element = self.browser.find_element_by_class_name('loginImg')
for i in num:
if i <= 4:
ActionChains(self.browser).move_to_element_with_offset(element,40+72*(i-1),73).click().perform()#鼠标移动到图片位置点击,40+72*(i-1)代表x坐标,73代表Y坐标
#ActionChains 处理鼠标相关的操作,行为事件存储在ActionChains对象队列,当使用perform()时,对象事件按顺序依次执行
else :
i -= 4
ActionChains(self.browser).move_to_element_with_offset(element,40+72*(i-1),145).click().perform()
self.browser.find_element_by_class_name('login-btn').click()
self.slider()
except Exception as e:
print(e)
#print('元素不可选!')
else:
self.browser.find_element_by_class_name('lgcode-refresh').click() #刷新验证码
time.sleep(1.5)
self.main()
def ease_out_quart(self, x):
return 1 - pow(1 - x, 4)
#生成滑动轨迹
def get_tracks(self, distance, seconds, ease_func):
tracks = [0]
offsets = [0]
for t in np.arange(0.0, seconds, 0.1):
ease = ease_func
offset = round(ease(t / seconds) * distance)
tracks.append(offset - offsets[-1])
offsets.append(offset)
return tracks
#处理滑动验证码
def slider(self):
print('开始处理滑动验证码...')
#track = self.get_tracks(305, 1, self.ease_out_quart)
track = [30, 50, 90, 140] #滑动轨迹可随意,只要距离大于300
try:
slider = wait.WebDriverWait(self.browser, 5).until(
EC.presence_of_element_located((By.CLASS_NAME, 'nc_iconfont.btn_slide')))
ActionChains(self.browser).click_and_hold(slider).perform()
for i in track:
ActionChains(self.browser).move_by_offset(xoffset=i, yoffset=0).perform()
except:
print('验证码识别错误!等待验证码刷新,重新识别验证码...')
time.sleep(2.1) #验证码刷新需要2秒
self.main()
def main(self):
self.get_captcha()
self.move()