selenium实现12306全自动购票

**思路:**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()
  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值