極驗滑動驗證碼缺口識別
感謝崔大佬的《python3網絡爬蟲開發實戰》這本書讓我開了眼界。最近學習到驗證碼識別這一塊,發現隨着網站防爬手段的不斷更新,要填的坑真是越來越多,這篇文章就分享一下近段時間的填坑之旅。
首先來說一下書上的缺口識別函數,大概原理是通過兩張圖片的像素對比,識別像素不同的點確定缺口位置。然而這種識別方法的前提條件是必須要有兩張圖,一張圖是不含缺口的圖片,另一張則是帶缺口的圖片。
但由於時間原因,我接觸書上的代碼時,極驗證網站的滑動識別機制已經更新。對的,網站上截不到第一張不帶缺口的圖片了,所以在我手動copy崔大佬的代碼運行時結果必然是失敗的。
所以我自己寫了兩種解決滑動驗證碼的方法與大家分享,希望能擦出思想的火花。
第一種方法大概不算識別方法吧,因爲裏面有些投機取巧的地方。
投機就投機在我偶然發現了繞過驗證碼可以直接登錄的地方。
偶然間發現極驗證那個智能驗證按鈕,按鈕左邊的小圓圈裏的指針,會隨着鼠標的座標而轉動,然後我就發現這個小圈圈其實就是所謂智能驗證的核心。也就是只要在這個按鈕加載出來後,有鼠標在周圍移動時,就會被視爲是人爲操作;而如果是按鈕加載出來後代碼直接點擊就會被視爲爬蟲,然後就會有滑動驗證碼來作爲反爬蟲的一道大坎。
所以要解決這個問題其實很簡單,我能想到的就是用代碼在按鈕附近移動鼠標,巧妙地繞過驗證。
所以我導入了py的pyautogui 模塊,在確認了按鈕座標後根據座標位置讓鼠標在其周圍稍微移動。
核心代碼是:pyautogui.moveTo(x,y,duration=0.1)
pyautogui.moveTo(x+20,y+20,duration=5)
其中x,y爲按鈕的座標,duration爲移動延時。延時儘量調整在5秒左右,因爲移動速度過快那個小圈圈不買賬。
上面代碼執行後再點擊按鈕就會出現如下情況(嘿嘿):
第一種解決方法真的是絕妙,繞過了複雜的缺口識別,輕而易舉就完事了。
但,這似乎有點勝之不武,是男人就應該正面解決問題(鋼),在別人背後偷偷插一刀不是男人的作風。所以,重點來了,第二種方法就是正面解決問題的正路!
先講講思路吧。
在經過一番觀察後,我發現我想要找到那個缺口的位置似乎是黑乎乎的,這個黑,就成爲了解決問題的切入點。
是的,基本思路就是通過像素的rgb值找出那塊黑乎乎的目標位置的x座標。
然後我展開了我的一系列仔細的觀察,還能發現,那個滑塊的周圍的顏色似乎是固定的,
固定的橙色系(瞎說)。
下面是研究對象:
然後我就開始尋找這個橙色系的rgb值的浮動範圍。
下面是經過一系列計算後的結果:
上面的計算包括rgb三個值的浮動範圍,三個值兩兩相減的絕對值的浮動範圍以及rgb值的比率。
後來我就把這個橙色系的rgb範圍大概計算出來了:
r區間:[159,247]
g區間:[154,249]
b區間:[102,231]
abs(r-g)區間:[0,10]
abs(g-b)區間:[18,117]
計算三個值的比例我是通過rgb各值除以r值,然後取的g值和b值之和,浮動區間爲:
[1.4,1.9]
有了這些數據之後就很容易能識別出滑塊周圍的那一圈橙色系(似乎不是很專業,嘿嘿)。
接下來就是發現滑塊的位置的y座標的那條直線必定經過缺口。
能夠定位滑塊邊緣座標之後,爲了減少其他因素的影響,通過減少識別範圍提高識別率,對上面的圖進行範圍縮小處理(截圖)。
接下來要處理的就是如何識別出上面紅框框裏面的缺口的x座標。
黑是識別的關鍵,也就是上面紅線標註旁邊的那條豎線像素rgb三值之和是圖像中以x座標爲直線所有像素點rgb三個值和的最小值。
解決方法來了,也就是以x座標劃豎線,遍歷計算出rgb三值之和最小的那條,然後確定x座標,x座標即爲缺口位置。
如下爲截取的圖片:
在ps上放大分析:
大功告成!!!
from io import BytesIO
from PIL import Image
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import time
url = 'https://account.geetest.com/login'
EMAIL = '*********'
PASSWORD = '*********'
def open_firefox(url,email,password):
'''
:param url: 極驗證登錄頁面地址
:param email: 登錄賬號
:param password: 密碼
:return:
'''
browers=webdriver.Firefox()
browers.get(url)
wait=WebDriverWait(browers,10)
email_block=wait.until(EC.presence_of_element_located((By.ID, 'email')))
password_block=wait.until((EC.presence_of_element_located((By.ID, 'password'))))
email_block.send_keys(email)
password_block.send_keys(password)
button =wait.until(EC.element_to_be_clickable((By.CLASS_NAME, 'geetest_radar_tip')))
button.click()
return browers,wait
def identify_gap(browers,wait):
'''
:param browers: 瀏覽器對象
:param wait: wait對象
:return: 缺口位置x座標
'''
#定位驗證碼圖片
small_img=wait.until(EC.presence_of_element_located((By.XPATH, '//canvas[@class="geetest_canvas_bg geetest_absolute"]')))
location=small_img.location #獲取圖片位置,及大小
size=small_img.size
top, bottom, left, right = location['y'], location['y'] + size['height'], location['x'], location['x'] + size[
'width'] #確定截圖位置
time.sleep(5)
screenshot=browers.get_screenshot_as_png()
screenshot = Image.open(BytesIO(screenshot))
captcha = screenshot.crop((left, top, right, bottom))
captcha.save(r'first.png') #保存圖片
first=Image.open(r'first.png')
xsize,ysize=first.size
pool=[] #保存符合條件的像素點的座標信息的數據池
pix=first.load()
for i in range(xsize): #顏色識別區域
for j in range(ysize):
if 159<=(pix[i,j])[0]<=247 and 154<=(pix[i,j])[1] <=249 and 102 <=(pix[i,j])[2]<=231:
if 0<=abs((pix[i,j])[0]-(pix[i,j])[1])<=10:
if 18<=abs((pix[i,j])[1]-(pix[i,j])[2])<=117 :
if 1.4<=(pix[i,j])[1]/(pix[i,j])[0]+(pix[i,j])[2]/(pix[i,j])[0]<=1.97:
pool.append((i,j))
#print(pool)
x,y=(pool[0])[0],(pool[0])[1] #獲取第一個符合條件的像素點的位置
captcha1=screenshot.crop((left+x,top+y,right,top+y+5)) #進行截圖
captcha1.save(r'second.png')
Pool=[] # 第二張截圖根據x座標的每一條豎線的rgb值和的數據池
second=Image.open(r'second.png')
Xsize,Ysize=second.size
pix1=second.load()
for i in range(Xsize):
sum=0
for j in range(Ysize):
sum+=(pix1[i,j])[0]+(pix1[i,j])[1]+(pix1[i,j])[1]
Pool.append((i,sum))
Pool=sorted(Pool,key=lambda x:x[1]) #排序,找出rgb值得和的最低豎線的x座標
#print(Pool)
return (Pool[0])[0]+x #返回偏移值
if __name__=='__main__':
browers,wait=open_firefox(url=url,email=EMAIL,password=PASSWORD)
print(identify_gap(browers,wait))