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

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

圖片

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

圖片

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

1、本節目標

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

2、準備工做

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

3、識別思路

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



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


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

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

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

咱們必需要收集到足夠多的模板,模板匹配方法的效果纔會好。而對於微博宮格驗證碼來講,宮格只有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、結語

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

相關文章
相關標籤/搜索