目錄javascript
近些年來出現了一些新型驗證碼,不想舊的驗證碼對人類不友好,可是這種驗證碼對於代碼來講識別難度上升了幾個等級。所以須要其餘的手段進行處理。
識別須要的python庫:selenium和ChromeDriver驅動,不一樣瀏覽器的要下載的驅動庫不一樣。
驗證碼獲取網站:http://www.geetest.com/
極驗滑動驗證碼已經到了3.0版本,相關於圖形驗證碼識別難度更大,原理是拖動圖片到缺口處,而後拼合圖像進行驗證,會生成三個加密參數,經過表單提交到後臺,後臺再進行驗證。
極驗驗證碼還增長了機器學習的方法來識別是不是惡意程序進行識別,有防模擬,防僞造,防暴力的方式, 只需0.4秒,而且保護資源不被濫用和盜取。
咱們的程序通常只要不是惡意進行爬取的,並遵照爬蟲協議,就能夠。千萬不要給服務器形成負擔。java
這裏咱們能夠採用模擬瀏覽器動做的方式完成驗證,用Selenium來徹底模擬人的行爲完成驗證。
主要分爲三步
(1)模擬點擊驗證按鈕
(2)識別滑動缺口的位置
(3)模擬拖動滑塊
第(1)步還比較好說,第(2)步操做識別接口的位置比較關鍵,須要用到圖像處理看到接口的位置,並和原圖對比檢測的方法來識別缺口的位置。同時獲取兩張圖片,設定一個對比閾值,而後遍歷兩張圖片,找出相同像素RGB差距超過此閾值的像素點,那麼像素點位置就是缺口的位置。
第(3)步較難,因爲人的移動軌跡是先加速後減速,勻速移動和隨機移動等方法都不能經過驗證,要模擬好這個過程。python
官網圖片爲:
web
# 註冊的用戶名和密碼 email = '' password = '' class CrackGeetest(): def __init__(self): self.url = 'https://account.geetest.com/login' self.browser = webdriver.Chrome() self.wait = WebDriverWait(self.browser, 20) self.email = email self.password = password
識別驗證碼第一步就是模擬點擊初始的驗證按鈕,用顯式等待的方法進行獲取。canvas
def get_geetest_button(self): """ 獲取初始驗證按鈕 返回值:按鈕對象 """ button = self.wait.until(EC.element_to_be_clickable((By.CLASS_NAME, 'geetest_radar_tip'))) return button
在調用位置便可模擬點擊:瀏覽器
# 點擊驗證按鈕 button = self.get_geetest_button() button.click()
接下來識別缺口的位置,首先獲取兩張圖片,進行對比,不同的位置就是缺口。
獲取不帶缺口的圖片。用selenium選取圖片元素獲得整個網頁的截圖而後裁剪便可,代碼以下:服務器
def get_screenshot(self): """ 獲取網頁截圖 :return: 截圖對象 """ screenshot = self.browser.get_screenshot_as_png() screenshot = Image.open(BytesIO(screenshot)) return screenshot def get_position(self): """ 獲取驗證碼位置 :return: 驗證碼位置元組 """ img = self.wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'geetest_canvas_img'))) time.sleep(2) location = img.location size = img.size top, bottom, left, right = location['y'], location['y'] + size['height'], location['x'], location['x'] + size[ 'width'] return (top, bottom, left, right) def get_geetest_image(self, name='captcha.png'): """ 獲取驗證碼圖片 :return: 圖片對象 """ top, bottom, left, right = self.get_position() print('驗證碼位置', top, bottom, left, right) screenshot = self.get_screenshot() captcha = screenshot.crop((left, top, right, bottom)) captcha.save(name) return captcha
接下來須要獲取第二張圖片,就是帶有缺口的圖片,只須要點擊下面的滑塊就能出現缺口,代碼以下:app
def get_slider(self): """ 獲取滑塊 :return: 滑塊對象 """ slider = self.wait.until(EC.element_to_be_clickable((By.CLASS_NAME, 'geetest_slider_button'))) return slider
用click()便可觸發點擊,以下:機器學習
# 點按呼出缺口 slider = self.get_slider() slider.click()
接下來就是經過對比圖片獲取缺口,經過遍歷圖片上的每一個座標點,獲取兩張圖片對應像素點的RGB數據。若是在必定範圍內,那就表明兩個像素相同,繼續對比下一個像素點。若是差距超過必定範圍,則表明像素點不一樣,當前位置就是缺口位置。經過設置一個閾值threshold,來進行判斷,代碼以下:ide
def is_pixel_equal(self, image1, image2, x, y): """ 判斷兩個像素是否相同 :param image1: 圖片1 :param image2: 圖片2 :param x: 位置x :param y: 位置y :return: 像素是否相同 """ # 取兩個圖片的像素點 pixel1 = image1.load()[x, y] pixel2 = image2.load()[x, y] threshold = 60 if abs(pixel1[0] - pixel2[0]) < threshold and abs(pixel1[1] - pixel2[1]) < threshold and abs( pixel1[2] - pixel2[2]) < threshold: return True else: return False def get_gap(self, image1, image2): """ 獲取缺口偏移量 :param image1: 不帶缺口圖片 :param image2: 帶缺口圖片 :return: """ left = 60 for i in range(left, image1.size[0]): for j in range(image1.size[1]): if not self.is_pixel_equal(image1, image2, i, j): left = i return left return left
模擬拖動並不複雜,可是裏面的細節比較多。用相關的函數將滑塊拖動到對應的位置便可。可是要是勻速拖動,會必然識別出是程序,非人類操做,由於人類沒法作到徹底勻速拖動,會識別出是機器操做,使得驗證碼失敗。
經過不一樣的方法檢測,咱們發現把前段滑塊作勻加速運動,後段滑塊作勻減速運動,便可完成驗證。
這裏加速度用a來表示,當前速度用v表示,初速度用vo表示,位移用x表示,時間用t表示。
代碼以下:
def get_track(self, distance): """ 根據偏移量獲取移動軌跡 :param distance: 偏移量 :return: 移動軌跡 """ # 移動軌跡 track = [] # 當前位移 current = 0 # 減速閾值 mid = distance * 4 / 5 # 計算間隔 t = 0.2 # 初速度 v = 0 while current < distance: if current < mid: # 加速度爲正2 a = 2 else: # 加速度爲負3 a = -3 # 初速度v0 v0 = v # 當前速度v = v0 + at v = v0 + a * t # 移動距離x = v0t + 1/2 * a * t^2 move = v0 * t + 1 / 2 * a * t * t # 當前位移 current += move # 加入軌跡 track.append(round(move)) return track def move_to_gap(self, slider, track): """ 拖動滑塊到缺口處 :param slider: 滑塊 :param track: 軌跡 :return: """ ActionChains(self.browser).click_and_hold(slider).perform() for x in track: ActionChains(self.browser).move_by_offset(xoffset=x, yoffset=0).perform() time.sleep(0.5) ActionChains(self.browser).release().perform()
import time from io import BytesIO from PIL import Image from selenium import webdriver from selenium.webdriver import ActionChains from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC EMAIL = 'cqc@cuiqingcai.com' PASSWORD = '' BORDER = 6 INIT_LEFT = 60 # 註冊的用戶名和密碼 email = '' password = '' class CrackGeetest(): def __init__(self): self.url = 'https://account.geetest.com/login' self.browser = webdriver.Chrome() self.wait = WebDriverWait(self.browser, 20) self.email = email self.password = password def __del__(self): self.browser.close() def get_geetest_button(self): """ 獲取初始驗證按鈕 返回值:按鈕對象 """ button = self.wait.until(EC.element_to_be_clickable((By.CLASS_NAME, 'geetest_radar_tip'))) return button def get_screenshot(self): """ 獲取網頁截圖 :return: 截圖對象 """ screenshot = self.browser.get_screenshot_as_png() screenshot = Image.open(BytesIO(screenshot)) return screenshot def get_position(self): """ 獲取驗證碼位置 :return: 驗證碼位置元組 """ img = self.wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'geetest_canvas_img'))) time.sleep(2) location = img.location size = img.size top, bottom, left, right = location['y'], location['y'] + size['height'], location['x'], location['x'] + size[ 'width'] return (top, bottom, left, right) def get_geetest_image(self, name='captcha.png'): """ 獲取驗證碼圖片 :return: 圖片對象 """ top, bottom, left, right = self.get_position() print('驗證碼位置', top, bottom, left, right) screenshot = self.get_screenshot() captcha = screenshot.crop((left, top, right, bottom)) captcha.save(name) 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 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 is_pixel_equal(self, image1, image2, x, y): """ 判斷兩個像素是否相同 :param image1: 圖片1 :param image2: 圖片2 :param x: 位置x :param y: 位置y :return: 像素是否相同 """ # 取兩個圖片的像素點 pixel1 = image1.load()[x, y] pixel2 = image2.load()[x, y] threshold = 60 if abs(pixel1[0] - pixel2[0]) < threshold and abs(pixel1[1] - pixel2[1]) < threshold and abs( pixel1[2] - pixel2[2]) < threshold: return True else: return False def get_gap(self, image1, image2): """ 獲取缺口偏移量 :param image1: 不帶缺口圖片 :param image2: 帶缺口圖片 :return: """ left = 60 for i in range(left, image1.size[0]): for j in range(image1.size[1]): if not self.is_pixel_equal(image1, image2, i, j): left = i return left return left def get_track(self, distance): """ 根據偏移量獲取移動軌跡 :param distance: 偏移量 :return: 移動軌跡 """ # 移動軌跡 track = [] # 當前位移 current = 0 # 減速閾值 mid = distance * 4 / 5 # 計算間隔 t = 0.2 # 初速度 v = 0 while current < distance: if current < mid: # 加速度爲正2 a = 2 else: # 加速度爲負3 a = -3 # 初速度v0 v0 = v # 當前速度v = v0 + at v = v0 + a * t # 移動距離x = v0t + 1/2 * a * t^2 move = v0 * t + 1 / 2 * a * t * t # 當前位移 current += move # 加入軌跡 track.append(round(move)) return track def move_to_gap(self, slider, track): """ 拖動滑塊到缺口處 :param slider: 滑塊 :param track: 軌跡 :return: """ ActionChains(self.browser).click_and_hold(slider).perform() for x in track: ActionChains(self.browser).move_by_offset(xoffset=x, yoffset=0).perform() time.sleep(0.5) ActionChains(self.browser).release().perform() 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() # 點擊驗證按鈕 button = self.get_geetest_button() button.click() # 獲取驗證碼圖片 image1 = self.get_geetest_image('captcha1.png') # 點按呼出缺口 slider = self.get_slider() slider.click() # 獲取帶缺口的驗證碼圖片 image2 = self.get_geetest_image('captcha2.png') # 獲取缺口位置 gap = self.get_gap(image1, image2) print('缺口位置', gap) # 減去缺口位移 gap -= BORDER # 獲取移動軌跡 track = self.get_track(gap) print('滑動軌跡', track) # 拖動滑塊 self.move_to_gap(slider, track) success = self.wait.until( EC.text_to_be_present_in_element((By.CLASS_NAME, 'geetest_success_radar_tip_content'), '驗證成功')) print(success) # 失敗後重試 if not success: self.crack() else: self.login() if __name__ == '__main__': crack = CrackGeetest() crack.crack()
這種方法對於不一樣的極驗滑動驗證碼來講都適用,關鍵在於識別的思路,如何識別缺口位置,如何生成運動軌跡等。以後遇到相似的驗證碼,均可以這樣進行識別。