1. 程式人生 > >python 3.X 使用selenium破解通用一二代滑塊驗證(有原圖的),以虎X網為列子(圖片畫素對比)

python 3.X 使用selenium破解通用一二代滑塊驗證(有原圖的),以虎X網為列子(圖片畫素對比)

前幾天看到有位大大寫的破解極驗證碼,也就是二十滑塊驗證滑塊驗證。
本偏文章主要借鑑了其中的圖片畫素對比的方法,在原基礎上盡心的修改創作。讓本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:喜歡的點個贊呦