1. 程式人生 > >Python + selenium + requests實現12306全自動搶票,驗證碼破解加自動點選!!!

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