基于selenium实现12306半自动化抢票

基于selenium实现12306半自动化抢票

一、需求分析

  1. 打开12306官网
  2. 登录 (进入登录页面,扫码登录)
  3. 进入到个人主页
  4. 进入到购票页面
      录入出发地
      录入目的地
      录入出发时间
      点击查询
      选择要购买的车次 如G8924,选择坐席
      判断是否有票,点击预定
  5. 确定乘车人、席别
  6. 提交订单

二、代码实现

  • 运行代码需要在同目录下存在 火车站代码.xlsx 文件,若不存在可通过运行以下代码获得
import requests
import re
import openpyxl

headers={'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36'}

def get_station():
    url='https://kyfw.12306.cn/otn/resources/js/framework/station_name.js?station_version=1.9190'
    resp=requests.get(url,headers=headers)
    resp.encoding='utf-8'
    station=re.findall('([\u4e00-\u9fa5]+)\|([A-Z]+)',resp.text)
    return station

def save(lst):
    wb=openpyxl.Workbook()
    ws=wb.active
    for item in lst:
        ws.append(item)
    wb.save('火车站代码.xlsx')

if __name__ == '__main__':
    station_lst=get_station()
    save(station_lst)
  1. 导入模块
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait #显示等待
from selenium.webdriver.support import expected_conditions as ec #显示等待的条件
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import Select
from selenium.common.exceptions import NoSuchElementException,ElementNotVisibleException
import openpyxl
  1. 建立一个爬虫类,默认继承object类
