廢話不多說,好久不寫博客了,難得用心寫了一下,怎么也給自己留個念想。。。雖然工作上暫時用不到。。。
其實網上已經好多了,我寫的時候遇到的問題也參考了不少其他人的代碼,嗯。。,那我這個就叫小白解讀帖吧,講解還算詳細
網站說:
少於150字的隨筆不允許發布到網站首頁
尼瑪,我下面的代碼數量何止150個字啊,哎,開始湊字數,你們懂的,作為毫無營養的廢話,我決定用顏色把他。。。靠,我說到哪了,嗯,反正也是廢話,湊字而已。日啊
自己都看不見的文字,讓我想起了m。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
我們開發的說,能寫一行代碼的絕對不寫兩行,但凡能攜程這樣的都是可以優化的,好比lamabada變大時,我覺得我打錯了。好吧,好吧,反正我也看不見。。湊夠字了吧。。
# -*- coding: utf-8 -*- """ @author:你們的龍哥 @2018-06-21 規則只匹配極驗的官網,若使用破解驗證碼的規則,仍需要根據實際項目情況做調整 建議:本程序提供的是思路,無需修改本程序代碼 寫代碼過程中遇到的問題: 1、chrome driver要下載,並配置好,過程忘記了,請百度。driver的版本一定要和chrome的版本對應。。。 2、chrome瀏覽器不在環境變量的,要把第32行(大概位置)注釋解開,並填入自己電腦chrome的位置。要不就會報錯,二進制文件找不到之類的。 3、reload(sys)要加,不然不能識別中文 4、location定位的時候,經常出白圖,是因為save_screenshot()截屏的時候,保存的圖片像素為實際像素的2倍,這個百度了一下,有人說是高分屏的原因,因為我用的mac? 5、第一次識別失敗后,經常會報錯,好像是時間超時之類的,我try掉了。想踩雷的,可以去掉所有的try自己玩一下。 6、本代碼經過本機的極限測試,連續嘗試40多次后依然可以正常運行,然后我就手動結束了測試。 """ from PIL import Image from selenium import webdriver from selenium.webdriver import ActionChains from selenium.webdriver.common.by import By from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.wait import WebDriverWait from selenium.webdriver.chrome.options import Options import logging import time import sys reload(sys) sys.setdefaultencoding('utf8') # 本段為指定chrome瀏覽器的位置,如果chrome安裝的時候放到了環境變量中,應該用不到這樣寫,注釋掉就可以 # 果然,把chrome移動到應用程序里面后,自動就配置到環境變量了(mac本) chrome_options = Options() # chrome_options.binary_location = r"/Users/qmp/Downloads/Google Chrome.app/Contents/MacOS/Google Chrome" class CrackGeetest(): """ 實現了正常登陸、錯誤重試、多次重試后刷新圖片的功能。。。除非規則和頁面結構變更,要不基本走不到多次刷新圖片重試這一步 沒有對crack()和fail_again()這兩個類方法進行精簡,是希望程序的運行步驟清晰明了。。。 """ def __init__(self,url,email,password,threshold=60,left=57,deviation=6): self.url = url self.driver = webdriver.Chrome(chrome_options=chrome_options) self.wait = WebDriverWait(self.driver,10) self.email = email self.password = password self.threshold = threshold #驗證碼圖片對比中RGB的差值,可調 self.left = left #驗證碼圖片的對比中的起始坐標,即拖動模塊的右邊線位置 self.deviation = deviation # 偏移量,這個值是多次測試得出的經驗值 self.count = 1 def close_win(self): self.driver.close() def get_geetest_button(self): """ 點擊按鈕,彈出沒有缺口的圖片 :return: 返回按鈕對象 """ button = self.wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'geetest_radar_tip'))) return button def get_slider(self): """ 獲取滑塊 :return: 滑塊對象 """ slider = self.wait.until(EC.presence_of_element_located((By.CLASS_NAME,'geetest_slider_button'))) return slider def get_snap(self): """ 對整個網頁截圖,保存圖片,然后用PIL.Image拿到圖片對象 :return: 圖片對象 """ self.driver.save_screenshot('極驗驗證過程中產生的圖片.png') page_snap_obj = Image.open('極驗驗證過程中產生的圖片.png') return page_snap_obj def get_image(self,name='captcha.png'): """ 從網頁的網站截圖中,截取驗證碼圖片 :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 = location['y'] bottom = location['y'] + size['height'] left = location['x'] right = location['x'] + size['width'] page_snap_obj = self.get_snap() #這里強調一下:大概由於高分屏的原因,網頁的截圖是實際像素的2倍,所以驗證碼定位也要相應*2 # crop_imag_obj = page_snap_obj.crop((left,top,right,bottom)) crop_imag_obj = page_snap_obj.crop((2 * left, 2 * top, 2 * right, 2 * bottom)) size = 258, 159 crop_imag_obj.thumbnail(size) #實際生產環境下就不需要保存這張圖片了 # crop_imag_obj.save(name) return crop_imag_obj def open(self): """ 打開網頁輸入用戶名和密碼 :return: """ self.driver.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_distance(self,image1, image2): """ 拿到滑動驗證碼需要移動的距離 :param image1: 沒有缺口的圖片對象 :param image2: 帶缺口的圖片對象 :return: 需要移動的距離 """ i = 0 for i in range(self.left, image1.size[0]): for j in range(image1.size[1]): rgb1 = image1.load()[i, j] rgb2 = image2.load()[i, j] res1 = abs(rgb1[0] - rgb2[0]) res2 = abs(rgb1[1] - rgb2[1]) res3 = abs(rgb1[2] - rgb2[2]) if not (res1 < self.threshold and res2 < self.threshold and res3 < self.threshold): return i - self.deviation # 誤差矯正 logging.debug('未識別出驗證碼中的不同位置,或圖片定位出現異常') return i # 如果沒有識別出不同位置,則象征性的滑動,以刷新下一張驗證碼 def get_tracks(self,distance): """ 拿到移動軌跡,模仿人的滑動行為,先勻加速后均減速 勻變速運動基本公式: ①:v=v0+at ②:s=v0t+½at² ③:v²-v0²=2as :param distance:需要移動的距離 :return:存放每0.3秒移動的距離 """ distance += 20 # 先滑過一點,最后再反着滑動回來 # 初速度 v = 0 # 單位時間為0.3s來統計軌跡,軌跡即0.3s內的位移 t = 0.3 # 位移/軌跡列表,列表內的一個元素代表0.3s的位移 forward_tracks = [] # 當前位移 current = 0 # 到達mid值開始減速 mid = distance * 4 / 5 while current < distance: if current < mid: # 加速度越小,單位時間的位移越小,模擬的軌跡就越多越詳細 a = 2 else: a = -3 # 初速度 v0 = v # 0.3秒時間內的位移 s = v0 * t + 0.5 * a * (t ** 2) # 當前的位置 current += s # 添加到軌跡列表,round()為保留一位小數且該小數要進行四舍五入 forward_tracks.append(round(s)) # 速度已經達到v,該速度作為下次的初速度 v = v0 + a * t # 反着滑動到准確位置 back_tracks = [-3, -3, -2, -2, -2, -2, -2, -1, -1, -1] # 總共等於-20 return {'forward_tracks': forward_tracks, 'back_tracks': back_tracks} def login(self): """ 登錄 :return: None """ submit = self.wait.until(EC.element_to_be_clickable((By.CLASS_NAME, 'login-btn'))) submit.click() print('登錄成功') logging.debug('登陸成功') def crack(self): """ 程序運行流程。。。 :return: """ #步驟一:輸入同戶名密碼 self.open() #步驟二:點擊按鈕,彈出沒有缺口的圖片 button = self.get_geetest_button() button.click() time.sleep(0.5) # 步驟三:拿到沒有缺口的圖片 image1 = self.get_image('captcha1.png') # 步驟四:點擊托送按鈕,彈出有缺口的圖片 slider = self.get_slider() slider.click() #步驟五:拿到有缺口的圖片 image2 = self.get_image('captcha2.png') #步驟六:對比兩張圖片的所有RBG像素點,得到不一樣像素點間的差值,即要移動的距離 distance = self.get_distance(image1, image2) # 步驟七:模擬人的行為習慣(先勻加速拖動后勻減速拖動),把需要拖動的總距離分成一段一段小的軌跡 tracks = self.get_tracks(distance) # 步驟八:按照軌跡拖動,完成驗證 button = self.wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'geetest_slider_button'))) ActionChains(self.driver).click_and_hold(slider).perform() # 正常人類總是自信滿滿地開始正向滑動,自信地表現是瘋狂加速 for track in tracks['forward_tracks']: ActionChains(self.driver).move_by_offset(xoffset=track, yoffset=0).perform() # 結果傻逼了,正常的人類停頓了一下,回過神來發現,卧槽,滑過了,然后開始反向滑動 time.sleep(0.6) for back_track in tracks['back_tracks']: ActionChains(self.driver).move_by_offset(xoffset=back_track, yoffset=0).perform() # 小范圍震盪一下,進一步迷惑極驗后台,這一步可以極大地提高成功率 time.sleep(0.3) ActionChains(self.driver).move_by_offset(xoffset=3, yoffset=0).perform() # 先移動去一點 time.sleep(0.4) ActionChains(self.driver).move_by_offset(xoffset=-3, yoffset=0).perform() # 再退回來,模仿人的行為習慣 time.sleep(0.6) # 0.6秒后釋放鼠標 ActionChains(self.driver).release().perform() try: success = self.wait.until( EC.text_to_be_present_in_element((By.CLASS_NAME, 'geetest_success_radar_tip'), '驗證成功')) self.login() time.sleep(5) self.close_win() except: # 位置沒定位好,或者時間太長了,所以本次失敗,進入下一輪 self.fail_again() def fail_again(self): """ 失敗重試功能,加了好幾個try,防止報錯,使程序運行下去。在故意改變誤差值(偏移量)的情況下,目前只嘗試到40多次,遇到的報錯都位於步驟三,程序暫無問題 :return: """ # 步驟二:點擊刷新按鈕,彈出沒有缺口的圖片,其實貌似也許大概,無需手動點刷新,失敗后自動就刷新了,但我就這么寫了。。。 try: button = self.wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'geetest_refresh_1'))) button.click() except Exception as e: print e print '嘗試次數過多,刷新一次圖片-步驟二' button = self.wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'geetest_reset_tip_content'))) button.click() time.sleep(2) self.fail_again() # 步驟三:拿到沒有缺口的圖片 try: time.sleep(0.5) image1 = self.get_image('captcha1.png') except Exception as e: print e print '嘗試次數過多,刷新一次圖片-步驟三' button = self.wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'geetest_reset_tip_content'))) button.click() time.sleep(2) self.fail_again() # 步驟四:點擊托送按鈕,彈出有缺口的圖片 try: slider = self.get_slider() slider.click() except Exception as e: print e print '嘗試次數過多,刷新一次圖片-步驟四' button = self.wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'geetest_reset_tip_content'))) button.click() time.sleep(2) self.fail_again() # 步驟五:拿到有缺口的圖片 try: image2 = self.get_image('captcha2.png') except Exception as e: print e print '嘗試次數過多,刷新一次圖片-步驟五' button = self.wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'geetest_reset_tip_content'))) button.click() time.sleep(2) self.fail_again() # 步驟六:對比兩張圖片的所有RBG像素點,得到不一樣像素點間的差值,即要移動的距離 distance = self.get_distance(image1, image2) # 步驟七:模擬人的行為習慣(先軍加速拖動后勻減速拖動),把需要拖動的總距離分成一段一段小的軌跡 tracks = self.get_tracks(distance) # 步驟八:按照軌跡拖動,完全驗證 button = self.wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'geetest_slider_button'))) ActionChains(self.driver).click_and_hold(slider).perform() # 正常人類總是自信滿滿地開始正向滑動,自信地表現是瘋狂加速 for track in tracks['forward_tracks']: ActionChains(self.driver).move_by_offset(xoffset=track, yoffset=0).perform() # 結果傻逼了,正常的人類停頓了一下,回過神來發現,卧槽,滑過了,然后開始反向滑動 time.sleep(0.6) for back_track in tracks['back_tracks']: ActionChains(self.driver).move_by_offset(xoffset=back_track, yoffset=0).perform() # 小范圍震盪一下,進一步迷惑極驗后台,這一步可以極大地提高成功率 time.sleep(0.3) ActionChains(self.driver).move_by_offset(xoffset=-4, yoffset=0).perform() # 先移動去一點 time.sleep(0.4) ActionChains(self.driver).move_by_offset(xoffset=4, yoffset=0).perform() # 再退回來,模仿人的行為習慣 time.sleep(0.6) # 0.5秒后釋放鼠標 ActionChains(self.driver).release().perform() print self.count try: success = self.wait.until( EC.text_to_be_present_in_element((By.CLASS_NAME, 'geetest_success_radar_tip'), '驗證成功')) self.login() time.sleep(5) self.close_win() except: self.count+=1 if self.count<100: self.fail_again() else: self.close_win() if __name__ == '__main__': url = 'https://account.geetest.com/login' email = '馬賽克了嗎' password = '賽克了' threshold = 60 #RGB色差 left = 57 #起始位置 deviation = 7 #偏移量,誤差 crack = CrackGeetest(url, email, password, threshold, left, deviation) crack.crack()
我去,趕緊馬賽克賬號密碼,差點把自己暴露了