python驗證碼識別(2)極驗滑動驗證碼識別


一:極驗滑動驗證碼簡介

  近些年來出現了一些新型驗證碼,不想舊的驗證碼對人類不友好,但是這種驗證碼對於代碼來說識別難度上升了幾個等級。因此需要其他的手段進行處理。

  識別需要的python庫:selenium和ChromeDriver驅動,不同瀏覽器的要下載的驅動庫不同。

  驗證碼獲取網站:http://www.geetest.com/

  極驗滑動驗證碼已經到了3.0版本,相關於圖形驗證碼識別難度更大,原理是拖動圖片到缺口處,然后拼合圖像進行驗證,會生成三個加密參數,通過表單提交到后台,后台再進行驗證。

  極驗驗證碼還增加了機器學習的方法來識別是否是惡意程序進行識別,有防模擬,防偽造,防暴力的方式, 只需0.4秒,並且保護資源不被濫用和盜取。

  我們的程序一般只要不是惡意進行爬取的,並遵守爬蟲協議,就可以。千萬不要給服務器造成負擔。

二:極驗滑動驗證碼識別思路

  這里我們可以采用模擬瀏覽器動作的方式完成驗證,用Selenium來完全模擬人的行為完成驗證。

  主要分為三步

  (1)模擬點擊驗證按鈕

  (2)識別滑動缺口的位置

  (3)模擬拖動滑塊

  第(1)步還比較好說,第(2)步操作識別接口的位置比較關鍵,需要用到圖像處理看到接口的位置,並和原圖對比檢測的方法來識別缺口的位置。同時獲取兩張圖片,設定一個對比閾值,然后遍歷兩張圖片,找出相同像素RGB差距超過此閾值的像素點,那么像素點位置就是缺口的位置。

  第(3)步較難,由於人的移動軌跡是先加速后減速,勻速移動和隨機移動等方法都不能通過驗證,要模擬好這個過程。

三:極驗驗證碼識別

1.極驗驗證碼官網:https://auth.geetest.com/login/

官網圖片為:

2.初始化配置

# 注冊的用戶名和密碼
email = ''
password = ''

class CrackGeetest():
    def __init__(self):
        self.url = 'https://account.geetest.com/login'
        self.browser = webdriver.Chrome()
        self.wait = WebDriverWait(self.browser, 20)
        self.email = email
        self.password = password

3.模擬點擊

  識別驗證碼第一步就是模擬點擊初始的驗證按鈕,用顯式等待的方法進行獲取。

def get_geetest_button(self):
        """
        獲取初始驗證按鈕
        返回值:按鈕對象
        """
        button = self.wait.until(EC.element_to_be_clickable((By.CLASS_NAME, 'geetest_radar_tip')))
        return button

  在調用位置即可模擬點擊:

# 點擊驗證按鈕
button = self.get_geetest_button()
button.click()

4.識別缺口

  接下來識別缺口的位置,首先獲取兩張圖片,進行對比,不一樣的位置就是缺口。

  獲取不帶缺口的圖片。用selenium選取圖片元素得到整個網頁的截圖然后裁剪即可,代碼如下:

def get_screenshot(self):
        """
        獲取網頁截圖
        :return: 截圖對象
        """
        screenshot = self.browser.get_screenshot_as_png()
        screenshot = Image.open(BytesIO(screenshot))
        return screenshot

    def get_position(self):
        """
        獲取驗證碼位置
        :return: 驗證碼位置元組
        """
        img = self.wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'geetest_canvas_img')))
        time.sleep(2)
        location = img.location
        size = img.size
        top, bottom, left, right = location['y'], location['y'] + size['height'], location['x'], location['x'] + size[
            'width']
        return (top, bottom, left, right)

    def get_geetest_image(self, name='captcha.png'):
        """
        獲取驗證碼圖片
        :return: 圖片對象
        """
        top, bottom, left, right = self.get_position()
        print('驗證碼位置', top, bottom, left, right)
        screenshot = self.get_screenshot()
        captcha = screenshot.crop((left, top, right, bottom))
        captcha.save(name)
        return captcha

  接下來需要獲取第二張圖片,就是帶有缺口的圖片,只需要點擊下面的滑塊就能出現缺口,代碼如下:

def get_slider(self):
        """
        獲取滑塊
        :return: 滑塊對象
        """
        slider = self.wait.until(EC.element_to_be_clickable((By.CLASS_NAME, 'geetest_slider_button')))
        return slider

  用click()即可觸發點擊,如下:

# 點按呼出缺口
slider = self.get_slider()
slider.click()

  接下來就是通過對比圖片獲取缺口,通過遍歷圖片上的每個坐標點,獲取兩張圖片對應像素點的RGB數據。如果在一定范圍內,那就代表兩個像素相同,繼續對比下一個像素點。如果差距超過一定范圍,則代表像素點不同,當前位置就是缺口位置。通過設置一個閾值threshold,來進行判斷,代碼如下:

def is_pixel_equal(self, image1, image2, x, y):
        """
        判斷兩個像素是否相同
        :param image1: 圖片1
        :param image2: 圖片2
        :param x: 位置x
        :param y: 位置y
        :return: 像素是否相同
        """
        # 取兩個圖片的像素點
        pixel1 = image1.load()[x, y]
        pixel2 = image2.load()[x, y]
        threshold = 60
        if abs(pixel1[0] - pixel2[0]) < threshold and abs(pixel1[1] - pixel2[1]) < threshold and abs(
                pixel1[2] - pixel2[2]) < threshold:
            return True
        else:
            return False

    def get_gap(self, image1, image2):
        """
        獲取缺口偏移量
        :param image1: 不帶缺口圖片
        :param image2: 帶缺口圖片
        :return:
        """
        left = 60
        for i in range(left, image1.size[0]):
            for j in range(image1.size[1]):
                if not self.is_pixel_equal(image1, image2, i, j):
                    left = i
                    return left
        return left

5.模擬拖動

  模擬拖動並不復雜,但是里面的細節比較多。用相關的函數將滑塊拖動到對應的位置即可。但是要是勻速拖動,會必然識別出是程序,非人類操作,因為人類無法做到完全勻速拖動,會識別出是機器操作,使得驗證碼失敗。

  通過不同的方法檢測,我們發現把前段滑塊做勻加速運動,后段滑塊做勻減速運動,即可完成驗證。

  這里加速度用a來表示,當前速度用v表示,初速度用vo表示,位移用x表示,時間用t表示。

  代碼如下:

def get_track(self, distance):
        """
        根據偏移量獲取移動軌跡
        :param distance: 偏移量
        :return: 移動軌跡
        """
        # 移動軌跡
        track = []
        # 當前位移
        current = 0
        # 減速閾值
        mid = distance * 4 / 5
        # 計算間隔
        t = 0.2
        # 初速度
        v = 0
        
        while current < distance:
            if current < mid:
                # 加速度為正2
                a = 2
            else:
                # 加速度為負3
                a = -3
            # 初速度v0
            v0 = v
            # 當前速度v = v0 + at
            v = v0 + a * t
            # 移動距離x = v0t + 1/2 * a * t^2
            move = v0 * t + 1 / 2 * a * t * t
            # 當前位移
            current += move
            # 加入軌跡
            track.append(round(move))
        return track
    
    def move_to_gap(self, slider, track):
        """
        拖動滑塊到缺口處
        :param slider: 滑塊
        :param track: 軌跡
        :return:
        """
        ActionChains(self.browser).click_and_hold(slider).perform()
        for x in track:
            ActionChains(self.browser).move_by_offset(xoffset=x, yoffset=0).perform()
        time.sleep(0.5)
        ActionChains(self.browser).release().perform()

6:全部代碼

import time
from io import BytesIO
from PIL import Image
from selenium import webdriver
from selenium.webdriver import ActionChains
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

EMAIL = 'cqc@cuiqingcai.com'
PASSWORD = ''
BORDER = 6
INIT_LEFT = 60

# 注冊的用戶名和密碼
email = ''
password = ''