class TrainSpider(object):
  1. 定义 类属性,定义init初始化方法
	#定义类属性
    login_url = 'https://kyfw.12306.cn/otn/resources/login.html' #登录的页面
    profile_url = 'https://kyfw.12306.cn/otn/view/index.html' #个人中心的网址
    left_Ticket = 'https://kyfw.12306.cn/otn/leftTicket/init?linktypeid=dc' #余票查询界面
    confirm_url = 'https://kyfw.12306.cn/otn/confirmPassenger/initDc'  # 确认乘车人和座席

    #定义init初始化方法
    def __init__(self,from_station,to_station,to_data,trains,passenger_name_lst):
        self.from_station=from_station
        self.to_station=to_station
        self.to_data=to_data
        self.station_code=self.init_station_code() #station_code为一个字典
        self.trains=trains
        self.passenger_name_lst=passenger_name_lst
        self.selected_no=None
        self.selected_seat=None
  1. 定义 获取火车站车次代号方法
    def init_station_code(self):
        wb=openpyxl.load_workbook('火车站代码.xlsx')
        ws=wb.active
        lst=[] #存储所有车站名称及其代号
        for row in ws.rows:
            sub_lst=[] #存储每一行的车站名称及代号
            for cell in row:
                sub_lst.append(cell.value)
            lst.append(sub_lst)
        return dict(lst) #dict(lst) 可迭代对象方式构造字典
  1. 定义 登录用户界面方法
	#登录的方法
    def login(self):
        driver.get(self.login_url)
        WebDriverWait(driver,1000).until(
            ec.url_to_be(self.profile_url) #等待url成为个人中心页面
        )
        print('登录成功!')
  1. 定义 查询火车票方法
    def search_ticket(self):
        #打开查询余票的网址
        driver.get(self.left_Ticket)

        #添加点击按钮,关闭温馨提醒窗口
        spantag = driver.find_element_by_id('qd_closeDefaultWarningWindowDialog_id')  # 获取span标签
        spantag.click()

        #找到出发站、到达站的隐藏HTML标签
        from_station_input=driver.find_element_by_id('fromStation')
        to_station_input = driver.find_element_by_id('toStation')
        #找到出发时间的input标签
        to_data_input=driver.find_element_by_id('train_date')

        #根据key获取value
        from_station_code=self.station_code[self.from_station] #根据出发地找到出发地的代号
        to_station_code=self.station_code[self.to_station] #根据目的地找到目的地的代号

        #执行js代码,把找到的值添加到隐藏的标签中
        driver.execute_script('arguments[0].value="%s"' % from_station_code,from_station_input)
        driver.execute_script('arguments[0].value="%s"' % to_station_code,to_station_input)
        driver.execute_script('arguments[0].value="%s"' % self.to_data,to_data_input)

        #执行单击查询操作
        query_ticket_tag=driver.find_element_by_id('query_ticket')
        query_ticket_tag.click()

        #解析车次,显示等待,等待tbody的出现
        WebDriverWait(driver,1000).until(
            ec.presence_of_element_located((By.XPATH,'//tbody[@id="queryLeftTable"]/tr'))
        )
        #筛选出有数据的tr,去掉属性为datatran的tr
        trains=driver.find_elements_by_xpath('//tbody[@id="queryLeftTable"]/tr[not(@datatran)]')

        is_flag=False #标记是否有余票,有余票为True
        while True:
            # 分别遍历每个车次
            for train in trains:
                #print(train.text) #输出train为状态码,train.text才是文本内容
                train_lst = train.text.replace('\n', ' ').split(' ')
                #print(train_lst) #到所乘车次时循环退出函数,后续车票信息不输出
                train_no = train_lst[0]
                if train_no in self.trains:
                    # 根据键获取值 席别是一个表
                    seat_types = self.trains[train_no] #获取的是需购买的车次的座次列表
                    for seat_type in seat_types:  # 遍历席位列表
                        if seat_type == 'O':  # 说明是二等座
                            count = train_lst[9]  # 索引为9的是二等座
                            if count.isdigit() or count == '有':
                                is_flag = True
                                break

                        elif seat_type == 'M':  # 说明是一等座
                            count = train_lst[8]  # 索引为8的是一等座
                            if count.isdigit() or count == '有':
                                is_flag = True
                                break
                    # 有余票进行单击预定
                    if is_flag:
                        self.selected_no = train_no
                        order_btn = train.find_element_by_xpath('.//a[@class="btn72"]')  # . 不可缺
                        order_btn.click()
                        return  # 退出函数
  1. 定义 确认乘车人、坐席方法
    def confirm(self):
        #等待来到确认乘车人界面
        WebDriverWait(driver,1000).until(
            ec.url_to_be(self.confirm_url)
        )
        #等待乘车人标签出来
        WebDriverWait(driver,1000).until(
            ec.presence_of_element_located((By.XPATH,'//ul[@id="normal_passenger_id"]/li/label'))
        )
        # 获取所有的乘车人
        passengers = driver.find_elements_by_xpath('//ul[@id="normal_passenger_id"]/li/label')
        for passenger in passengers: #分别遍历每一位乘车人 (label标签)
            name=passenger.text #获取乘车人的姓名文本
            if name in self.passenger_name_lst: #注意!!!此处判断:输入的乘车人姓名信息必须与12036官网上显示的一模一样!!!
                passenger.click()   #label标签的单击

        #确定席位
        seat_select=Select(driver.find_element_by_id('seatType_1'))
        seat_types=self.trains[self.selected_no] #根据键获取值,得到要抢票的车次
        for seat_type in seat_types:
            try:
                seat_select.select_by_value(seat_type)
                self.selected_seat=seat_type #记录有票的坐席
            except NoSuchElementException:
                continue
            else:
                break

        # 等待提交订单按钮可以点击
        WebDriverWait(driver,1000).until(
            ec.element_to_be_clickable((By.ID,'submitOrder_id'))
        )
        # 提交订单
        submit_btn=driver.find_element_by_id('submitOrder_id')
        submit_btn.click()

        # 等待核对信息窗口出现
        WebDriverWait(driver,1000).until(
            ec.presence_of_element_located((By.CLASS_NAME,'dhtmlx_window_active'))
        )

        # 等待最终确认按钮可以点击
        WebDriverWait(driver, 1000).until(
            ec.element_to_be_clickable((By.ID, 'qr_submit_id'))
        )
        final_confirm_btn=driver.find_element_by_id('qr_submit_id')
        while final_confirm_btn:
            try:
                final_confirm_btn.click()
                final_confirm_btn=driver.find_element_by_id('qr_submit_id')
            except ElementNotVisibleException:
                break

        print(f'恭喜{self.passenger_name_lst}的{self.selected_no}车次列车{self.selected_seat}座席次抢票成功!')
  1. 定义 一个run方法,负责组织其他方法
    def run(self):
        #1.登录
        self.login()
        #2.余票查询
        self.search_ticket()
        #3.确认乘车人和订单
        self.confirm()
  1. 完整代码如下
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait #显示等待
from selenium.webdriver.support import expected_conditions as ec #显示等待的条件
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import Select
from selenium.common.exceptions import NoSuchElementException,ElementNotVisibleException
import openpyxl

