使用opencv破解滑塊驗證碼:以今日頭條PC端登錄頁面滑塊驗證碼爲例

       本文目標人羣:python爬蟲工程師

       一、首先看看破解的效果圖

     效果一

      效果二

      效果三

      效果四

       二、滑塊驗證碼的破解

       滑塊驗證碼的破解的難點主要有兩個:計算出滑塊到缺口的距離和模擬人拖動滑塊的軌跡。

       如何計算出滑塊到缺口的距離?從網上的資料來看,主要有兩種方式:自己使用Pillow庫實現算法,使用OpenCV庫提供的現成方法。本文就使用後者,簡單而又強大、成功率高。

      本文主要參考https://juejin.im/post/5cf4cbd4f265da1b8e7089b4,但是由於它的註釋太少,也未給出完整代碼。所以,決定寫這篇博客。

      三、什麼是OpenCV?什麼是opencv-python?

      OpenCV(Open Source Computer Vision Library)是一個開源的計算機視覺庫,提供了很多處理圖片、視頻的方法。雖然是C/C++開發的,但是提供了 Python、Java、MATLAB 等其他語言的接口。

      OpenCV-python庫就是使用pthon操作OpenCV的一個庫。

      四、爲什麼要用OpenCV-python庫?

      說白了,就是OpenCV庫提供了一個方法(matchTemplate()):從一張較大的圖片中搜索一張較小圖片,計算出這張大圖上各個區域和小圖相似度。調用這個方法後返回一個二維數組(numpy庫中ndarray對象),從中就能拿到最佳匹配區域的座標。換到滑塊驗證碼上面,滑塊背景圖片是大圖,滑塊是小圖。

      opencv-python參見官網:https://docs.opencv.org/4.0.0/index.html

     cv.matchTemplate()方法參見:

     https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_imgproc/py_template_matchin/py_template_matching.html

    今日頭條PC端登錄頁面: https://sso.toutiao.com/

    五、環境

    首先,selenium、谷歌瀏覽器、谷歌瀏覽器驅動等環境是必須的。

    安裝OpenCV-python庫

    pip install  opencv-python

    或者使用阿里源:

    pip install -i http://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com opencv-python

    六、代碼

   源碼也可以在我的github找到https://github.com/chushiyan/slide_captcha_cracking

# create by chushiyan
# date: 20190926
# email: Y2h1c2hpeWFuMDQxNUAxNjMuY29t(base64)

import cv2 as cv
from selenium import webdriver
from selenium.common.exceptions import StaleElementReferenceException
from selenium.webdriver import ActionChains
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions
import time
import urllib.request
import random


#  傳入滑塊背景圖片本地路徑和滑塊本地路徑,返回滑塊到缺口的距離
def findPic(img_bg_path, img_slider_path):
    """
    找出圖像中最佳匹配位置
    :param img_bg_path: 滑塊背景圖本地路徑
    :param img_slider_path: 滑塊圖片本地路徑
    :return: 返回最差匹配、最佳匹配對應的x座標
    """

    # 讀取滑塊背景圖片,參數是圖片路徑,OpenCV默認使用BGR模式
    # cv.imread()是 image read的簡寫
    # img_bg 是一個numpy庫ndarray數組對象
    img_bg = cv.imread(img_bg_path)

    # 對滑塊背景圖片進行處理,由BGR模式轉爲gray模式(即灰度模式,也就是黑白圖片)
    # 爲什麼要處理? BGR模式(彩色圖片)的數據比黑白圖片的數據大,處理後可以加快算法的計算
    # BGR模式:常見的是RGB模式
    # R代表紅,red; G代表綠,green;  B代表藍,blue。
    # RGB模式就是,色彩數據模式,R在高位,G在中間,B在低位。BGR正好相反。
    # 如紅色:RGB模式是(255,0,0),BGR模式是(0,0,255)
    img_bg_gray = cv.cvtColor(img_bg, cv.COLOR_BGR2GRAY)

    # 讀取滑塊,參數1是圖片路徑,參數2是使用灰度模式
    img_slider_gray = cv.imread(img_slider_path, 0)

    # 在滑塊背景圖中匹配滑塊。參數cv.TM_CCOEFF_NORMED是opencv中的一種算法
    res = cv.matchTemplate(img_bg_gray, img_slider_gray, cv.TM_CCOEFF_NORMED)

    print('#' * 50)
    print(type(res))  # 打印:<class 'numpy.ndarray'>
    print(res)
    # 打印:一個二維的ndarray數組
    # [[0.05604218  0.05557462  0.06844381... - 0.1784117 - 0.1811338 - 0.18415523]
    #  [0.06151756  0.04408009  0.07010461... - 0.18493137 - 0.18440475 - 0.1843424]
    # [0.0643926    0.06221284  0.0719175... - 0.18742703 - 0.18535161 - 0.1823346]
    # ...
    # [-0.07755355 - 0.08177952 - 0.08642308... - 0.16476074 - 0.16210903 - 0.15467581]
    # [-0.06975575 - 0.07566144 - 0.07783117... - 0.1412715 - 0.15145643 - 0.14800543]
    # [-0.08476129 - 0.08415948 - 0.0949327... - 0.1371379 - 0.14271489 - 0.14166716]]

    print('#' * 50)

    # cv2.minMaxLoc() 從ndarray數組中找到最小值、最大值及他們的座標
    value = cv.minMaxLoc(res)
    # 得到的value,如:(-0.1653602570295334, 0.6102921366691589, (144, 1), (141, 56))

    print(value, "#" * 30)

    # 獲取x座標,如上面的144、141
    return value[2:][0][0], value[2:][1][0]


