1、前言:
目前很多網站會在正常的賬號密碼認證之外加一些驗證碼,以此來明確區分人/機行為,最典型的就是極驗滑動驗證。(如下圖)
這里我們以簡單實例說明如何實現自動校驗類似驗證。
2、步驟:
1)點擊驗證,彈出驗證碼圖片;
2)操作JS,獲取完整驗證碼圖片並截圖;
3)操作JS恢復原圖,獲取帶有缺口的驗證碼圖片並截圖;
4)對比兩張圖片所有的像素點,得到要移動的距離;
5)模擬人的行為,把需要拖動的總距離分成一段一段的軌跡;
6)按照軌跡拖動,完成驗證;
7)完成登錄;
3、准備工作:
1)安裝chrome瀏覽器;
2)配置好python+selenium環境;
3)安裝Pillow模塊;
4、詳細代碼:
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 from PIL import Image from io import BytesIO import time class AccessCode(object): def __init__(self,driver): self.driver = driver self.wait = WebDriverWait(driver, 20) self.border = 6 #設置偏差值 def get_position(self): """ 獲取驗證碼位置 :return: 驗證碼位置元組 """ img = self.wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'geetest_window'))) 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_screenshot(self): """ 獲取網頁截圖 :return: 截圖對象 """ screenshot = self.driver.get_screenshot_as_png() screenshot = Image.open(BytesIO(screenshot)) return screenshot def get_image1(self,filename): ''' 獲取完整驗證碼圖片 :return: 圖片對象 ''' time.sleep(0.2) js_code = '''document.getElementsByClassName('geetest_canvas_fullbg')[0].style.display="block";''' time.sleep(1) self.driver.execute_script(js_code) # 截取圖片 top, bottom, left, right = self.get_position() screenshot = self.get_screenshot() captcha = screenshot.crop((2 * left, 2 * top, 2 * right, 2 * bottom)) size = 258, 159 captcha.thumbnail(size) # 生成縮略圖 captcha.save(filename) return captcha def get_image2(self,filename): ''' 獲取有缺口的驗證碼圖片 :param filename: 圖片名稱 :return: 有缺口的驗證碼圖片對象 ''' time.sleep(0.2) js_code = '''document.getElementsByClassName('geetest_canvas_fullbg')[0].style.display="none";''' self.driver.execute_script(js_code) time.sleep(1) # 截取圖片 top, bottom, left, right = self.get_position() screenshot = self.get_screenshot() captcha = screenshot.crop((2*left, 2*top, 2*right, 2*bottom)) size = 258, 159 captcha.thumbnail(size) # 生成縮略圖 captcha.save(filename) return captcha def get_gap(self,image1, image2): """ 獲取缺口偏移量 :param img1: 不帶缺口圖片 :param img2: 帶缺口圖片 :return:缺口偏移量 """ left = 57 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 is_pixel_equal(self,img1, img2, x, y): """ 判斷兩個像素是否相同 :param image1: 圖片1 :param image2: 圖片2 :param x: 位置x :param y: 位置y :return: 像素是否相同 """ # 取兩個圖片的像素點 pixel1 = img1.getpixel((x, y)) pixel2 = img2.getpixel((x, y)) for i in range(0, 3): if abs(pixel1[i] - pixel2[i]) >= 60: return False return True 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.driver).click_and_hold(slider).perform() for x in track: ActionChains(self.driver).move_by_offset(xoffset=x, yoffset=0).perform() time.sleep(0.5) ActionChains(self.driver).release().perform() def get_slider(self): """ 獲取滑塊 :return: 滑塊對象 """ slider = self.wait.until(EC.element_to_be_clickable((By.CLASS_NAME, 'geetest_slider_button'))) return slider def crack(self): '''驗證操作''' #1.針對完整的圖片進行截取 image1 = self.get_image1('snap_full.png') #2.針對有缺口的圖片進行截取 image2 = self.get_image2('snap.png') #3.對比兩張圖片,獲取滑動距離 distance = self.get_gap(image1,image2) #4減去缺口位移 distance -= self.border #5.獲取滑塊對象 slider = self.get_slider() #6.模擬人為滑動軌跡 track = self.get_track(distance) #7.拖動滑塊 self.move_to_gap(slider, track) time.sleep(0.5) #8.失敗重試 try: success = self.wait.until(EC.text_to_be_present_in_element((By.XPATH,"//span[@class='geetest_success_radar_tip_content']"), '驗證成功')) if not success: content = self.driver.find_element_by_xpath("//div[@class='geetest_result_content']").text if "怪物吃了拼圖" in content: # 如果出現"怪物吃了拼圖"字樣,需要等待3S后繼續操作 time.sleep(3) if "拖動滑塊將懸浮圖像正確拼合" in content: #距離計算錯誤,刷新圖片驗證碼重試 self.driver.find_element_by_xpath("/html/body/div[3]/div[2]/div[1]/div/div[2]/div/a[2]").click() self.crack() else: #博客園校驗成功后自動登錄跳轉 time.sleep(5) print("------------登錄成功--------------") except Exception as e: content = self.driver.find_element_by_xpath("//div[@class='geetest_result_content']").text if "怪物吃了拼圖" in content: # 如果出現"怪物吃了拼圖"字樣,需要等待3S后繼續操作 time.sleep(3) if "拖動滑塊將懸浮圖像正確拼合" in content: # 距離計算錯誤,刷新圖片驗證碼重試 self.driver.find_element_by_xpath("/html/body/div[3]/div[2]/div[1]/div/div[2]/div/a[2]").click() self.crack() if __name__ == '__main__': chrome_options = webdriver.ChromeOptions() chrome_options.add_argument('--start-maximized') # 指定瀏覽器分辨率 chrome_options.add_argument('--disable-gpu') # 谷歌文檔提到需要加上這個屬性來規避bug driver = webdriver.Chrome(executable_path=DRIVER_PATH, options=chrome_options) #DRIVER_PATH為chromedriver存放路徑,自行變更 crack = AccessCode(driver) # 1.打開網頁 driver.get("https://passport.cnblogs.com/user/signin") driver.maximize_window() #窗口最大化 # 2.輸入用戶名,username自行補全 driver.find_element_by_xpath("//input[@id='input1']").send_keys(username) # 3.輸入密碼,password自行補全 driver.find_element_by_xpath("//input[@id='input2']").send_keys(password) # 4.點擊登錄,彈出驗證按鈕 driver.find_element_by_xpath("//input[@id='signin']").click() # 5.點擊驗證按鈕 time.sleep(3) driver.find_element_by_xpath("//div[@class='modal-content center-block']").click() driver.find_element_by_xpath("//div[@class='geetest_radar_tip']").click() # 6.調用驗證 crack.crack()