**思路:**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=driverself.login_url=login_urlself.user_name=user_nameself.password=passwordself.fromStation=fromStationself.toStationText=toStationTextself.train_date=train_dateself.passenger_list=passenger_listdef 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_handlesself.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]').textif left_ticket == '有' or left_ticket.isdigit:orderBtn = tr.find_element_by_class_name('btn72')orderBtn.click()time.sleep(2)self.confirm_Passenger()breakdef confirm_Passenger(self):#乘客确认页,选择要购票乘客all_handles = self.driver.window_handlesself.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').textfor 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识别出来用的是seleniumlogin_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 ECclass Code():def __init__(self, browser):self.browser = browserself.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('验证码未能识别!重新识别验证码...')returntry:if int(num):print('验证码识别成功!图片位置:%s' % num)return [int(num)]except ValueError:try:num = list(map(int,num.split()))print('验证码识别成功!图片位置:%s' % num)return numexcept 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 -= 4ActionChains(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_funcoffset = 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] #滑动轨迹可随意,只要距离大于300try: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()