溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Python3爬蟲里如何實(shí)現(xiàn)識(shí)別微博宮格驗(yàn)證碼

發(fā)布時(shí)間:2020-07-31 09:15:38 來(lái)源:億速云 閱讀:181 作者:小豬 欄目:開發(fā)技術(shù)

這篇文章主要為大家展示了Python3爬蟲里如何實(shí)現(xiàn)識(shí)別微博宮格驗(yàn)證碼,內(nèi)容簡(jiǎn)而易懂,希望大家可以學(xué)習(xí)一下,學(xué)習(xí)完之后肯定會(huì)有收獲的,下面讓小編帶大家一起來(lái)看看吧。

本節(jié)我們來(lái)介紹一下新浪微博宮格驗(yàn)證碼的識(shí)別,此驗(yàn)證碼是一種新型交互式驗(yàn)證碼,每個(gè)宮格之間會(huì)有一條指示連線,指示了我們應(yīng)該的滑動(dòng)軌跡,我們需要按照滑動(dòng)軌跡依次從起始宮格一直滑動(dòng)到終止宮格才可以完成驗(yàn)證,如圖所示:

Python3爬蟲里如何實(shí)現(xiàn)識(shí)別微博宮格驗(yàn)證碼

鼠標(biāo)滑動(dòng)后的軌跡會(huì)以黃色的連線來(lái)標(biāo)識(shí),如圖所示:

Python3爬蟲里如何實(shí)現(xiàn)識(shí)別微博宮格驗(yàn)證碼

我們可以訪問新浪微博移動(dòng)版登錄頁(yè)面就可以看到如上驗(yàn)證碼,鏈接為:https://passport.weibo.cn/signin/login,當(dāng)然也不是每次都會(huì)出現(xiàn)驗(yàn)證碼,一般當(dāng)頻繁登錄或者賬號(hào)存在安全風(fēng)險(xiǎn)的時(shí)候會(huì)出現(xiàn)。

接下來(lái)我們就來(lái)試著識(shí)別一下此類驗(yàn)證碼。

1. 本節(jié)目標(biāo)

本節(jié)我們的目標(biāo)是用程序來(lái)識(shí)別并通過微博宮格驗(yàn)證碼的驗(yàn)證。

2. 準(zhǔn)備工作

本次我們使用的 Python 庫(kù)是 Selenium,使用的瀏覽器為 Chrome,在此之前請(qǐng)確保已經(jīng)正確安裝好了 Selenium 庫(kù)、Chrome瀏覽器并配置好了 ChromeDriver,相關(guān)流程可以參考第一章的說明。

3. 識(shí)別思路

要識(shí)別首先要從探尋規(guī)律入手,那么首先我們找到的規(guī)律就是此驗(yàn)證碼的四個(gè)宮格一定是有連線經(jīng)過的,而且每一條連線上都會(huì)相應(yīng)的指示箭頭,連線的形狀多樣,如C型、Z型、X型等等,如圖 8-26、8-27、8-28 所示:

Python3爬蟲里如何實(shí)現(xiàn)識(shí)別微博宮格驗(yàn)證碼

C 型

Python3爬蟲里如何實(shí)現(xiàn)識(shí)別微博宮格驗(yàn)證碼

Z 型

Python3爬蟲里如何實(shí)現(xiàn)識(shí)別微博宮格驗(yàn)證碼

X 型

而同時(shí)我們發(fā)現(xiàn)同一種類型它的連線軌跡是相同的,唯一不同的就是連線的方向,如圖所示:

Python3爬蟲里如何實(shí)現(xiàn)識(shí)別微博宮格驗(yàn)證碼

反向連線

Python3爬蟲里如何實(shí)現(xiàn)識(shí)別微博宮格驗(yàn)證碼

正向連線

這兩種驗(yàn)證碼的連線軌跡是相同的,但是由于連線上面的指示箭頭不同導(dǎo)致滑動(dòng)的宮格順序就有所不同。

所以要完全識(shí)別滑動(dòng)宮格順序的話就需要具體識(shí)別出箭頭的朝向,而觀察一下整個(gè)驗(yàn)證碼箭頭朝向一共可能有 8 種,而且會(huì)出現(xiàn)在不同的位置,如果要寫一個(gè)箭頭方向識(shí)別算法的話需要都考慮到不同箭頭所在的位置,我們需要找出各個(gè)位置的箭頭的像素點(diǎn)坐標(biāo),同時(shí)識(shí)別算法還需要計(jì)算其像素點(diǎn)變化規(guī)律,這個(gè)工作量就變得比較大。

這時(shí)我們可以考慮用模板匹配的方法,模板匹配的意思就是將一些識(shí)別目標(biāo)提前保存下來(lái)并做好標(biāo)記,稱作模板,在這里我們就可以獲取驗(yàn)證碼圖片并做好拖動(dòng)順序的標(biāo)記當(dāng)做模板。在匹配的時(shí)候來(lái)對(duì)比要新識(shí)別的目標(biāo)和每一個(gè)模板哪個(gè)是匹配的,如果找到匹配的模板,則被匹配到的模板就和新識(shí)別的目標(biāo)是相同的,這樣就成功識(shí)別出了要新識(shí)別的目標(biāo)了。模板匹配在圖像識(shí)別中也是非常常用的一種方法,實(shí)現(xiàn)簡(jiǎn)單而且易用性好。

模板匹配方法如果要效果好的話,我們必須要收集到足夠多的模板才可以,而對(duì)于微博宮格驗(yàn)證碼來(lái)說,宮格就 4 個(gè),驗(yàn)證碼的樣式最多就是 4 3 2 * 1 = 24種,所以我們可以直接將所有模板都收集下來(lái)。

所以接下來(lái)我們需要考慮的就是用何種模板來(lái)進(jìn)行匹配,是只匹配箭頭還是匹配整個(gè)驗(yàn)證碼全圖呢?我們來(lái)權(quán)衡一下這兩種方式的匹配精度和工作量:

首先是精度問題。如果要匹配箭頭的話,我們比對(duì)的目標(biāo)只有幾個(gè)像素點(diǎn)范圍的箭頭,而且我們需要精確知道各個(gè)箭頭所在的像素點(diǎn),一旦像素點(diǎn)有所偏差,那么匹配模板的時(shí)候會(huì)直接錯(cuò)位,導(dǎo)致匹配結(jié)果大打折扣。如果匹配全圖,我們無(wú)需關(guān)心箭頭所在位置,同時(shí)還有連線幫助輔助匹配,所以匹配精度上顯然是全圖匹配精度更高。

其次是工作量的問題。如果要匹配箭頭的話,我們需要將所有不同朝向的箭頭模板都保存下來(lái),而相同位置箭頭的朝向可能不一,相同朝向的箭頭位置可能不一,這時(shí)候我們需要都算出各個(gè)箭頭的位置并將其逐個(gè)截出來(lái)保存成模板,同時(shí)在匹配的時(shí)候也需要依次去探尋驗(yàn)證碼對(duì)應(yīng)位置是否有匹配模板。如果匹配全圖的話,我們不需要關(guān)心每個(gè)箭頭的位置和朝向,只需要將驗(yàn)證碼全圖保存下來(lái)即可,在匹配的時(shí)候也不需要再去計(jì)算箭頭的位置,所以工作量上明顯是匹配全圖更小。

所以綜上考慮,我們選用全圖匹配的方式來(lái)進(jìn)行識(shí)別。

所以到此為止,我們就可以使用全圖模板匹配的方法來(lái)識(shí)別這個(gè)宮格驗(yàn)證碼了,找到匹配的模板之后,我們就可以得到事先為模板定義的拖動(dòng)順序,然后模擬拖動(dòng)即可。

4. 獲取模板

在開始之前,我們需要做一下準(zhǔn)備工作,先將 24 張驗(yàn)證碼全圖保存下來(lái),保存工作難道需要手工來(lái)做嗎?當(dāng)然不是的,因?yàn)轵?yàn)證碼是隨機(jī)的,一共有 24 種,所以我們可以寫一段程序來(lái)批量保存一些驗(yàn)證碼圖片,然后從中篩選出需要的圖片就好了,代碼如下:

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):
        """
        打開網(wǎng)頁(yè)輸入用戶名密碼并點(diǎn)擊
        :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):
        """
        獲取驗(yàn)證碼位置
        :return: 驗(yàn)證碼位置元組
        """
        try:
            img = self.wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'patt-shadow')))
        except TimeoutException:
            print('未出現(xiàn)驗(yàn)證碼')
            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):
        """
        獲取網(wǎng)頁(yè)截圖
        :return: 截圖對(duì)象
        """
        screenshot = self.browser.get_screenshot_as_png()
        screenshot = Image.open(BytesIO(screenshot))
        return screenshot
    def get_image(self, name='captcha.png'):
        """
        獲取驗(yàn)證碼圖片
        :return: 圖片對(duì)象
        """
        top, bottom, left, right = self.get_position()
        print('驗(yàn)證碼位置', top, bottom, left, right)
        screenshot = self.get_screenshot()
        captcha = screenshot.crop((left, top, right, bottom))
        captcha.save(name)
        return captcha
    def main(self):
        """
        批量獲取驗(yàn)證碼
        :return: 圖片對(duì)象
        """
        count = 0
        while True:
            self.open()
            self.get_image(str(count) + '.png')
            count += 1
if __name__ == '__main__':
    crack = CrackWeiboSlide()
    crack.main()

其中這里需要將 USERNAME 和 PASSWORD 修改為自己微博的用戶名密碼,運(yùn)行一段時(shí)間后便可以發(fā)現(xiàn)在本地多了很多以數(shù)字命名的驗(yàn)證碼,如圖所示:

Python3爬蟲里如何實(shí)現(xiàn)識(shí)別微博宮格驗(yàn)證碼

在這里我們只需要挑選出不同的24張驗(yàn)證碼圖片并命名保存就好了,名稱可以直接取作宮格的滑動(dòng)的順序,如某張驗(yàn)證碼圖片如圖所示:

Python3爬蟲里如何實(shí)現(xiàn)識(shí)別微博宮格驗(yàn)證碼

我們將其命名為 4132.png 即可,也就是代表滑動(dòng)順序?yàn)?4-1-3-2,按照這樣的規(guī)則,我們將驗(yàn)證碼整理為如下 24 張圖,如圖 所示:

Python3爬蟲里如何實(shí)現(xiàn)識(shí)別微博宮格驗(yàn)證碼

如上的 24 張圖就是我們的模板,接下來(lái)我們?cè)谧R(shí)別的時(shí)候只需要遍歷模板進(jìn)行匹配即可。

5. 模板匹配

上面的代碼已經(jīng)實(shí)現(xiàn)了將驗(yàn)證碼保存下來(lái)的功能,通過調(diào)用 get_image() 方法我們便可以得到驗(yàn)證碼圖片對(duì)象,得到驗(yàn)證碼對(duì)象之后我們就需要對(duì)其進(jìn)行模板匹配了,定義如下的方法進(jìn)行匹配:

from os import listdir
def detect_image(self, image):
    """
    匹配圖片
    :param image: 圖片
    :return: 拖動(dòng)順序
    """
    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('拖動(dòng)順序', numbers)
            return numbers

在這里 TEMPLATES_FOLDER 就是模板所在的文件夾,在這里我們用 listdir() 方法將所有模板的文件名稱獲取出來(lái),然后對(duì)其進(jìn)行遍歷,通過 same_image() 方法對(duì)驗(yàn)證碼和模板進(jìn)行比對(duì),如果成功匹配,那么就將匹配到的模板文件名轉(zhuǎn)為列表,如匹配到了 3124.png,則返回結(jié)果 [3, 1, 2, 4]。

比對(duì)的方法實(shí)現(xiàn)如下:

def is_pixel_equal(self, image1, image2, x, y):
    """
    判斷兩個(gè)像素是否相同
    :param image1: 圖片1
    :param image2: 圖片2
    :param x: 位置x
    :param y: 位置y
    :return: 像素是否相同
    """
    # 取兩個(gè)圖片的像素點(diǎn)
    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):
    """
    識(shí)別相似驗(yàn)證碼
    :param image: 待識(shí)別驗(yàn)證碼
    :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