class CrackGeetest():
    def __init__(self):
        self.url = 'https://account.geetest.com/login'
        self.browser = webdriver.Chrome()
        self.wait = WebDriverWait(self.browser, 20)
        self.email = email
        self.password = password
    
    def __del__(self):
        self.browser.close()
    
    def get_geetest_button(self):
        """
        獲取初始驗證按鈕
        返回值:按鈕對象
        """
        button = self.wait.until(EC.element_to_be_clickable((By.CLASS_NAME, 'geetest_radar_tip')))
        return button
    
    def get_screenshot(self):
        """
        獲取網頁截圖
        :return: 截圖對象
        """
        screenshot = self.browser.get_screenshot_as_png()
        screenshot = Image.open(BytesIO(screenshot))
        return screenshot

    def get_position(self):
        """
        獲取驗證碼位置
        :return: 驗證碼位置元組
        """
        img = self.wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'geetest_canvas_img')))
        time.sleep(2)
        location = img.location
        size = img.size
        top, bottom, left, right = location['y'], location['y'] + size['height'], location['x'], location['x'] + size[
            'width']
        return (top, bottom, left, right)
    
    def get_geetest_image(self, name='captcha.png'):
        """
        獲取驗證碼圖片
        :return: 圖片對象
        """
        top, bottom, left, right = self.get_position()
        print('驗證碼位置', top, bottom, left, right)
        screenshot = self.get_screenshot()
        captcha = screenshot.crop((left, top, right, bottom))
        captcha.save(name)
        return captcha

    def get_slider(self):
        """
        獲取滑塊
        :return: 滑塊對象
        """
        slider = self.wait.until(EC.element_to_be_clickable((By.CLASS_NAME, 'geetest_slider_button')))
        return slider
    
    def open(self):
        """
        打開網頁輸入用戶名密碼
        :return: None
        """
        self.browser.get(self.url)
        email = self.wait.until(EC.presence_of_element_located((By.ID, 'email')))
        password = self.wait.until(EC.presence_of_element_located((By.ID, 'password')))
        email.send_keys(self.email)
        password.send_keys(self.password)
    
    def is_pixel_equal(self, image1, image2, x, y):
        """
        判斷兩個像素是否相同
        :param image1: 圖片1
        :param image2: 圖片2
        :param x: 位置x
        :param y: 位置y
        :return: 像素是否相同
        """
        # 取兩個圖片的像素點
        pixel1 = image1.load()[x, y]
        pixel2 = image2.load()[x, y]
        threshold = 60
        if abs(pixel1[0] - pixel2[0]) < threshold and abs(pixel1[1] - pixel2[1]) < threshold and abs(
                pixel1[2] - pixel2[2]) < threshold:
            return True
        else:
            return False

    def get_gap(self, image1, image2):
        """
        獲取缺口偏移量
        :param image1: 不帶缺口圖片
        :param image2: 帶缺口圖片
        :return:
        """
        left = 60
        for i in range(left, image1.size[0]):
            for j in range(image1.size[1]):
                if not self.is_pixel_equal(image1, image2, i, j):
                    left = i
                    return left
        return left
    
    def get_track(self, distance):
        """
        根據偏移量獲取移動軌跡
        :param distance: 偏移量
        :return: 移動軌跡
        """
        # 移動軌跡
        track = []
        # 當前位移
        current = 0
        # 減速閾值
        mid = distance * 4 / 5
        # 計算間隔
        t = 0.2
        # 初速度
        v = 0
        
        while current < distance:
            if current < mid:
                # 加速度為正2
                a = 2
            else:
                # 加速度為負3
                a = -3
            # 初速度v0
            v0 = v
            # 當前速度v = v0 + at
            v = v0 + a * t
            # 移動距離x = v0t + 1/2 * a * t^2
            move = v0 * t + 1 / 2 * a * t * t
            # 當前位移
            current += move
            # 加入軌跡
            track.append(round(move))
        return track
    
    def move_to_gap(self, slider, track):
        """
        拖動滑塊到缺口處
        :param slider: 滑塊
        :param track: 軌跡
        :return:
        """
        ActionChains(self.browser).click_and_hold(slider).perform()
        for x in track:
            ActionChains(self.browser).move_by_offset(xoffset=x, yoffset=0).perform()
        time.sleep(0.5)
        ActionChains(self.browser).release().perform()
    
    def login(self):
        """
        登錄
        :return: None
        """
        submit = self.wait.until(EC.element_to_be_clickable((By.CLASS_NAME, 'login-btn')))
        submit.click()
        time.sleep(10)
        print('登錄成功')
    
    def crack(self):
        # 輸入用戶名密碼
        self.open()
        # 點擊驗證按鈕
        button = self.get_geetest_button()
        button.click()

        # 獲取驗證碼圖片
        image1 = self.get_geetest_image('captcha1.png')
        # 點按呼出缺口
        slider = self.get_slider()
        slider.click()
        # 獲取帶缺口的驗證碼圖片
        image2 = self.get_geetest_image('captcha2.png')
        # 獲取缺口位置
        gap = self.get_gap(image1, image2)
        print('缺口位置', gap)
        # 減去缺口位移
        gap -= BORDER
        # 獲取移動軌跡
        track = self.get_track(gap)
        print('滑動軌跡', track)
        # 拖動滑塊
        self.move_to_gap(slider, track)
        
        success = self.wait.until(
            EC.text_to_be_present_in_element((By.CLASS_NAME, 'geetest_success_radar_tip_content'), '驗證成功'))
        print(success)
        
        # 失敗后重試
        if not success:
            self.crack()
        else:
            self.login()


if __name__ == '__main__':
    crack = CrackGeetest()
    crack.crack()

這種方法對於不同的極驗滑動驗證碼來說都適用,關鍵在於識別的思路,如何識別缺口位置,如何生成運動軌跡等。之后遇到類似的驗證碼,都可以這樣進行識別。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM