模擬登入物件:部落格園
驗證碼型別:無原圖滑動驗證碼
使用工具與模組: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()