python 3.X 使用selenium破解通用一二代滑塊驗證(有原圖的),以虎X網為列子(圖片畫素對比)
阿新 • • 發佈:2019-02-16
前幾天看到有位大大寫的破解極驗證碼,也就是二十滑塊驗證滑塊驗證。
本偏文章主要借鑑了其中的圖片畫素對比的方法,在原基礎上盡心的修改創作。讓本demo更適用於多個網站和一二代的滑塊驗證。
還有一個原因是原作者的demo我跑不通,邏輯出現了bug。
原作者地址:https://mp.weixin.qq.com/s/_SKphxxGg7Plgv9iG_LOkw
程式碼中我會詳細的解釋每一步的作用,歡迎借鑑。
裡面的可借用的東西還是很多的。
更新: 2018-9-28
1.優化了移動軌跡
下面是程式碼demo:
from selenium import webdriver from selenium.webdriver.support.wait import WebDriverWait # from selenium.webdriver.support import expected_conditions as EC import time from selenium.common.exceptions import TimeoutException from selenium.webdriver.common.by import By from selenium.webdriver.common.action_chains import ActionChains from bs4 import BeautifulSoup import re from PIL import Image, ImageChops from io import BytesIO import random from bs4 import BeautifulSoup class HuXiu(object): def __init__(self): #--------設定谷歌瀏覽器屬性---------- chrome_option = webdriver.ChromeOptions() self.driver = webdriver.Chrome( executable_path=r"C:/Users/Administrator/AppData/Local/Google/Chrome/Application/chromedriver.exe", chrome_options=chrome_option) self.driver.set_window_size(1440, 900) #--------設定谷歌瀏覽器屬性---------- def visit_index(self): #使用瀏覽器開啟指定連結 self.driver.get("https://www.huxiu.com/") #WebDriverWiat( #driver:檢視的頁面 #timeOut:最大等待時間 #poll_frequency:呼叫until或until_not的頻率,預設是0.5) #EC就是expected_conditions,判斷通過xpath能否獲取到指定path下的按鈕有沒 #有出現,如果出現了,那麼WebDriverWait就能接收到until傳回的True,程式則會繼續執行 #這個地方是判斷頁面有沒有加載出註冊按鈕 WebDriverWait(self.driver, 10, 0.5).until(EC.element_to_be_clickable((By.XPATH, '//*[@class="js-register"]'))) #獲取註冊按鈕的element reg_element = self.driver.find_element_by_xpath('//*[@class="js-register"]') #觸發點選事件,彈出註冊框 reg_element.click() #這個與上面同理,等待註冊框載入完成 WebDriverWait(self.driver, 10, 0.5).until( EC.element_to_be_clickable((By.XPATH, '//div[@class="gt_slider_knob gt_show"]'))) # 進入模擬拖動流程 self.analog_drag() def analog_drag(self): # 獲取拖動按鈕的element element = self.driver.find_element_by_xpath('//div[@class="gt_slider_knob gt_show"]') #print('移動到滑塊,獲取完整圖片') #ActionChains()是selenium中關於底層自動互動的一個方法,可以根據提供的方法實現自動的點選、拖拽、移動等事件 ActionChains(self.driver).move_to_element(element).perform() #防止網速出現異常的等待時間,讓圖片加載出來 time.sleep(3) #----重新整理驗證圖片----- #這一步是在第一次驗證失敗後,會直接出現只有缺口的圖片,無法進行完整圖片的截圖,因此進行了一次重新整理動作 element_refresh = self.driver.find_element_by_xpath('//a[@class="gt_refresh_button"]') element_refresh.click() time.sleep(1) #----重新整理驗證圖片------ #完整圖儲存地址,最好設定成png格式,jpg設定會報錯,原因下面會說明 fullImg_Path = 'I:/study_tz/venv/Slider_validation/full.png' #缺口圖片儲存地址 cutImg_Path = 'I:/study_tz/venv/Slider_validation/cut.png' #不相同,也就是說缺口位置儲存地址 diff_path = 'I:/study_tz/venv/Slider_validation/dif.png' #獲取完整圖片,呼叫get_img(自寫)的方法 full = self.get_img(self.driver, 'class', 'gt_box', fullImg_Path) #('點住不放,獲取缺口圖片') ActionChains(self.driver).click_and_hold(element).perform() #獲取帶缺口圖片 cut = self.get_img(self.driver, 'class', 'gt_box', cutImg_Path) # 根據兩個圖片計算距離,需要傳入二進位制資料 distance = self.get_offset_distance(cut, full) #執行滑塊移動 self.start_move(element, distance) # 判斷是否驗證成功 #每個網站的判定方式不同,需要自行查詢每次驗證後的提示成功與否的地方 #在虎X中,有個div會根據結果顯示success或者fail,通過BeautifulSoup來獲取該元素,如果為None,說明能獲取到元素,即成功 #該判斷結構可以根據實際情況進行修改,模式不限。 soup = BeautifulSoup(self.driver.page_source,'lxml') success = soup.find('div',{'class':'gt_ajax_tip gt_success'}) if success is None: #這個部分是原作者的判斷,他考慮到或許因為網速等原因,返回值的時候會有延遲,因此使用的事webDriverWait來獲取的 try: WebDriverWait(self.driver, 10, 0.5).until( EC.element_to_be_clickable((By.XPATH, '//div[@class="gt_ajax_tip gt_success"]'))) except TimeoutException: print("again times") time.sleep(5) # 失敗後遞迴執行拖動 self.analog_drag() else: # 成功後輸入手機號,傳送驗證碼 self.register() else: time.sleep(3) #失敗後遞迴執行拖動 self.analog_drag() # self.driver.close() # 開始移動 def start_move(self, element, distance): #這裡加上52,理論上應該是+60,因為截圖的時候把x+60了嘛 #但現在變成+52,是因為在實際中,滑塊和缺口塊的位置並不相同,存在一定的位置,所以需要使用的時候,調整下 distance += 52 # 按下滑鼠左鍵 ActionChains(self.driver).click_and_hold(element).perform() time.sleep(0.5) track_list=get_track(distances+3) time.sleep(2) ActionChains(driver).click_and_hold(element).perform() time.sleep(0.2) # 根據軌跡拖拽圓球 for track in track_list: ActionChains(self.driver).move_by_offset(xoffset=track,yoffset=0).perform() # 模擬人工滑動超過缺口位置返回至缺口的情況,資料來源於人工滑動軌跡,同時還加入了隨機數,都是為了更貼近人工滑動軌跡 imitate=ActionChains(driver).move_by_offset(xoffset=-1, yoffset=0) time.sleep(0.015) imitate.perform() time.sleep(random.randint(6,10)/10) imitate.perform() time.sleep(0.04) imitate.perform() time.sleep(0.012) imitate.perform() time.sleep(0.019) imitate.perform() time.sleep(0.033) ActionChains(self.driver).move_by_offset(xoffset=1, yoffset=0).perform() # 放開圓球 ActionChains(self.driver).pause(random.randint(6,14)/10).release(element).perform() # 判斷顏色是否相近 def is_similar_color(self, x_pixel, y_pixel): for i, pixel in enumerate(x_pixel): #當返回True時,說明兩點是相同的,返回False,說明找到了不同點,也就是找到了缺口元素所在的地方 50可根據情況做調整,主要是圖片中會出現略微帶點陰影的缺口值 if abs(y_pixel[i] - pixel) > 50: return False return True #滑塊移動軌跡 def get_track(distance): track=[] current=0 mid=distance*3/4 t=random.randint(2,3)/10 v=0 while current<distance: if current<mid: a=2 else: a=-3 v0=v v=v0+a*t move=v0*t+1/2*a*t*t current+=move track.append(round(move)) return track #計算缺口座標,獲取x,也就是說第一個符合閾值的點的橫座標,也就是距離邊框的距離 #做兩個for迴圈,分別獲取兩張圖對應座標的畫素 def get_offset_distance(self, cut_image, full_image): for x in range(cut_image.width): for y in range(cut_image.height): cpx = cut_image.getpixel((x, y)) fpx = full_image.getpixel((x, y)) #呼叫is_similar_color方法作對比 if not self.is_similar_color(cpx, fpx): #這個截圖只是為了看下是不是擷取位置正確,可以捨棄的 img = cut_image.crop((x, y, x + 50, y + 40)) img.save("I:/study_tz/venv/Slider_validation/dif.png") return x #這一步就是通過定位元素的位置,然後獲取該元素的大小,來計算截圖位置, def get_img(self, driver, element_type, element_path, img_path): #獲取想要擷取截圖的元素 element = driver.find_element_by_xpath('//div[@%s="%s"]' % (element_type, element_path)) #將整個頁面轉成二進位制資料,臨時儲存在記憶體裡 #這裡要說下,在寫這個方法的時候,selenium提供了一個screenshot的方法可以直接將某個元素變成截圖,可是在使用Charm作為開啟的瀏覽器,就會報錯‘未知方法’。 #然後寄出然了百度,無果,都以一個問題。後在查閱外網的使用者使用經驗,有外國大佬總結了,這個方法好像僅僅支援火狐的瀏覽器,也就是說谷歌不行,因此就只能用笨辦法,自己寫了。 png = driver.get_screenshot_as_png() im = Image.open(BytesIO(png)) #獲取元素的大小,返回的是字典格式{‘width’:值,‘height’:值} size = element.size #獲取元素的座標,返回的是字典格式{‘w’:值,‘y’:值} location = element.location #這個地方+60,是因為擷取缺口圖的時候,缺口的那個滑塊會被擷取進去(那個滑塊有陰影的,與缺口處陰影值相同),影響畫素對比的結果,因此將這個部分去掉,可以自行調整 left = location['x'] + 60 top = location['y'] right = location['x'] + size['width'] bottom = location['y'] + size['height'] # print(left, top, right, bottom) #使用crop方法,傳入四個值,截圖 im = im.crop((left, top, right, bottom)) im.save(img_path) return im #這一步是輸入手機號註冊,獲取驗證碼的,很簡單,就不詳細敘述了。 def register(self): element = self.driver.find_element_by_xpath('//input[@id="sms_username"]') element.clear() element.send_keys("你的手機號") ele_captcha = self.driver.find_element_by_xpath('//span[@class="js-btn-captcha btn-captcha"]') ele_captcha.click() if __name__ == "__main__": hx = HuXiu() hx.visit_index()
下面是一些截圖:
full_img:
cut_img:
dif_img:
驗證圖:
驗證碼:
我這個demo在該網站上,從寫好後,正確率在99%。。沒錯過,很尷尬,可能在一些異常丟擲錯誤上還有漏洞。如果你借鑑使用後出現這方面你的問題了。歡迎留言。
希望這篇文章對你有所幫助,咱們下次見。
ps:喜歡的點個贊呦