滑動宮格驗證碼都給碰上了?沒事兒,看完此文分分鐘拿下!

本節咱們將介紹新浪微博宮格驗證碼的識別。微博宮格驗證碼是一種新型交互式驗證碼,每一個宮格之間會有一條指示連線,指示了應該的滑動軌跡。咱們要按照滑動軌跡依次從起始宮格滑動到終止宮格,才能夠完成驗證,以下圖所示。
css

鼠標滑動後的軌跡會以黃色的連線來標識,以下圖所示。html

訪問新浪微博移動版登陸頁面,就能夠看到如上驗證碼,連接爲https://passport.weibo.cn/signin/login。不是每次登陸都會出現驗證碼,當頻繁登陸或者帳號存在安全風險的時候,驗證碼纔會出現。
git

1、本節目標

咱們的目標是用程序來識別並經過微博宮格驗證碼的驗證。
github

2、準備工做

本次咱們使用的Python庫是Selenium,使用的瀏覽器爲Chrome,請確保已經正確安裝好Selenium庫、Chrome瀏覽器,並配置好ChromeDriver。
web

3、識別思路

識別從探尋規律入手。規律就是,此驗證碼的四個宮格必定是有連線通過的,每一條連線上都會相應的指示箭頭,連線的形狀多樣,包括C型、Z型、X型等,以下圖所示。
算法



咱們發現,同一類型的連線軌跡是相同的,惟一不一樣的就是連線的方向,以下圖所示。瀏覽器


這兩種驗證碼的連線軌跡是相同的。可是因爲連線上面的指示箭頭不一樣,致使滑動的宮格順序有所不一樣。安全

若是要徹底識別滑動宮格順序,就須要具體識別出箭頭的朝向。而整個驗證碼箭頭朝向一共有8種,並且會出如今不一樣的位置。若是要寫一個箭頭方向識別算法,須要考慮不一樣箭頭所在的位置,找出各個位置箭頭的像素點座標,計算像素點變化規律,這個工做量就會變得比較大。bash

這時咱們能夠考慮用模板匹配的方法,就是將一些識別目標提早保存並作好標記,這稱做模板。這裏將驗證碼圖片作好拖動順序的標記當作模板。對比要新識別的目標和每個模板,若是找到匹配的模板,則就成功識別出要新識別的目標。在圖像識別中,模板匹配也是經常使用的方法,實現簡單且易用性好。微信

咱們必需要收集到足夠多的模板,模板匹配方法的效果纔會好。而對於微博宮格驗證碼來講,宮格只有4個,驗證碼的樣式最多4×3×2×1=24種,則咱們能夠將全部模板都收集下來。

接下來咱們須要考慮的就是,用何種模板來進行匹配,只匹配箭頭仍是匹配整個驗證碼全圖呢?咱們權衡一下這兩種方式的匹配精度和工做量。

  • 首先是精度問題。若是是匹配箭頭,比對的目標只有幾個像素點範圍的箭頭,咱們須要精確知道各個箭頭所在的像素點,一旦像素點有誤差,那麼會直接錯位,致使匹配結果大打折扣。若是是匹配全圖,咱們無需關心箭頭所在位置,同時還有連線幫助輔助匹配。顯然,全圖匹配的精度更高。

  • 其次是工做量的問題。若是是匹配箭頭,咱們須要保存全部不一樣朝向的箭頭模板,而相同位置箭頭的朝向可能不一,相同朝向的箭頭位置可能不一,那麼咱們須要算出每一個箭頭的位置並將其逐個截出保存成模板,依次探尋驗證碼對應位置是否有匹配模板。若是是匹配全圖,咱們不須要關心每一個箭頭的位置和朝向,只須要將驗證碼全圖保存下來便可,在匹配的時候也不須要計算箭頭的位置。顯然,匹配全圖的工做量更少。

綜上考慮,咱們選用全圖匹配的方式來進行識別。找到匹配的模板以後,咱們就能夠獲得事先爲模板定義的拖動順序,而後模擬拖動便可。

4、獲取模板

咱們須要作一下準備工做。先保存24張驗證碼全圖。由於驗證碼是隨機的,一共有24種。咱們能夠寫一段程序來批量保存驗證碼圖片,而後從中篩選出須要的圖片,代碼以下所示:

import time
from io import BytesIO
from PIL import Image
from selenium import webdriver
from selenium.common.exceptions import TimeoutException
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

USERNAME = ''
PASSWORD = ''

class CrackWeiboSlide():
    def __init__(self):
        self.url = 'https://passport.weibo.cn/signin/login'
        self.browser = webdriver.Chrome()
        self.wait = WebDriverWait(self.browser, 20)
        self.username = USERNAME
        self.password = PASSWORD

    def __del__(self):
        self.browser.close()

    def open(self):
        """ 打開網頁輸入用戶名密碼並點擊 :return: None """
        self.browser.get(self.url)
        username = self.wait.until(EC.presence_of_element_located((By.ID, 'loginName')))
        password = self.wait.until(EC.presence_of_element_located((By.ID, 'loginPassword')))
        submit = self.wait.until(EC.element_to_be_clickable((By.ID, 'loginAction')))
        username.send_keys(self.username)
        password.send_keys(self.password)
        submit.click()

    def get_position(self):
        """ 獲取驗證碼位置 :return: 驗證碼位置元組 """
        try:
            img = self.wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'patt-shadow')))
        except TimeoutException:
            print('未出現驗證碼')
            self.open()
        time.sleep(2)
        location = img.location
        size = img.size
        top, bottom, left, right = location['y'], location['y'] + size['height'], location['x'], location['x'] + size['width']
        return (top, bottom, left, right)

    def get_screenshot(self):
        """ 獲取網頁截圖 :return: 截圖對象 """
        screenshot = self.browser.get_screenshot_as_png()
        screenshot = Image.open(BytesIO(screenshot))
        return screenshot

    def get_image(self, name='captcha.png'):
        """ 獲取驗證碼圖片 :return: 圖片對象 """
        top, bottom, left, right = self.get_position()
        print('驗證碼位置', top, bottom, left, right)
        screenshot = self.get_screenshot()
        captcha = screenshot.crop((left, top, right, bottom))
        captcha.save(name)
        return captcha

    def main(self):
        """ 批量獲取驗證碼 :return: 圖片對象 """
        count = 0
        while True:
            self.open()
            self.get_image(str(count) + '.png')
            count += 1

if __name__ == '__main__':
    crack = CrackWeiboSlide()
    crack.main()複製代碼

這裏須要將USERNAMEPASSWORD修改成本身微博的用戶名和密碼。運行一段時間後,本地多了不少以數字命名的驗證碼,以下圖所示。

這裏咱們只須要挑選出不一樣的24張驗證碼圖片並命名保存。名稱能夠直接取做宮格的滑動的順序,以下圖所示。

咱們將圖片命名爲4132.png,表明滑動順序爲4-1-3-2。按照這樣的規則,咱們將驗證碼整理爲以下24張圖,以下圖所示。

如上24張圖就是咱們的模板。接下來,識別過程只須要遍歷模板進行匹配便可。

5、模板匹配

調用get_image()方法,獲得驗證碼圖片對象。而後,對驗證碼圖片對象進行模板匹配,定義以下所示的方法:

from os import listdir

def detect_image(self, image):
    """ 匹配圖片 :param image: 圖片 :return: 拖動順序 """
    for template_name in listdir(TEMPLATES_FOLDER):
        print('正在匹配', template_name)
        template = Image.open(TEMPLATES_FOLDER + template_name)
        if self.same_image(image, template):
            # 返回順序
            numbers = [int(number) for number in list(template_name.split('.')[0])]
            print('拖動順序', numbers)
            return numbers複製代碼

TEMPLATES_FOLDER就是模板所在的文件夾。這裏經過listdir()方法獲取全部模板的文件名稱,而後對其進行遍歷,經過same_image()方法對驗證碼和模板進行比對。若是匹配成功,那麼就將匹配到的模板文件名轉換爲列表。如模板文件3124.png匹配到了,則返回結果爲[3, 1, 2, 4]。

比對的方法實現以下所示:

def is_pixel_equal(self, image1, image2, x, y):
    """ 判斷兩個像素是否相同 :param image1: 圖片1 :param image2: 圖片2 :param x: 位置x :param y: 位置y :return: 像素是否相同 """
    # 取兩個圖片的像素點
    pixel1 = image1.load()[x, y]
    pixel2 = image2.load()[x, y]
    threshold = 20
    if abs(pixel1[0] - pixel2[0]) < threshold and abs(pixel1[1] - pixel2[1]) < threshold and abs(
            pixel1[2] - pixel2[2]) < threshold:
        return True
    else:
        return False

def same_image(self, image, template):
    """ 識別類似驗證碼 :param image: 待識別驗證碼 :param template: 模板 :return: """
    # 類似度閾值
    threshold = 0.99
    count = 0
    for x in range(image.width):
        for y in range(image.height):
            # 判斷像素是否相同
            if self.is_pixel_equal(image, template, x, y):
                count += 1
    result = float(count) / (image.width * image.height)
    if result > threshold:
        print('成功匹配')
        return True
    return False複製代碼

在這裏比對圖片也利用了遍歷像素的方法。same_image()方法接收兩個參數,image爲待檢測的驗證碼圖片對象,template是模板對象。因爲兩者大小是徹底一致的,因此在這裏咱們遍歷了圖片的全部像素點。比對兩者同一位置的像素點,若是像素點相同,計數就加1。最後計算相同的像素點佔總像素的比例。若是該比例超過必定閾值,那就斷定圖片徹底相同,則匹配成功。這裏閾值設定爲0.99,即若是兩者有0.99以上的類似比,則表明匹配成功。

經過上面的方法,依次匹配24個模板。若是驗證碼圖片正常,咱們總能找到一個匹配的模板,這樣就能夠獲得宮格的滑動順序了。

6、模擬拖動

接下來,根據滑動順序拖動鼠標,鏈接各個宮格,方法實現以下所示:

def move(self, numbers):
    """ 根據順序拖動 :param numbers: :return: """
    # 得到四個按點
    circles = self.browser.find_elements_by_css_selector('.patt-wrap .patt-circ')
    dx = dy = 0
    for index in range(4):
        circle = circles[numbers[index] - 1]
        # 若是是第一次循環
        if index == 0:
            # 點擊第一個按點
            ActionChains(self.browser) \
                .move_to_element_with_offset(circle, circle.size['width'] / 2, circle.size['height'] / 2) \
                .click_and_hold().perform()
        else:
            # 小幅移動次數
            times = 30
            # 拖動
            for i in range(times):
                ActionChains(self.browser).move_by_offset(dx / times, dy / times).perform()
                time.sleep(1 / times)
        # 若是是最後一次循環
        if index == 3:
            # 鬆開鼠標
            ActionChains(self.browser).release().perform()
        else:
            # 計算下一次偏移
            dx = circles[numbers[index + 1] - 1].location['x'] - circle.location['x']
            dy = circles[numbers[index + 1] - 1].location['y'] - circle.location['y']複製代碼

這裏方法接收的參數就是宮格的點按順序,如[3,1,2,4]。首先咱們利用find_elements_by_css_selector()方法獲取到4個宮格元素,它是一個列表形式,每一個元素表明一個宮格。接下來遍歷宮格的點按順序,作一系列對應操做。

其中若是當前遍歷的是第一個宮格,那就直接鼠標點擊並保持動做,不然移動到下一個宮格。若是當前遍歷的是最後一個宮格,那就鬆開鼠標,若是不是最後一個宮格,則計算移動到下一個宮格的偏移量。

經過4次循環,咱們即可以成功操做瀏覽器完成宮格驗證碼的拖拽填充,鬆開鼠標以後便可識別成功。運行效果以下圖所示。

鼠標會慢慢從起始位置移動到終止位置。最後一個宮格鬆開以後,驗證碼的識別便完成了。

至此,微博宮格驗證碼的識別就所有完成。驗證碼窗口會自動關閉。直接點擊登陸按鈕便可登陸微博。

7、本節代碼

本節代碼地址爲:https://github.com/Python3WebSpider/CrackWeiboSlide。

8、結語

本節介紹了一種經常使用的模板匹配識別圖片的方式,模擬了鼠標拖拽動做來實現驗證碼的識別。若是遇到相似的驗證碼,咱們能夠採用一樣的思路進行識別。


本資源首發於崔慶才的我的博客靜覓: Python3網絡爬蟲開發實戰教程 | 靜覓

如想了解更多爬蟲資訊,請關注個人我的微信公衆號:進擊的Coder

weixin.qq.com/r/5zsjOyvEZ… (二維碼自動識別)

相關文章
相關標籤/搜索