極驗(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)證成功')

