1. 程式人生 > >破解滑動驗證碼(selenium, opencv)

破解滑動驗證碼(selenium, opencv)

概述

由於本人近期參加一個遊戲比賽,然後有個拉票的環節,票高者得人氣獎。又比較懶不想到處拉票麻煩別人。就想自己嘗試著破解驗證碼然後來達到刷票的目的。

這個也純屬娛樂,最後發現是不可行的。

最終目標:實現自動點選紅星,並且拖動完成驗證

這裡寫圖片描述

自動點選並且獲取圖片

自動模擬的過程通過 selenium 實現。
selenium安裝過程自己上網找,很簡單,還需要安裝一個webdriver

class WangYi(object):
    def __init__(self):
        self.browser = webdriver.Edge()
        self.back_img = None
        self.cut_img = None
        self.scaling_ratio = 1.0


    def visit(self, url):
        self.browser.get(url)
        WebDriverWait(self.browser, 10, 0.5).until(EC.element_to_be_clickable((By.CLASS_NAME, 'big-heart')))
        time.sleep(2)
        self.browser.find_element_by_class_name("big-heart").click() 

    def get_image(self):
        # 等待載入       
        WebDriverWait(self.browser, 10, 0.5).until(EC.visibility_of_element_located((By.CLASS_NAME, 'yidun_bgimg')))
        back_url= self.browser.find_element_by_class_name("yidun_bg-img").get_attribute('src')
        cut_url = self.browser.find_element_by_class_name("yidun_jigsaw").get_attribute('src')
        # 從url獲取圖片並儲存到本地
        resq = requests.get(back_url)
        file = BytesIO(resq.content)
        back_img = Image.open(file)
        back_img.save("back_img.jpg")
        resq = requests.get(cut_url)
        file = BytesIO(resq.content)
        cut_img = Image.open(file)
        cut_img.save("cut_img.png")
        # opencv讀取圖片
        self.back_img = cv2.imread("back_img.jpg")
        self.cut_img = cv2.imread("cut_img.png")
        self.scaling_ratio = self.browser.find_element_by_class_name("yidun_bg-img").size['width'] / back_width
        return self.cut_img, self.back_img 

邊緣檢測計算滑動距離

計算滑動距離分為以下幾步:

  • 利用opencv庫中提供的邊界查詢函式(cv2.findContours)提取單片拼圖邊緣軌跡並構造成一個二維矩陣(運算元)
  • 利用 高斯模糊運算元( cv2.GaussianBlur )和 Canny 邊緣檢測運算元( cv2.Canny )對背景圖進行處理,凸顯出拼圖在圖片中的邊緣
  • 用拼圖軌跡運算元在處理後的背景圖上進行 互相關操作,所得最大(小)值的位置就是拼圖在背景圖中的座標

還需要注意的就是,在影象中計算出來的距離還需要根據圖片與實際web中的比例進行scaling

def get_distance(self):
    back_canny = get_back_canny(self.back_img)
    operator = get_operator(self.cut_img)
    pos_x, max_value = best_match(back_canny, operator)
    distance = pos_x * self.scaling_ratio
    return distance

def read_img_file(cut_dir, back_dir):
    cut_image = cv2.imread(cut_dir)
    back_image = cv2.imread(back_dir)
    return cut_image, back_image

def best_match(back_canny, operator):
    max_value, pos_x = 0, 0
    for x in range(cut_width, back_width - cut_width):
        block = back_canny[:, x:x + cut_width]
        value = (block * operator).sum()
        if value > max_value:
            max_value = value
            pos_x = x
    return pos_x, max_value

def get_back_canny(back_img):
    img_blur = cv2.GaussianBlur(back_img, (3, 3), 0)
    img_gray = cv2.cvtColor(img_blur, cv2.COLOR_BGR2GRAY)
    img_canny = cv2.Canny(img_gray, 100, 200)
    return img_canny

def get_operator(cut_img):

    cut_gray = cv2.cvtColor(cut_img, cv2.COLOR_BGR2GRAY)

    _, cut_binary = cv2.threshold(cut_gray, 127, 255, cv2.THRESH_BINARY)
    # 獲取邊界
    _, contours, hierarchy = cv2.findContours(cut_binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
    # 獲取最外層邊界
    contour = contours[-1]
    # operator矩陣
    operator = np.zeros((cut_height, cut_width))
    # 根據 contour填寫operator
    for point in contour:
        operator[point[0][1]][point[0][0]] = 1
    return operator

模擬人類滑動過程

其實這一步才是最難的,因為本人針對的是網易雲盾的滑動驗證碼,滑動驗證碼會在拖拽完成之後,將互動資料發到雲端。然後雲端再通過機器學習反欺詐演算法來檢驗這個互動過程是機器完成的還是人完成的。所以需要在程式碼中加入很多隨機因子

def auto_drag(self, distance):
    element = self.browser.find_element_by_class_name("yidun_slider")

    # 這裡就是根據移動進行除錯,計算出來的位置不是百分百正確的,加上一點偏移
    #distance -= element.size.get('width') / 2
    distance += 13
    has_gone_dist = 0
    remaining_dist = distance
    #distance += randint(-10, 10)

    # 按下滑鼠左鍵
    ActionChains(self.browser).click_and_hold(element).perform()
    time.sleep(0.5)
    while remaining_dist > 0:
        ratio = remaining_dist / distance
        if ratio < 0.2:
            # 開始階段移動較慢
            span = random.randint(5, 8)
        elif ratio > 0.8:
            # 結束階段移動較慢
            span = random.randint(5, 8)
        else:
            # 中間部分移動快
            span = random.randint(10, 16)
        ActionChains(self.browser).move_by_offset(span, random.randint(-5, 5)).perform()
        remaining_dist -= span
        has_gone_dist += span
        time.sleep(random.randint(5,20)/100)

    ActionChains(self.browser).move_by_offset(remaining_dist, random.randint(-5, 5)).perform()
    ActionChains(self.browser).release(on_element=element).perform()

總結