Python + selenium + requests实现12306全自动抢票,验证码破解加自动点击!!!

Python + selenium + requests实现12306全自动抢票,验证码破解加自动点击!!!!!

测试结果:
这里写图片描述
整个买票流程可以再快一点,不过为了稳定起见,有些地方等待了一些时间

完整程序,拿去可用
整个程序分了三个模块:购票模块(主体)、验证码识别模块、余票查询模块
购票模块:

from selenium import webdriver
from selenium.webdriver.common.by import By 
from selenium.webdriver.support import expected_conditions as EC 
from selenium.common.exceptions import NoSuchElementException, StaleElementReferenceException, ElementNotVisibleException
import time
import requests
from urllib.parse import urlencode
from pyquery import PyQuery as pq
from check_ticket import Check
from verify import Code
import json

class Buy_Ticket():
    def __init__(self, start_station, end_station, date, username, password, purpose):
        self.num = 1
        self.start = start_station
        self.end = end_station
        self.date = date
        self.username = username
        self.password = password
        self.purpose = purpose
        self.login_url = 'https://kyfw.12306.cn/otn/login/init'
        self.ticket_url = 'https://kyfw.12306.cn/otn/leftTicket/init'

    def login(self):
        browser.get(self.login_url)
        try:
            input_name = browser.find_element_by_id('username')
            input_pd = browser.find_element_by_id('password')
            button = browser.find_element_by_id('loginSub')
            time.sleep(1)
            input_name.send_keys(self.username)
            input_pd.send_keys(self.password)
            c = Code(browser)       #调用验证码识别模块
            c.main()
            button.click()
            time.sleep(2)
            #等待页面跳转,如果验证码识别错误,就执行下面的while语句
            while browser.current_url == self.login_url + '#':
                c = Code(browser)
                c.main()
                button.click()
                time.sleep(2)
            #self.get_passenger()
            self.check()
        except NoSuchElementException:
            self.login()

    def check(self):
        #调用余票查询模块
        check = Check(self.date, self.start, self.end, self.purpose)
        start_end = check.look_up_station()
        self.num = check.get_info()
        #cookie的添加,json.dumps把以汉字形式呈现的起始、终点站转化成unicode编码,可在审查元素里查看cookie
        browser.add_cookie({'name':'_jc_save_fromStation', 'value':json.dumps(self.start).strip('"').replace('\\', '%') + '%2C' + start_end[0]})
        browser.add_cookie({'name':'_jc_save_toStation', 'value':json.dumps(self.end).strip('"').replace('\\', '%') + '%2C' + start_end[1]})
        browser.add_cookie({'name':'_jc_save_fromDate', 'value':self.date})
        browser.get(self.ticket_url)
        if self.purpose == '学生':
            btn = browser.find_element_by_id('sf2')
            time.sleep(1)
            btn.click()
        button = browser.find_element_by_id('query_ticket')
        time.sleep(1)
        button.click()

    def book_ticket(self):
        print('开始预订车票...')
        #先查找出所有车次对应的预订按钮,再根据余票查询模块返回的车次序号,点击相应的预订按钮
        button = browser.find_elements_by_class_name('btn72')
        button[self.num-1].click()
        time.sleep(3)
        button2 = browser.find_element_by_id('normalPassenger_0')  #按实际情况,可自行修改,这里就选择的第一个常用联系人,
                                                                    #第二个是normalPassenger_1,依此类推
        button2.click()
        button3 = browser.find_element_by_id('submitOrder_id')
        time.sleep(1)
        button3.click()
        time.sleep(3)  #等待页面加载完毕,不然后面可能会报错,等待时间自行决定
        try:
            button4 = browser.find_element_by_id('qr_submit_id')
            button4.click()
        except ElementNotVisibleException:
            button4 = browser.find_element_by_id('qr_submit_id')
            button4.click()
        print('车票预定成功!请在30分钟内完成付款!')

    def main(self):
        self.login()
        self.book_ticket()

if __name__ == '__main__':
    begin = time.time()
    browser = webdriver.Chrome()
    b = Buy_Ticket('上海', '重庆', '2018-09-18', '账号', '密码', 'ADULT')  #账号、密码自行修改
    b.main()
    end = time.time()
    print('总耗时:%d秒' % int(end-begin))
    #browser.close()

验证码识别模块:

import requests
from PIL import Image
from selenium.webdriver import ActionChains
import time
from io import BytesIO

class Code():
    def __init__(self, browser):
        self.browser = browser
        self.verify_url = 'http://littlebigluo.qicp.net:47720/'     #验证码识别网址,返回识别结果

        #确定验证码的位置
    def get_position(self):
        time.sleep(3)
        element = self.browser.find_element_by_class_name('touclick-img-par')
        time.sleep(2)
        location = element.location
        size = element.size
        position= (location['x'], location['y'], location['x'] + size['width'], location['y'] + size['height'])
        return position

        #截取整个网页页面
    def get_screenshot(self):
        screenshot = self.browser.get_screenshot_as_png()
        screenshot = Image.open(BytesIO(screenshot))
        return screenshot

        #从截取的网页,裁剪出验证码图片,并保存到本地
    def get_touclick_img(self, name = 'captcha.png'):
        position = self.get_position()
        print('验证码的位置:', position)
        screenshot = self.get_screenshot()
        captcha = screenshot.crop(position)
        captcha.save('captcha.png')

        #验证码解析
    def parse_img(self):
        files = {'file': open('captcha.png', 'rb')}             #打开保存到本地的验证码图片
        response = requests.post(self.verify_url, files=files)
        num = response.text.split('<B>')[1].split('<')[0]
        print('验证码识别成功!图片位置:%s' % num)
        try:
            if int(num):
                return [int(num)]
        except ValueError:
            num = list(map(int,num.split()))
            return num

        #识别结果num都以列表形式返回,方便后续验证码的点击

        #实现验证码自动点击
    def move(self):
        num = self.parse_img()
        try:
            element = self.browser.find_element_by_class_name('touclick-img-par')
            for i in num:
                if i <= 4:
                    ActionChains(self.browser).move_to_element_with_offset(element,40+72*(i-1),73).click().perform()
                else :
                    i -= 4
                    ActionChains(self.browser).move_to_element_with_offset(element,40+72*(i-1),145).click().perform()
        except:
            print('元素不可选!')

    def main(self):
        self.get_touclick_img()
        self.move()

余票查询模块:

import requests
from urllib.parse import urlencode

class Check():
    def __init__(self, date, start, end, purpose):
        self.base_url = 'https://kyfw.12306.cn/otn/leftTicket/queryA?'
        self.url = 'https://kyfw.12306.cn/otn/resources/js/framework/station_name.js?station_version=1.9018'
        self.date = date
        self.start_station = start
        self.end_station = end
        if purpose == '学生':
            self.purpose = '0X00'
        else:
            self.purpose = purpose

        #查找出车站的英文简称,用于构造cookie、完整的余票查询链接
    def look_up_station(self):
        response1 = requests.get(self.url)
        a = response1.text.split('@')
        a.pop(0)
        for each in a:
            i = each.split('|')
            if self.start_station == i[1]:
                self.start_station = i[2]
            elif self.end_station == i[1]:
                self.end_station = i[2]
        return [self.start_station, self.end_station]

    def get_info(self):
        start_end = self.look_up_station()
        #构造请求参数
        data = {
        'leftTicketDTO.train_date':self.date,
        'leftTicketDTO.from_station':start_end[0],
        'leftTicketDTO.to_station':start_end[1],
        'purpose_codes':self.purpose
        }
        url = self.base_url + urlencode(data)
        response = requests.get(url)
        json = response.json()
        maps = json['data']['map']
        count = 0       #用于对车次编号              
        for each in json['data']['result']:
            count += 1
            s = each.split('|')[3:]
            info = {
            'train':s[0],
            'start_end':maps[s[3]] + '-' + maps[s[4]],
            'time':s[5] + '-' + s[6],
            '历时':s[7],
            '一等座':s[-5],
            '二等座':s[-6]
            }
            try:
                #余票的结果有3种:有、一个具体的数字(如:18、6等)、无,判断如果余票是有或者一个具体的数字就直接输出对应的车次信息,然后返回
                if info['二等座'] == '有' or int(info['二等座']):     
                    print('[%d]' % count, info)
                    return count
            except ValueError:
                continue

猜你喜欢

转载自blog.csdn.net/jay_wonder/article/details/82529973