Python爬蟲 | 滑動驗(yàn)證碼破解

極驗(yàn)驗(yàn)證碼:需要手動拼合滑塊來完成的驗(yàn)證,相對圖形驗(yàn)證碼識別難度上升了幾個(gè)等級。下面用程序識別并通過極驗(yàn)驗(yàn)證碼的驗(yàn)證,其中有分析識別思路、識別缺口位置、生成滑塊拖動、模擬實(shí)現(xiàn)滑塊拼合通過驗(yàn)證等步驟。需要用到Chrome 瀏覽器,并配置 ChromeDriver ,要用到的 Python 庫是 Selenium。

1、 對極驗(yàn)驗(yàn)證碼了解
  極驗(yàn)驗(yàn)證碼官網(wǎng):http://www.geetest.com/。一個(gè)專注提供驗(yàn)證安全的系統(tǒng),主要驗(yàn)證方式是拖動滑塊拼命圖像。若圖像完全拼合,則驗(yàn)證成功,即表單提交,否則需要重新驗(yàn)證。

現(xiàn)在極驗(yàn)驗(yàn)證碼使用的企業(yè)很多,每天有超過幾億次的響應(yīng)。極驗(yàn)驗(yàn)證碼廣泛用于直播視頻、金融服務(wù)、電子商務(wù)、游戲娛樂、政府企業(yè)等各大類網(wǎng)站。

2、 極驗(yàn)驗(yàn)證碼特點(diǎn)
  識別難度大。首先要點(diǎn)擊按鈕進(jìn)行智能驗(yàn)證,如果驗(yàn)證不通過,則會彈出滑動窗口,拖動滑塊拼合圖像進(jìn)行驗(yàn)證。之后三個(gè)加密參數(shù)會生成,通過表單提交到后臺,后臺還會進(jìn)行一次驗(yàn)證。

極驗(yàn)驗(yàn)證碼增加了機(jī)器學(xué)習(xí)的方法來識別手動軌跡。其官方網(wǎng)站的安全防護(hù)有下面幾點(diǎn)說明:

  • 三角防護(hù)之防模擬:惡意程序模仿人類行為軌跡對驗(yàn)證碼進(jìn)行識別。利用機(jī)器學(xué)習(xí)和神經(jīng)網(wǎng)絡(luò),構(gòu)建線上線下的多重靜態(tài)、動態(tài)防御模型,識別模擬軌跡,界定人機(jī)邊界。

  • 三角防護(hù)之防偽造:惡意程序通過偽造設(shè)備瀏覽器環(huán)境對驗(yàn)證碼進(jìn)行識別。利用設(shè)備基因技術(shù),深度分析瀏覽器的實(shí)際性能來辨識偽造信息,同時(shí)根據(jù)依靠事件不斷更新黑名單來提高防依靠能力。

  • 三角防護(hù)之防暴力:惡意程序在短時(shí)間內(nèi)進(jìn)行密集的攻擊,對驗(yàn)證碼進(jìn)行暴力識別。為了防暴力,極驗(yàn)驗(yàn)證碼有多種形態(tài),每種形態(tài)都用神經(jīng)網(wǎng)絡(luò)生成海量圖庫儲備,每一張圖片都是獨(dú)一無二的,且圖庫不斷更新,極大程度提高防暴力識別成本。

3、 識別思路
  對于極驗(yàn)驗(yàn)證碼,直接模擬表單提交,加密參數(shù)構(gòu)造較困難,需要分析其加密和校驗(yàn)邏輯,相對煩瑣。所以直接采用模擬瀏覽器動作的方式來完成驗(yàn)證。使用 Python 的 Selenium 庫模擬人的行為方式來完成驗(yàn)證,成本要相對去識別加密算法少很多。如圖所示。

!極驗(yàn)驗(yàn)證碼實(shí)例](https://img2018.cnblogs.com/blog/1501522/201905/1501522-20190520165616663-50859391.png)

先找一個(gè)帶有極驗(yàn)證的網(wǎng)站,這里以博客園的登錄為例進(jìn)行驗(yàn)證。極驗(yàn)的官方登錄網(wǎng)站是 https://account.geetest.com/login,也可以在其官網(wǎng)上做測試。在博客園的登錄頁面點(diǎn)擊登錄后會出現(xiàn)一個(gè)極驗(yàn)驗(yàn)證按鈕。如圖2-2所示。

該按鈕是智能驗(yàn)證按鈕。一般來說,如果是同一個(gè)會話,一段時(shí)間內(nèi)第二次點(diǎn)擊會直接通過驗(yàn)證。如果智能識別不通過,則會彈出滑動驗(yàn)證窗口,需要拖動滑塊圖像完成二步驗(yàn)證。驗(yàn)證成功后,驗(yàn)證按鈕會提示驗(yàn)證成功。接下來就是提交表單。

經(jīng)過上述分析,極驗(yàn)驗(yàn)證需要完成下面三步:
(1)模擬點(diǎn)擊驗(yàn)證按鈕。
(2)識別滑動缺口的位置。
(3)模擬手動滑塊。

第(1)步操作相對簡單,可使用 Selenium 模擬點(diǎn)擊按鈕。
第(2)步操作識別缺口的位置很關(guān)鍵,需要用到圖像的相關(guān)處理方法。首先觀察缺口的樣子,如圖所示。

缺口的四周邊緣有明顯的斷裂邊緣,邊緣與邊緣周圍有明顯的區(qū)別??捎眠吘墮z測算法來找出缺口的位置。對于極驗(yàn)驗(yàn)證碼,可以利用和原圖對比檢測方式識別缺口的位置,通常在沒有拖動滑塊前,缺口沒有呈現(xiàn)。

可以同時(shí)獲取兩張圖片,設(shè)定一個(gè)對比閾值,然后遍歷兩張圖片,找出相同位置像素 RGB 差距超過此閾值的像素點(diǎn),此像素點(diǎn)的位置就是缺口的位置。

第(3)步中要注意的是,極驗(yàn)驗(yàn)證碼增加了機(jī)器軌跡識別,勻速移動、隨機(jī)速度移動等方法都不能通過驗(yàn)證,只有完全模擬人的移動軌跡才可以通過驗(yàn)證。人的移動軌跡一般是先加速后減速,要對這個(gè)過程模擬才能成功。

要包括這幾個(gè)步驟。

第一步,初始化,在這里我們先初始化 一些selenium的 配置及一些參數(shù)的配置。

第二步,就是模擬點(diǎn)擊了,這里主要是利用selenium模塊模擬瀏覽器對網(wǎng)頁進(jìn)行操作。

第三步,就該識別缺口的位置了。首先獲取前后兩張圖片,得到其所在位置和寬高,然后獲取整個(gè)網(wǎng)頁的截圖,圖片裁切下來即可。

最后一步,模擬拖動,經(jīng)過多次試驗(yàn),得出一個(gè)結(jié)論,那就是完全模擬加速減速的過程通過了驗(yàn)證。前段作勻加速,后段作勻減速運(yùn)動,利用物理學(xué)的加速度公式即可完成驗(yàn)證。

位移移動

需要的基礎(chǔ)知識

位移移動相當(dāng)于勻變速直線運(yùn)動,類似于小汽車從起點(diǎn)開始運(yùn)行到終點(diǎn)的過程(首先為勻加速,然后再勻減速)。

其中a為加速度,且為恒量(即單位時(shí)間內(nèi)的加速度是不變的),t為時(shí)間

教程一:博客園

誤區(qū)一:

下面整個(gè)button區(qū)域?qū)?yīng)著整個(gè)登錄框,可以直接使用id里的標(biāo)簽進(jìn)行定位,而不是第1個(gè)span標(biāo)簽

button = self.wait.until(EC.presence_of_element_located((By.ID,"submitBtn")))

誤區(qū)二:注意關(guān)于圖片的一些知識點(diǎn)

img1 = Image.open("E:/python27/pic/1.jpg")
img1.size
r, g, b = img1.split()

打開圖片:Image.open(fp, mode='r')
保存圖片:Image.save(fp, format=None, **params):
展示圖片:Image.show(title=None, command=None):
location = img.location
# location屬性可以返回該圖片對象(既這張圖片)在瀏覽器中的位置,以字典的形式返回,{‘x’:30,‘y’:30} 這里我們圖片的位置是(30,30)。坐標(biāo)軸是以屏幕左上角為原點(diǎn),x軸向右遞增,y軸像下遞增。(相對整個(gè)html的坐標(biāo))



size = img.size  
# 通過size獲取屬性大小,size屬性同樣返回一個(gè)字典,{‘height’:30,‘width’:30 } 即圖片對象的高度,寬度。



four_corner =(location['x'],
        location['y'],
        location['x']+size['width'],
        location['y']+size['height'])        
# 通過location和size獲取上下左右,注意是小寫的x和y



captcha = screenshot.crop((top, bottom, left, right))  
# Image.crop() 從圖像中提取出某個(gè)矩形大小的圖像。它接收一個(gè)四元素的元組作為參數(shù),各元素為(left, upper, right, lower),坐標(biāo)系統(tǒng)的原點(diǎn)(0, 0)是左上角。# 因?yàn)樯厦娼貓D,截取到的是整個(gè)頁面,現(xiàn)在只把驗(yàn)證碼圖片取到


image.size屬性是一個(gè)列表,下標(biāo)0是橫坐標(biāo)(寬,圖片的最右邊),下標(biāo)1是縱坐標(biāo)
image.size[0]
image.size[1]

完整代碼

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

EMAIL =
PASSWORD =
BODER = 6
INIT_LEFT = 60

class CrackGeetest():

    def __init__(self):
        self.url = 'http://www.sf-express.com/cn/sc/dynamic_function/waybill'
        self.browser = webdriver.Chrome('D:\\chromedriver.exe')
        self.wait = WebDriverWait(self.browser, 100)
        self.email = EMAIL
        self.keyword = PASSWORD
        self.BORDER = 6


    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 get_geetest_button(self):
        """
        點(diǎn)擊按鈕,彈出沒有缺口的圖片
        :return: 返回按鈕對象
        """
        button = self.wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'geetest_radar_tip')))
        return button

    def __del__(self):
        self.browser.close()


    def get_position(self):
        """
        獲取驗(yàn)證碼位置
        :return:驗(yàn)證碼位置元組
        """
        img = self.wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'geetest_canvas_img')))
        time.sleep(2)

        # location屬性可以返回該圖片對象(既這張圖片)在瀏覽器中的位置,以字典的形式返回,{‘x’:30,‘y’:30} 這里我們圖片的位置是(30,30)。坐標(biāo)軸是以屏幕左上角為原點(diǎn),x軸向右遞增,y軸像下遞增。(相對整個(gè)html的坐標(biāo))
        location = img.location

        # size屬性同樣返回一個(gè)字典,{‘height’:30,‘width’:30 } 即圖片對象的高度,寬度。
        size = img.size                 # 通過sizes屬性獲取大小
        top,bottom,left,right = location['y'],location['y']+size['height'],location['x'],location['x']+size['width']    # 通過location和size獲取上下左右
        return (top,bottom,left,right)


    def get_screenshot(self):
        """
        獲取網(wǎng)頁截圖
        :return: 截圖對象
        """
        pic.png = self.browser.get_screenshot_as_png()
        screenshot = Image.open(BytesIO(pic.png))            # 轉(zhuǎn)換為字節(jié)對象

            '''
            img1 = Image.open("E:/python27/pic/1.jpg")
            img1.size
            r, g, b = img1.split()
            
            打開圖片:Image.open(fp, mode='r')
            保存圖片:Image.save(fp, format=None, **params):
            展示圖片:Image.show(title=None, command=None):
            '''

    def get_geetest_image(self, name='captcha.jpg'):
        """
        獲取驗(yàn)證碼圖片
        :return: 圖片對象
        """
        top, bottom, left, right = self.get_position()                  # 定位到這個(gè)位置之后,再截圖
        print('驗(yàn)證碼位置',top, bottom, left, right)
        screenshot = self.get_screenshot()                              # 獲取到截圖

        # Image.crop() 從圖像中提取出某個(gè)矩形大小的圖像。它接收一個(gè)四元素的元組作為參數(shù),各元素為(left, upper, right, lower),坐標(biāo)系統(tǒng)的原點(diǎn)(0, 0)是左上角。
        captcha = screenshot.crop((top, bottom, left, right))           # 因?yàn)樯厦娼貓D,截取到的是整個(gè)頁面,現(xiàn)在只把驗(yàn)證碼圖片取到
        captcha.save(name)                                              # 把圖片存儲起來,name是傳進(jìn)來的變量
        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 is_pixel_equal(self, img1, img2, x, y):
        """
        判斷兩個(gè)像素是否相同
        :param image1: 圖片1
        :param image2: 圖片2
        :param x: 位置x
        :param y: 位置y
        :return: 像素是否相同
        """
        # 取兩個(gè)圖片的像素點(diǎn) (img.load()[x, y]獲取某一點(diǎn)的像素值)
        pixel1 = img1.load()[x, y]      # 上面的 i 和 j
        pixel2 = img2.load()[x, y]
        threshold = 60                  # 控制誤差
        if (abs(pixel1[0] - pixel2[0] < threshold) and abs(pixel1[1] - pixel2[1] < threshold)           # 0 1 2 是RGB的三種顏色
        and abs(pixel1[2] - pixel2[2] < threshold)):
            return True
        else:                           # 有一個(gè)超過閾值,則認(rèn)為2個(gè)像素點(diǎn)不一致
            return False


    def get_gap(self, img1, img2):
        """
        獲取缺口偏移量
        :param img1: 不帶缺口圖片
        :param img2: 帶缺口圖片
        :return:
        """
        left = 60                                   # 從圖片的60處開始往右,這樣就可以把被拖動滑塊跳過去,因?yàn)楸煌蟿踊瑝K處像素也不一樣

        # 相當(dāng)于從60開始,一列列做對比
        for i in range(left, img1.size[0]):         # i 相當(dāng)于橫坐標(biāo)      size屬性是一個(gè)列表,下標(biāo)0是橫坐標(biāo)(寬,圖片的最右邊),下標(biāo)1是縱坐標(biāo)
            for j in range(img1.size[1]):           # j 相當(dāng)于縱坐標(biāo)      從0開始
                if not self.is_pixel_equal(img1, img2, i, j):
                    left = i                                            # 如果相同位置的像素不一致,則替換為i
                    return left
        return left


    def get_track(self, distance):
        """
        根據(jù)偏移量獲取移動軌跡
        :param distance: 偏移量
        :return: 移動軌跡
        """
        # 移動軌跡
        track = []
        # 當(dāng)前位移
        current = 0
        # 減速閾值
        mid = distance * 4 / 5
        # 計(jì)算間隔
        t = 0.2
        # 初速度
        v = 0

        while current < distance:                   # 所以 track是不會大于總長度的
            if current < mid:
                # 加速度為正2
                a = 2
            else:
                # 加速度為負(fù)3
                a = -3
            # 初速度v0
            v0 = v
            # 移動距離x = v0t + 1/2 * a * t^2,現(xiàn)做了加速運(yùn)動
            move = v0 * t + 1 / 2 * a * t * t
            # 當(dāng)前速度v = v0 + at  速度已經(jīng)達(dá)到v,該速度作為下次的初速度
            v = v0 + a * t
            # 當(dāng)前位移
            current += move
            # 加入軌跡
            track.append(round(move))                   # track 就是最終鼠標(biāo)在 X 軸移動的軌跡
        return track


    def move_to_gap(self, slider, track):
        """
        拖動滑塊到缺口處
        :param slider: 滑塊
        :param track: 軌跡
        :return:
        """
        ActionChains(self.browser).click_and_hold(slider).perform()         # 利用動作鏈,獲取slider,perform是

        for x in track:
            ActionChains(self.browser).move_by_offset(xoffset=x, yoffset=0).perform()       # xoffset橫坐標(biāo),yoffset縱坐標(biāo)。使得鼠標(biāo)向前推進(jìn)
        time.sleep(0.5)                                     # 推動到合適位置之后,暫停一會
        ActionChains(self.browser).release().perform()      # 抬起鼠標(biāo)左鍵


    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()

        # 點(diǎn)擊按鈕,彈出沒有缺口的圖片
        button = self.get_geetest_button()
        button.click()
        time.sleep(0.5)

        # 獲取驗(yàn)證碼圖片
        image1 = self.get_geetest_image('captcha1.png')

        #點(diǎn)按呼出滑塊
        slider = self.get_slider()
        slider.click()                              # 一般的驗(yàn)證碼,只有點(diǎn)完slide按鈕之后,才會出現(xiàn)缺口

        # 獲取帶缺口的驗(yàn)證碼圖片
        image2 = self.get_geetest_image('captcha2.png')

        # 獲取缺口位置
        gap = self.get_gap(image1, image2)
        print('缺口位置', gap)

        # 減去缺口的位移(因?yàn)椋瑝K移動img1到img2的時(shí)候,允許有誤差范圍。在范圍內(nèi),即使有幾個(gè)像素差,也是可以接受的
        gap -= BODER

        # 獲取移動軌跡
        track = self.get_track(gap)
        print('滑動軌跡',track)

        # 拖動滑塊到缺口處。模擬人的行為習(xí)慣(先勻加速拖動后勻減速拖動),把需要拖動的總距離分成一段一段小的軌跡
        self.move_to_gap(slider, track)

        success = self.wait.until(
            EC.text_to_be_present_in_element((By.CLASS_NAME,'geetest_sucess_radar_tip_constant'),'驗(yàn)證成功')        # 證明極驗(yàn)成功
        )
        print(success)

        # 失敗后重試
        if not success:
            self.crack()
        else:
            self.login()


if __name__ == '__main__':
    print('開始驗(yàn)證')
    crack = CrackGeetest()
    crack.crack()
    print('驗(yàn)證成功')
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容