天天看點

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

本節我們将介紹新浪微網誌宮格驗證碼的識别。微網誌宮格驗證碼是一種新型互動式驗證碼,每個宮格之間會有一條訓示連線,訓示了應該的滑動軌迹。我們要按照滑動軌迹依次從起始宮格滑動到終止宮格,才可以完成驗證,如下圖所示。

滑動宮格驗證碼都給碰上了?沒事兒,看完此文分分鐘拿下
滑鼠滑動後的軌迹會以黃色的連線來辨別,如下圖所示。
滑動宮格驗證碼都給碰上了?沒事兒,看完此文分分鐘拿下

通路新浪微網誌移動版登入頁面,就可以看到如上驗證碼,連結為https://passport.weibo.cn/signin/login。不是每次登入都會出現驗證碼,當頻繁登入或者賬号存在安全風險的時候,驗證碼才會出現。

一、本節目标

我們的目标是用程式來識别并通過微網誌宮格驗證碼的驗證。

二、準備工作

本次我們使用的Python庫是Selenium,使用的浏覽器為Chrome,請確定已經正确安裝好Selenium庫、Chrome浏覽器,并配置好ChromeDriver。

三、識别思路

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

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

我們發現,同一類型的連線軌迹是相同的,唯一不同的就是連線的方向,如下圖所示。

這兩種驗證碼的連線軌迹是相同的。但是由于連線上面的訓示箭頭不同,導緻滑動的宮格順序有所不同。

如果要完全識别滑動宮格順序,就需要具體識别出箭頭的朝向。而整個驗證碼箭頭朝向一共有8種,而且會出現在不同的位置。如果要寫一個箭頭方向識别算法,需要考慮不同箭頭所在的位置,找出各個位置箭頭的像素點坐标,計算像素點變化規律,這個工作量就會變得比較大。

這時我們可以考慮用模闆比對的方法,就是将一些識别目标提前儲存并做好标記,這稱作模闆。這裡将驗證碼圖檔做好拖動順序的标記當做模闆。對比要新識别的目标和每一個模闆,如果找到比對的模闆,則就成功識别出要新識别的目标。在圖像識别中,模闆比對也是常用的方法,實作簡單且易用性好。

我們必須要收集到足夠多的模闆,模闆比對方法的效果才會好。而對于微網誌宮格驗證碼來說,宮格隻有4個,驗證碼的樣式最多4×3×2×1=24種,則我們可以将所有模闆都收集下來。

接下來我們需要考慮的就是,用何種模闆來進行比對,隻比對箭頭還是比對整個驗證碼全圖呢?我們權衡一下這兩種方式的比對精度和工作量。

滑動宮格驗證碼都給碰上了?沒事兒,看完此文分分鐘拿下
首先是精度問題。如果是比對箭頭,比對的目标隻有幾個像素點範圍的箭頭,我們需要精确知道各個箭頭所在的像素點,一旦像素點有偏差,那麼會直接錯位,導緻比對結果大打折扣。如果是比對全圖,我們無需關心箭頭所在位置,同時還有連線幫助輔助比對。顯然,全圖比對的精度更高。
滑動宮格驗證碼都給碰上了?沒事兒,看完此文分分鐘拿下

其次是工作量的問題。如果是比對箭頭,我們需要儲存所有不同朝向的箭頭模闆,而相同位置箭頭的朝向可能不一,相同朝向的箭頭位置可能不一,那麼我們需要算出每個箭頭的位置并将其逐個截出儲存成模闆,依次探尋驗證碼對應位置是否有比對模闆。如果是比對全圖,我們不需要關心每個箭頭的位置和朝向,隻需要将驗證碼全圖儲存下來即可,在比對的時候也不需要計算箭頭的位置。顯然,比對全圖的工作量更少。

綜上考慮,我們選用全圖比對的方式來進行識别。找到比對的模闆之後,我們就可以得到事先為模闆定義的拖動順序,然後模拟拖動即可。

四、擷取模闆

我們需要做一下準備工作。先儲存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()           

這裡需要将

USERNAME

PASSWORD

修改為自己微網誌的使用者名和密碼。運作一段時間後,本地多了很多以數字命名的驗證碼,如下圖所示。

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

這裡我們隻需要挑選出不同的24張驗證碼圖檔并命名儲存。名稱可以直接取作宮格的滑動的順序,如下圖所示。

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

我們将圖檔命名為4132.png,代表滑動順序為4-1-3-2。按照這樣的規則,我們将驗證碼整理為如下24張圖,如下圖所示。

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

如上24張圖就是我們的模闆。接下來,識别過程隻需要周遊模闆進行比對即可。

五、模闆比對

調用

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個模闆。如果驗證碼圖檔正常,我們總能找到一個比對的模闆,這樣就可以得到宮格的滑動順序了。

六、模拟拖動

接下來,根據滑動順序拖動滑鼠,連接配接各個宮格,方法實作如下所示:

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]。首先我們利用f

ind_elements_by_css_selector()

方法擷取到4個宮格元素,它是一個清單形式,每個元素代表一個宮格。接下來周遊宮格的點按順序,做一系列對應操作。

其中如果目前周遊的是第一個宮格,那就直接滑鼠點選并保持動作,否則移動到下一個宮格。如果目前周遊的是最後一個宮格,那就松開滑鼠,如果不是最後一個宮格,則計算移動到下一個宮格的偏移量。

通過4次循環,我們便可以成功操作浏覽器完成宮格驗證碼的拖拽填充,松開滑鼠之後即可識别成功。運作效果如下圖所示。

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

滑鼠會慢慢從起始位置移動到終止位置。最後一個宮格松開之後,驗證碼的識别便完成了。

至此,微網誌宮格驗證碼的識别就全部完成。驗證碼視窗會自動關閉。直接點選登入按鈕即可登入微網誌。

七、本節代碼

本節代碼位址為:https://github.com/Python3WebSpider/CrackWeiboSlide。

八、結語

本節介紹了一種常用的模闆比對識别圖檔的方式,模拟了滑鼠拖拽動作來實作驗證碼的識别。如果遇到類似的驗證碼,我們可以采用同樣的思路進行識别。

原文釋出時間為:2018-06-25

本文作者:崔慶才

本文來自雲栖社群官方釘群“

Python技術進階

”,了解相關資訊可以關注“

”。

Python技術進階交流群

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