# 返回兩個數組:一個用於加速拖動滑塊,一個用於減速拖動滑塊
def generate_tracks(distance):
    # 給距離加上20,這20像素用在滑塊滑過缺口後,減速折返回到缺口
    distance += 20
    v = 0
    t = 0.2
    forward_tracks = []
    current = 0
    mid = distance * 3 / 5  # 減速閥值
    while current < distance:
        if current < mid:
            a = 2  # 加速度爲+2
        else:
            a = -3  # 加速度-3
        s = v * t + 0.5 * a * (t ** 2)
        v = v + a * t
        current += s
        forward_tracks.append(round(s))

    back_tracks = [-3, -3, -2, -2, -2, -2, -2, -1, -1, -1, -1]
    return forward_tracks, back_tracks


def main():
    # 創建一個參數對象,用來控制谷歌瀏覽器以無界面模式打開
    chrome_options = Options()
    # chrome_options.add_argument('--headless')
    # chrome_options.add_argument('--disable-gpu')

    driver = webdriver.Chrome(chrome_options=chrome_options)

    # 設置瀏覽器寬高
    driver.set_window_size(width=2000, height=3000)

    wait = WebDriverWait(driver, 10)

    driver.get("https://sso.toutiao.com/")

    try:
        # 獲取手機號碼輸入框
        text_box = wait.until(expected_conditions.presence_of_element_located((By.ID, "user-mobile")))
        print(text_box)

        # 獲取發送驗證按鈕
        send_btn = wait.until(expected_conditions.presence_of_element_located((By.ID, "mobile-code-get")))
        print(send_btn)

    except Exception as e:
        print(e)
        raise RuntimeError("獲取 手機號碼輸入框 或者 發送驗證按鈕 失敗,程序結束")

    text_box.send_keys("18812345678")

    # time.sleep(random.uniform(2, 3))

    try:
        action = ActionChains(driver)
        action.click(send_btn).perform()
    except Exception as e:
        print(e)
    else:
        time.sleep(1.5)

        # 獲取 滑塊背景圖
        bg_image = wait.until(
            expected_conditions.presence_of_element_located((By.XPATH, '//div[@class="validate-main"]/img[1]')))
        print("\n背景:")

        print(bg_image)
        bg_image_url = bg_image.get_attribute('src')

        print(bg_image_url)

        # 使用urllib下載背景圖
        # 原因是:使用bg_image.screenshot()程序卡在這裏,也不報錯
        urllib.request.urlretrieve(bg_image_url, "./img_bg.png")

        # 獲取 滑塊
        slider = wait.until(
            expected_conditions.presence_of_element_located((By.XPATH, '//div[@class="validate-main"]/img[2]')))

        print("\n滑塊:")
        print(slider)

        # 注意:千萬不能通過截圖獲取滑塊,因爲滑塊不是規則的圖形
        # 而截圖截出的是矩形,會把滑塊周圍的滑塊背景圖一起截取,勢必影響匹配
        # slider.screenshot('./img_slider.png')
        slider_url = slider.get_attribute('src')

        urllib.request.urlretrieve(slider_url, "./img_slider.png")

        value_1, value_2 = findPic('./img_bg.png', './img_slider.png')

        print("#" * 30)
        print("最差匹配和最佳匹配對應的x座標分別是:")
        print(value_1)
        print(value_2)
        print("#" * 30)

        # 獲取滑動按鈕
        button = wait.until(
            expected_conditions.presence_of_element_located((By.XPATH, '//div[@class="validate-drag-button"]/img')))

        action = ActionChains(driver)
        try:
            action.click_and_hold(button).perform()
        except StaleElementReferenceException as e:
            print(e)

        action.reset_actions()

        forward_tracks, back_tracks = generate_tracks(value_2)

        for x in forward_tracks:
            action.move_by_offset(x, 0)  # 前進移動滑塊
            print(x)

        print('#' * 50)

        for x in back_tracks:
            action.move_by_offset(x, 0)  # 後退移動滑塊
            print(x)

        action.release().perform()


    finally:
        # time.sleep(10000)
        driver.close()


if __name__ == '__main__':
    main()

本文僅作爲技術交流,如有侵權請聯繫我。

郵箱:Y2h1c2hpeWFuMDQxNUAxNjMuY29t(base64編碼)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章