爬蟲模擬登入破解無原圖滑動驗證碼

日天達人發表於2019-06-19

模擬登入物件:部落格園

驗證碼型別:無原圖滑動驗證碼

使用工具與模組:python,selenium 

瀏覽器:Chrome

大體思路:以前的滑動驗證碼多為有原圖的驗證碼,可以通過Image模組擷取兩張不同的圖,通過對比畫素得出移動的距離,無原圖驗證碼也是基於這個原理,只是多了一步找出原圖,該操作可以通過driver.execute_script()新增JS程式碼,改變display顯示獲得原圖,然後就變成了有原圖的滑動驗證碼的操作流程。

具體思路:

第一步:輸入賬號、密碼,然後點選登陸

   from selenium import webdriver
   #為了方便演示與檢視結果,在此使用有介面的Chrome瀏覽器,成功之後可以換成無介面瀏覽器
    driver=webdriver.Chrome()
    #引數為部落格園登入頁面
    driver.get('https://account.cnblogs.com/signin')
    #隱式等待3秒
    driver.implicitly_wait(3)
    #找到使用者名稱標籤和密碼標籤,用ID查詢
    input_username=driver.find_element_by_id('LoginName')
    input_password=driver.find_element_by_id('Password')
    #輸入使用者名稱和密碼
    input_username.send_keys('11111111111')
    input_password.send_keys('xxxxxxxxxx')
    #找到提交按鈕
    submitBtn=driver.find_element_by_id('submitBtn')
    #點選提交
    submitBtn.click()

效果如圖所示:

第二步:彈出有缺口的圖,並擷取

找到該標籤,通過xpath查詢找到位置,(通過classname查詢,可能會報錯,原因未知),這個位置不僅是缺口圖的位置,還是原圖的位置,所以獲取原圖和缺口圖的方式是一樣的

先寫一個截圖函式:

from PIL import Image
def get_snap(driver):
    #建立一個空的圖片檔案
    driver.save_screenshot('snap.png')
    snap_obj=Image.open('snap.png')
    return snap_obj
def get_image(driver):
    #通過xpath找到元素
    img_element = driver.find_element_by_xpath(
        '//div[@class="geetest_panel_next"]//canvas[@class="geetest_canvas_slice geetest_absolute"]')
    #獲得圖片的大小和位置
    size = img_element.size
    location = img_element.location
    left=location['x']
    top=location['y']
    right=left+size['width']
    bottom=top+size['height']
    snap_obj=get_snap(driver)
    #注意該引數是元組
    img_obj=snap_obj.crop((left,top,right,bottom))
    return img_obj

 

通過獲得的left,top,right,bottom進行截圖

第三步:通過JS程式碼,顯示原圖

 

 找到該便籤,改變style中的display,其值為block時顯示的是無缺口圖:

現在通過程式碼改變該標籤的值:

driver.execute_script("var x=document.getElementsByClassName('geetest_canvas_fullbg geetest_fade geetest_absolute')[0];"
                          "x.style.display='block';"
                          "x.style.opacity=1"
                          )

測試時,有時候,opacity預設為0,需要變為1才會顯示原圖。

顯示原圖之後,因為位置是一樣的,同第二步,使用同一個函式進行截圖。

第四步:對比兩張圖片,即滑動的位移

none_img=get_image(driver)#缺口圖
    driver.execute_script("var x=document.getElementsByClassName('geetest_canvas_fullbg geetest_fade geetest_absolute')[0];"
                          "x.style.display='block';"
                          "x.style.opacity=1"
                          )
block_img=get_image(driver)#原圖

進行圖片滑動的距離的計算:

def get_distance(img1,img2):
    start_x=60#初始X
    threhold=60#閾值
    for x in range(start_x,img1.size[0]):
        for y in range(img1.size[1]):
            rgb1=img1.load()[x,y]
            rgb2=img2.load()[x,y]
            res1=abs(rgb1[0]-rgb2[0])
            res2=abs(rgb1[1]-rgb2[1])
            res3=abs(rgb1[2]-rgb2[2])
            if not (res1<threhold and res2<threhold and res3<threhold):
                return x-7#測試後-7可以提高成功率

關於初始值:

滑動驗證碼,缺口一定和滑塊有距離,所以滑塊的所佔的X的範圍可以排除,測量得出滑塊大小約為60畫素(包含邊距),所以start_x=60。

 

第五步:按照人的行為行為習慣,把總位移切成一段段小的位移

人的習慣為:先加速,再減速,可能有超出的現象。

為了保證更像人,本次有回退步驟

def get_tracks(distance):
    #distance為上一步得出的總距離。20是等會要回退的畫素
    distance+=20
    #初速度為0,s是已經走的路程,t是時間
    v0=2
    s=0
    t=0.4
   #mid是進行減速的路程
    mid=distance*3/5
   #存放走的距離
    forward_tracks=[]
    while s<distance:
        if s<mid:
            a=2
        else:
            a=-3
        #高中物理,勻加速路程的計算
        v=v0
        tance=v*t+0.5*a*(t**2)
        tance=round(tance)
        s+=tance
        v0=v+a*t
        forward_tracks.append(tance)
    #因為回退20畫素,所以可以手動打出,只要和為20即可
    back_tracks = [-1, -1, -1, -2, -2, -2, -3, -3, -2, -2, -1]  # 20
    return {"forward_tracks": forward_tracks, 'back_tracks': back_tracks}

 

第六步:按照距離移動

 

#獲得滑塊元素
geetest_slider_button=driver.find_element_by_class_name('geetest_slider_button')
    #獲得距離
    distance=get_distance(block_img,none_img)
    #獲得步數
    tracks_dic=get_tracks(distance)
   #點選並按住    ActionChains(driver).click_and_hold(geetest_slider_button).perform()
    forword_tracks=tracks_dic['forward_tracks']
    back_tracks=tracks_dic['back_tracks']
    for forword_track in forword_tracks:
        ActionChains(driver).move_by_offset(xoffset=forword_track,yoffset=0).perform()
    #停頓一會,更像人
    time.sleep(0.2)
    for back_tracks in back_tracks:
        ActionChains(driver).move_by_offset(xoffset=back_tracks, yoffset=0).perform()
    print(forword_tracks)
    ActionChains(driver).move_by_offset(xoffset=-3, yoffset=0).perform()
    ActionChains(driver).move_by_offset(xoffset=3, yoffset=0).perform()
    time.sleep(0.3)
    #鬆開滑鼠
    ActionChains(driver).release().perform()

 

 完整程式碼:

from selenium import webdriver
from selenium.webdriver import ActionChains
from selenium.webdriver.common.keys import Keys
from PIL import Image
import time
driver=webdriver.Chrome()

def get_snap(driver):
    driver.save_screenshot('snap.png')
    snap_obj=Image.open('snap.png')
    return snap_obj
def get_image(driver):
    img_element = driver.find_element_by_xpath(
        '//div[@class="geetest_panel_next"]//canvas[@class="geetest_canvas_slice geetest_absolute"]')
    size = img_element.size
    location = img_element.location
    left=location['x']
    top=location['y']
    right=left+size['width']
    bottom=top+size['height']
    snap_obj=get_snap(driver)
    img_obj=snap_obj.crop((left,top,right,bottom))
    return img_obj
# try:
#     driver.get('https://www.baidu.com')
#     driver.implicitly_wait(5)
#     r1=driver.find_element_by_link_text('登入').click()
#     driver.find_element_by_id('TANGRAM__PSP_10__footerULoginBtn').click()
#     input_username=driver.find_element_by_id('TANGRAM__PSP_10__userName')
#     input_username.send_keys('17396876501')
#     input_password=driver.find_element_by_id('TANGRAM__PSP_10__password')
#     input_password.send_keys('dfcver')
#     driver.find_element_by_id('TANGRAM__PSP_10__submit').click()
#     time.sleep(5)
# finally:
#     driver.close()
def get_distance(img1,img2):
    start_x=60
    threhold=60#閾值
    for x in range(start_x,img1.size[0]):
        for y in range(img1.size[1]):
            rgb1=img1.load()[x,y]
            rgb2=img2.load()[x,y]
            res1=abs(rgb1[0]-rgb2[0])
            res2=abs(rgb1[1]-rgb2[1])
            res3=abs(rgb1[2]-rgb2[2])
            if not (res1<threhold and res2<threhold and res3<threhold):
                return x-7
def get_tracks(distance):
    distance+=20
    v0=2
    s=0
    t=0.4
    mid=distance*3/5
    forward_tracks=[]
    while s<distance:
        if s<mid:
            a=2
        else:
            a=-3
        v=v0
        tance=v*t+0.5*a*(t**2)
        tance=round(tance)
        s+=tance
        v0=v+a*t
        forward_tracks.append(tance)
    back_tracks = [-1, -1, -1, -2, -2, -2, -3, -3, -2, -2, -1]  # 20
    return {"forward_tracks": forward_tracks, 'back_tracks': back_tracks}

try:
    driver.get('https://account.cnblogs.com/signin')
    driver.implicitly_wait(3)
    input_username=driver.find_element_by_id('LoginName')
    input_password=driver.find_element_by_id('Password')
    input_username.send_keys('928480709')
    input_password.send_keys('dfcver1112223334')
    submitBtn=driver.find_element_by_id('submitBtn')
    submitBtn.click()
    time.sleep(2)#等待驗證碼載入
    none_img=get_image(driver)
    driver.execute_script("var x=document.getElementsByClassName('geetest_canvas_fullbg geetest_fade geetest_absolute')[0];"
                          "x.style.display='block';"
                          "x.style.opacity=1"
                          )
    block_img=get_image(driver)
    geetest_slider_button=driver.find_element_by_class_name('geetest_slider_button')

    distance=get_distance(block_img,none_img)
    tracks_dic=get_tracks(distance)
    ActionChains(driver).click_and_hold(geetest_slider_button).perform()
    forword_tracks=tracks_dic['forward_tracks']
    back_tracks=tracks_dic['back_tracks']
    for forword_track in forword_tracks:
        ActionChains(driver).move_by_offset(xoffset=forword_track,yoffset=0).perform()
    time.sleep(0.2)
    for back_tracks in back_tracks:
        ActionChains(driver).move_by_offset(xoffset=back_tracks, yoffset=0).perform()
    print(forword_tracks)
    ActionChains(driver).move_by_offset(xoffset=-3, yoffset=0).perform()
    ActionChains(driver).move_by_offset(xoffset=3, yoffset=0).perform()
    time.sleep(0.3)
    ActionChains(driver).release().perform()

    time.sleep(60)
finally:
    driver.close()
完整程式碼

 

相關文章