極驗(yàn)驗(yàn)證碼:需要手動(dòng)拼合滑塊來完成的驗(yàn)證早抠,相對(duì)圖形驗(yàn)證碼識(shí)別難度上升了幾個(gè)等級(jí)囚痴。下面用程序識(shí)別并通過極驗(yàn)驗(yàn)證碼的驗(yàn)證,其中有分析識(shí)別思路族展、識(shí)別缺口位置森缠、生成滑塊拖動(dòng)、模擬實(shí)現(xiàn)滑塊拼合通過驗(yàn)證等步驟仪缸。需要用到Chrome 瀏覽器贵涵,并配置 ChromeDriver ,要用到的 Python 庫(kù)是 Selenium恰画。
1宾茂、 對(duì)極驗(yàn)驗(yàn)證碼了解
極驗(yàn)驗(yàn)證碼官網(wǎng):http://www.geetest.com/。一個(gè)專注提供驗(yàn)證安全的系統(tǒng)拴还,主要驗(yàn)證方式是拖動(dòng)滑塊拼命圖像跨晴。若圖像完全拼合,則驗(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)
識(shí)別難度大。首先要點(diǎn)擊按鈕進(jìn)行智能驗(yàn)證势决,如果驗(yàn)證不通過阻塑,則會(huì)彈出滑動(dòng)窗口,拖動(dòng)滑塊拼合圖像進(jìn)行驗(yàn)證果复。之后三個(gè)加密參數(shù)會(huì)生成陈莽,通過表單提交到后臺(tái),后臺(tái)還會(huì)進(jìn)行一次驗(yàn)證虽抄。
極驗(yàn)驗(yàn)證碼增加了機(jī)器學(xué)習(xí)的方法來識(shí)別手動(dòng)軌跡走搁。其官方網(wǎng)站的安全防護(hù)有下面幾點(diǎn)說明:
三角防護(hù)之防模擬:惡意程序模仿人類行為軌跡對(duì)驗(yàn)證碼進(jìn)行識(shí)別。利用機(jī)器學(xué)習(xí)和神經(jīng)網(wǎng)絡(luò)迈窟,構(gòu)建線上線下的多重靜態(tài)私植、動(dòng)態(tài)防御模型,識(shí)別模擬軌跡车酣,界定人機(jī)邊界曲稼。
三角防護(hù)之防偽造:惡意程序通過偽造設(shè)備瀏覽器環(huán)境對(duì)驗(yàn)證碼進(jìn)行識(shí)別。利用設(shè)備基因技術(shù)湖员,深度分析瀏覽器的實(shí)際性能來辨識(shí)偽造信息贫悄,同時(shí)根據(jù)依靠事件不斷更新黑名單來提高防依靠能力。
三角防護(hù)之防暴力:惡意程序在短時(shí)間內(nèi)進(jìn)行密集的攻擊破衔,對(duì)驗(yàn)證碼進(jìn)行暴力識(shí)別清女。為了防暴力,極驗(yàn)驗(yàn)證碼有多種形態(tài)晰筛,每種形態(tài)都用神經(jīng)網(wǎng)絡(luò)生成海量圖庫(kù)儲(chǔ)備嫡丙,每一張圖片都是獨(dú)一無二的,且圖庫(kù)不斷更新读第,極大程度提高防暴力識(shí)別成本曙博。
3、 識(shí)別思路
對(duì)于極驗(yàn)驗(yàn)證碼怜瞒,直接模擬表單提交父泳,加密參數(shù)構(gòu)造較困難,需要分析其加密和校驗(yàn)邏輯吴汪,相對(duì)煩瑣惠窄。所以直接采用模擬瀏覽器動(dòng)作的方式來完成驗(yàn)證。使用 Python 的 Selenium 庫(kù)模擬人的行為方式來完成驗(yàn)證漾橙,成本要相對(duì)去識(shí)別加密算法少很多杆融。如圖所示。
!極驗(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)上做測(cè)試。在博客園的登錄頁(yè)面點(diǎn)擊登錄后會(huì)出現(xiàn)一個(gè)極驗(yàn)驗(yàn)證按鈕藕各。如圖2-2所示池摧。
該按鈕是智能驗(yàn)證按鈕。一般來說激况,如果是同一個(gè)會(huì)話作彤,一段時(shí)間內(nèi)第二次點(diǎn)擊會(huì)直接通過驗(yàn)證。如果智能識(shí)別不通過誉碴,則會(huì)彈出滑動(dòng)驗(yàn)證窗口宦棺,需要拖動(dòng)滑塊圖像完成二步驗(yàn)證。驗(yàn)證成功后黔帕,驗(yàn)證按鈕會(huì)提示驗(yàn)證成功代咸。接下來就是提交表單。
經(jīng)過上述分析成黄,極驗(yàn)驗(yàn)證需要完成下面三步:
(1)模擬點(diǎn)擊驗(yàn)證按鈕呐芥。
(2)識(shí)別滑動(dòng)缺口的位置。
(3)模擬手動(dòng)滑塊奋岁。
第(1)步操作相對(duì)簡(jiǎn)單思瘟,可使用 Selenium 模擬點(diǎn)擊按鈕。
第(2)步操作識(shí)別缺口的位置很關(guān)鍵闻伶,需要用到圖像的相關(guān)處理方法滨攻。首先觀察缺口的樣子,如圖所示蓝翰。
缺口的四周邊緣有明顯的斷裂邊緣光绕,邊緣與邊緣周圍有明顯的區(qū)別⌒蠓荩可用邊緣檢測(cè)算法來找出缺口的位置诞帐。對(duì)于極驗(yàn)驗(yàn)證碼,可以利用和原圖對(duì)比檢測(cè)方式識(shí)別缺口的位置爆雹,通常在沒有拖動(dòng)滑塊前停蕉,缺口沒有呈現(xiàn)。
可以同時(shí)獲取兩張圖片钙态,設(shè)定一個(gè)對(duì)比閾值慧起,然后遍歷兩張圖片,找出相同位置像素 RGB 差距超過此閾值的像素點(diǎn)册倒,此像素點(diǎn)的位置就是缺口的位置完慧。
第(3)步中要注意的是,極驗(yàn)驗(yàn)證碼增加了機(jī)器軌跡識(shí)別,勻速移動(dòng)屈尼、隨機(jī)速度移動(dòng)等方法都不能通過驗(yàn)證,只有完全模擬人的移動(dòng)軌跡才可以通過驗(yàn)證拴孤。人的移動(dòng)軌跡一般是先加速后減速脾歧,要對(duì)這個(gè)過程模擬才能成功。
要包括這幾個(gè)步驟演熟。
第一步鞭执,初始化,在這里我們先初始化 一些selenium的 配置及一些參數(shù)的配置芒粹。
第二步兄纺,就是模擬點(diǎn)擊了,這里主要是利用selenium模塊模擬瀏覽器對(duì)網(wǎng)頁(yè)進(jìn)行操作化漆。
第三步估脆,就該識(shí)別缺口的位置了。首先獲取前后兩張圖片座云,得到其所在位置和寬高疙赠,然后獲取整個(gè)網(wǎng)頁(yè)的截圖,圖片裁切下來即可朦拖。
最后一步圃阳,模擬拖動(dòng),經(jīng)過多次試驗(yàn)璧帝,得出一個(gè)結(jié)論捍岳,那就是完全模擬加速減速的過程通過了驗(yàn)證。前段作勻加速睬隶,后段作勻減速運(yùn)動(dòng)锣夹,利用物理學(xué)的加速度公式即可完成驗(yàn)證。
位移移動(dòng)
需要的基礎(chǔ)知識(shí)
位移移動(dòng)相當(dāng)于勻變速直線運(yùn)動(dòng)理疙,類似于小汽車從起點(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)于圖片的一些知識(shí)點(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屬性可以返回該圖片對(duì)象(既這張圖片)在瀏覽器中的位置滤蝠,以字典的形式返回,{‘x’:30,‘y’:30} 這里我們圖片的位置是(30,30)授嘀。坐標(biāo)軸是以屏幕左上角為原點(diǎn)物咳,x軸向右遞增,y軸像下遞增蹄皱。(相對(duì)整個(gè)html的坐標(biāo))
size = img.size
# 通過size獲取屬性大小览闰,size屬性同樣返回一個(gè)字典芯肤,{‘height’:30,‘width’:30 } 即圖片對(duì)象的高度,寬度压鉴。
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è)頁(yè)面歌豺,現(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: 返回按鈕對(duì)象
"""
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屬性可以返回該圖片對(duì)象(既這張圖片)在瀏覽器中的位置谴咸,以字典的形式返回轮听,{‘x’:30,‘y’:30} 這里我們圖片的位置是(30,30)。坐標(biāo)軸是以屏幕左上角為原點(diǎn)岭佳,x軸向右遞增血巍,y軸像下遞增。(相對(duì)整個(gè)html的坐標(biāo))
location = img.location
# size屬性同樣返回一個(gè)字典珊随,{‘height’:30,‘width’:30 } 即圖片對(duì)象的高度述寡,寬度。
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)頁(yè)截圖
:return: 截圖對(duì)象
"""
pic.png = self.browser.get_screenshot_as_png()
screenshot = Image.open(BytesIO(pic.png)) # 轉(zhuǎn)換為字節(jié)對(duì)象
'''
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: 圖片對(duì)象
"""
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è)頁(yè)面艺晴,現(xiàn)在只把驗(yàn)證碼圖片取到
captcha.save(name) # 把圖片存儲(chǔ)起來昼钻,name是傳進(jìn)來的變量
return captcha
def get_slider(self):
"""
獲取滑塊
:return: 滑塊對(duì)象
"""
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處開始往右封寞,這樣就可以把被拖動(dòng)滑塊跳過去然评,因?yàn)楸煌蟿?dòng)滑塊處像素也不一樣
# 相當(dāng)于從60開始,一列列做對(duì)比
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ù)偏移量獲取移動(dòng)軌跡
:param distance: 偏移量
:return: 移動(dòng)軌跡
"""
# 移動(dòng)軌跡
track = []
# 當(dāng)前位移
current = 0
# 減速閾值
mid = distance * 4 / 5
# 計(jì)算間隔
t = 0.2
# 初速度
v = 0
while current < distance: # 所以 track是不會(huì)大于總長(zhǎng)度的
if current < mid:
# 加速度為正2
a = 2
else:
# 加速度為負(fù)3
a = -3
# 初速度v0
v0 = v
# 移動(dòng)距離x = v0t + 1/2 * a * t^2亿眠,現(xiàn)做了加速運(yùn)動(dòng)
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 軸移動(dòng)的軌跡
return track
def move_to_gap(self, slider, track):
"""
拖動(dòng)滑塊到缺口處
:param slider: 滑塊
:param track: 軌跡
:return:
"""
ActionChains(self.browser).click_and_hold(slider).perform() # 利用動(dòng)作鏈,獲取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) # 推動(dòng)到合適位置之后爹耗,暫停一會(huì)
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按鈕之后,才會(huì)出現(xiàn)缺口
# 獲取帶缺口的驗(yàn)證碼圖片
image2 = self.get_geetest_image('captcha2.png')
# 獲取缺口位置
gap = self.get_gap(image1, image2)
print('缺口位置', gap)
# 減去缺口的位移(因?yàn)槎范簦瑝K移動(dòng)img1到img2的時(shí)候山卦,允許有誤差范圍。在范圍內(nèi)诵次,即使有幾個(gè)像素差账蓉,也是可以接受的
gap -= BODER
# 獲取移動(dòng)軌跡
track = self.get_track(gap)
print('滑動(dòng)軌跡',track)
# 拖動(dòng)滑塊到缺口處。模擬人的行為習(xí)慣(先勻加速拖動(dòng)后勻減速拖動(dòng))逾一,把需要拖動(dòng)的總距離分成一段一段小的軌跡
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)證成功')