driver=webdriver.Chrome() #创建浏览器对象

class TrainSpider(object):
    #定义类属性
    login_url = 'https://kyfw.12306.cn/otn/resources/login.html' #登录的页面
    profile_url = 'https://kyfw.12306.cn/otn/view/index.html' #个人中心的网址
    left_Ticket = 'https://kyfw.12306.cn/otn/leftTicket/init?linktypeid=dc' #余票查询界面
    confirm_url = 'https://kyfw.12306.cn/otn/confirmPassenger/initDc'  # 确认乘车人和座席

    #定义init初始化方法
    def __init__(self,from_station,to_station,to_data,trains,passenger_name_lst):
        self.from_station=from_station
        self.to_station=to_station
        self.to_data=to_data
        self.station_code=self.init_station_code() #station_code为一个字典
        self.trains=trains
        self.passenger_name_lst=passenger_name_lst
        self.selected_no=None
        self.selected_seat=None

    def init_station_code(self):
        wb=openpyxl.load_workbook('火车站代码.xlsx')
        ws=wb.active
        lst=[] #存储所有车站名称及其代号
        for row in ws.rows:
            sub_lst=[] #存储每一行的车站名称及代号
            for cell in row:
                sub_lst.append(cell.value)
            lst.append(sub_lst)
        return dict(lst) #dict(lst) 可迭代对象方式构造字典

    #登录的方法
    def login(self):
        driver.get(self.login_url)
        WebDriverWait(driver,1000).until(
            ec.url_to_be(self.profile_url) #等待url成为个人中心页面
        )
        print('登录成功!')

    def search_ticket(self):
        #打开查询余票的网址
        driver.get(self.left_Ticket)

        #添加点击按钮,关闭温馨提醒窗口
        spantag = driver.find_element_by_id('qd_closeDefaultWarningWindowDialog_id')  # 获取span标签
        spantag.click()

        #找到出发站、到达站的隐藏HTML标签
        from_station_input=driver.find_element_by_id('fromStation')
        to_station_input = driver.find_element_by_id('toStation')
        #找到出发时间的input标签
        to_data_input=driver.find_element_by_id('train_date')

        #根据key获取value
        from_station_code=self.station_code[self.from_station] #根据出发地找到出发地的代号
        to_station_code=self.station_code[self.to_station] #根据目的地找到目的地的代号

        #执行js代码,把找到的值添加到隐藏的标签中
        driver.execute_script('arguments[0].value="%s"' % from_station_code,from_station_input)
        driver.execute_script('arguments[0].value="%s"' % to_station_code,to_station_input)
        driver.execute_script('arguments[0].value="%s"' % self.to_data,to_data_input)

        #执行单击查询操作
        query_ticket_tag=driver.find_element_by_id('query_ticket')
        query_ticket_tag.click()

        #解析车次,显示等待,等待tbody的出现
        WebDriverWait(driver,1000).until(
            ec.presence_of_element_located((By.XPATH,'//tbody[@id="queryLeftTable"]/tr'))
        )
        #筛选出有数据的tr,去掉属性为datatran的tr
        trains=driver.find_elements_by_xpath('//tbody[@id="queryLeftTable"]/tr[not(@datatran)]')

        is_flag=False #标记是否有余票,有余票为True
        while True:
            # 分别遍历每个车次
            for train in trains:
                #print(train.text) #输出train为状态码,train.text才是文本内容
                train_lst = train.text.replace('\n', ' ').split(' ')
                #print(train_lst) #到所乘车次时循环退出函数,后续车票信息不输出
                train_no = train_lst[0]
                if train_no in self.trains:
                    # 根据键获取值 席别是一个表
                    seat_types = self.trains[train_no] #获取的是需购买的车次的座次列表
                    for seat_type in seat_types:  # 遍历席位列表
                        if seat_type == 'O':  # 说明是二等座
                            count = train_lst[9]  # 索引为9的是二等座
                            if count.isdigit() or count == '有':
                                is_flag = True
                                break

                        elif seat_type == 'M':  # 说明是一等座
                            count = train_lst[8]  # 索引为8的是一等座
                            if count.isdigit() or count == '有':
                                is_flag = True
                                break
                    # 有余票进行单击预定
                    if is_flag:
                        self.selected_no = train_no
                        order_btn = train.find_element_by_xpath('.//a[@class="btn72"]')  # . 不可缺
                        order_btn.click()
                        return  # 退出函数

    def confirm(self):
        #等待来到确认乘车人界面
        WebDriverWait(driver,1000).until(
            ec.url_to_be(self.confirm_url)
        )
        #等待乘车人标签出来
        WebDriverWait(driver,1000).until(
            ec.presence_of_element_located((By.XPATH,'//ul[@id="normal_passenger_id"]/li/label'))
        )
        # 获取所有的乘车人
        passengers = driver.find_elements_by_xpath('//ul[@id="normal_passenger_id"]/li/label')
        for passenger in passengers: #分别遍历每一位乘车人 (label标签)
            name=passenger.text #获取乘车人的姓名文本
            if name in self.passenger_name_lst: #注意!!!此处判断:输入的乘车人姓名信息必须与12036官网上显示的一模一样!!!
                passenger.click()   #label标签的单击

        #确定席位
        seat_select=Select(driver.find_element_by_id('seatType_1'))
        seat_types=self.trains[self.selected_no] #根据键获取值,得到要抢票的车次
        for seat_type in seat_types:
            try:
                seat_select.select_by_value(seat_type)
                self.selected_seat=seat_type #记录有票的坐席
            except NoSuchElementException:
                continue
            else:
                break

        # 等待提交订单按钮可以点击
        WebDriverWait(driver,1000).until(
            ec.element_to_be_clickable((By.ID,'submitOrder_id'))
        )
        # 提交订单
        submit_btn=driver.find_element_by_id('submitOrder_id')
        submit_btn.click()

        # 等待核对信息窗口出现
        WebDriverWait(driver,1000).until(
            ec.presence_of_element_located((By.CLASS_NAME,'dhtmlx_window_active'))
        )

        # 等待最终确认按钮可以点击
        WebDriverWait(driver, 1000).until(
            ec.element_to_be_clickable((By.ID, 'qr_submit_id'))
        )
        final_confirm_btn=driver.find_element_by_id('qr_submit_id')
        while final_confirm_btn:
            try:
                final_confirm_btn.click()
                final_confirm_btn=driver.find_element_by_id('qr_submit_id')
            except ElementNotVisibleException:
                break

        print(f'恭喜{self.passenger_name_lst}的{self.selected_no}车次列车{self.selected_seat}座席次抢票成功!')

    #负责组织其他代码
    def run(self):
        #1.登录
        self.login()
        #2.余票查询
        self.search_ticket()
        #3.确认乘车人和订单
        self.confirm()

def start():                                            # O 表示的是二等座,M 代表是一等座
    trainspider=TrainSpider('秦皇岛','天津','2021-05-10',{'G8924':['O','M']},[' “此处填姓名即可” ']) #***(学生)
    trainspider.run()

if __name__ == '__main__':
    start()

三、可优化方向

1. 若购买学生票等其他特殊票种会有弹窗提醒,这些都要判断选择来关掉弹窗
2. 添加其他座次选择判断,增加更多坐席级别选择的空间
3. 实现购票信息代码外输入、重复输入及判断输入信息正误机制

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值