在這里比對(duì)圖片也是利用了遍歷像素的方法,same_image() 方法接收兩個(gè)參數(shù),image 為待檢測(cè)的驗(yàn)證碼圖片對(duì)象,template 是模板對(duì)象,由于二者大小是完全一致的,所以在這里我們遍歷了圖片的所有像素點(diǎn),比對(duì)二者同一位置的像素點(diǎn)是否相同,如果相同就計(jì)數(shù)加 1,最后計(jì)算一下相同的像素點(diǎn)占總像素的比例,如果該比例超過一定閾值那就判定為圖片完全相同,匹配成功。在這里設(shè)定閾值為 0.99,即如果二者有 0.99 以上的相似比則代表匹配成功。

這樣通過上面的方法,依次匹配 24 個(gè)模板,如果驗(yàn)證碼圖片正常,總能找到一個(gè)匹配的模板,這樣最后就可以得到宮格的滑動(dòng)順序了。

6. 模擬拖動(dòng)

得到了滑動(dòng)順序之后,我們接下來(lái)就是根據(jù)滑動(dòng)順序來(lái)拖動(dòng)鼠標(biāo)連接各個(gè)宮格了,方法實(shí)現(xiàn)如下:

def move(self, numbers):
    """
    根據(jù)順序拖動(dòng)
    :param numbers:
    :return:
    """
    # 獲得四個(gè)按點(diǎn)
    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]
        # 如果是第一次循環(huán)
        if index == 0:
            # 點(diǎn)擊第一個(gè)按點(diǎn)
            ActionChains(self.browser) 
                .move_to_element_with_offset(circle, circle.size['width'] / 2, circle.size['height'] / 2) 
                .click_and_hold().perform()
        else:
            # 小幅移動(dòng)次數(shù)
            times = 30
            # 拖動(dòng)
            for i in range(times):
                ActionChains(self.browser).move_by_offset(dx / times, dy / times).perform()
                time.sleep(1 / times)
        # 如果是最后一次循環(huán)
        if index == 3:
            # 松開鼠標(biāo)
            ActionChains(self.browser).release().perform()
        else:
            # 計(jì)算下一次偏移
            dx = circles[numbers[index + 1] - 1].location['x'] - circle.location['x']
            dy = circles[numbers[index + 1] - 1].location['y'] - circle.location['y']

在這里方法接收的參數(shù)就是宮格的點(diǎn)按順序,如 [3, 1, 2, 4]。首先我們利用 find_elements_by_css_selector() 方法獲取到四個(gè)宮格元素,是一個(gè)列表形式,每個(gè)元素代表一個(gè)宮格,接下來(lái)我們遍歷了宮格的點(diǎn)按順序,再做一系列對(duì)應(yīng)操作。

其中如果是第一個(gè)宮格,那就直接鼠標(biāo)點(diǎn)擊并保持動(dòng)作,否則移動(dòng)到下一個(gè)宮格。如果是最后一個(gè)宮格,那就松開鼠標(biāo),否則計(jì)算移動(dòng)到下一個(gè)宮格的偏移量。

通過四次循環(huán),我們便可以成功操作瀏覽器完成宮格驗(yàn)證碼的拖拽填充,松開鼠標(biāo)之后即可識(shí)別成功。

運(yùn)行效果如圖所示:

Python3爬蟲里如何實(shí)現(xiàn)識(shí)別微博宮格驗(yàn)證碼

鼠標(biāo)會(huì)慢慢的從起始位置移動(dòng)到終止位置,最后一個(gè)宮格松開之后便完成了驗(yàn)證碼的識(shí)別。

至此,微博宮格驗(yàn)證碼的識(shí)別就全部完成了。

識(shí)別完成之后驗(yàn)證碼窗口會(huì)自動(dòng)關(guān)閉,接下來(lái)直接點(diǎn)擊登錄按鈕即可完成微博登錄。

以上就是關(guān)于Python3爬蟲里如何實(shí)現(xiàn)識(shí)別微博宮格驗(yàn)證碼的內(nèi)容,如果你們有學(xué)習(xí)到知識(shí)或者技能,可以把它分享出去讓更多的人看到。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI