本節咱們將介紹新浪微博宮格驗證碼的識別。微博宮格驗證碼是一種新型交互式驗證碼,每一個宮格之間會有一條指示連線,指示了應該的滑動軌跡。咱們要按照滑動軌跡依次從起始宮格滑動到終止宮格,才能夠完成驗證,以下圖所示。
css
鼠標滑動後的軌跡會以黃色的連線來標識,以下圖所示。html
訪問新浪微博移動版登陸頁面,就能夠看到如上驗證碼,連接爲https://passport.weibo.cn/signin/login。不是每次登陸都會出現驗證碼,當頻繁登陸或者帳號存在安全風險的時候,驗證碼纔會出現。
git
咱們的目標是用程序來識別並經過微博宮格驗證碼的驗證。
github
本次咱們使用的Python庫是Selenium,使用的瀏覽器爲Chrome,請確保已經正確安裝好Selenium庫、Chrome瀏覽器,並配置好ChromeDriver。
web
識別從探尋規律入手。規律就是,此驗證碼的四個宮格必定是有連線通過的,每一條連線上都會相應的指示箭頭,連線的形狀多樣,包括C型、Z型、X型等,以下圖所示。
算法
咱們發現,同一類型的連線軌跡是相同的,惟一不一樣的就是連線的方向,以下圖所示。瀏覽器
這兩種驗證碼的連線軌跡是相同的。可是因爲連線上面的指示箭頭不一樣,致使滑動的宮格順序有所不一樣。安全
若是要徹底識別滑動宮格順序,就須要具體識別出箭頭的朝向。而整個驗證碼箭頭朝向一共有8種,並且會出如今不一樣的位置。若是要寫一個箭頭方向識別算法,須要考慮不一樣箭頭所在的位置,找出各個位置箭頭的像素點座標,計算像素點變化規律,這個工做量就會變得比較大。bash
這時咱們能夠考慮用模板匹配的方法,就是將一些識別目標提早保存並作好標記,這稱做模板。這裏將驗證碼圖片作好拖動順序的標記當作模板。對比要新識別的目標和每個模板,若是找到匹配的模板,則就成功識別出要新識別的目標。在圖像識別中,模板匹配也是經常使用的方法,實現簡單且易用性好。微信
咱們必需要收集到足夠多的模板,模板匹配方法的效果纔會好。而對於微博宮格驗證碼來講,宮格只有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]。首先咱們利用find_elements_by_css_selector()
方法獲取到4個宮格元素,它是一個列表形式,每一個元素表明一個宮格。接下來遍歷宮格的點按順序,作一系列對應操做。
其中若是當前遍歷的是第一個宮格,那就直接鼠標點擊並保持動做,不然移動到下一個宮格。若是當前遍歷的是最後一個宮格,那就鬆開鼠標,若是不是最後一個宮格,則計算移動到下一個宮格的偏移量。
經過4次循環,咱們即可以成功操做瀏覽器完成宮格驗證碼的拖拽填充,鬆開鼠標以後便可識別成功。運行效果以下圖所示。
鼠標會慢慢從起始位置移動到終止位置。最後一個宮格鬆開以後,驗證碼的識別便完成了。
至此,微博宮格驗證碼的識別就所有完成。驗證碼窗口會自動關閉。直接點擊登陸按鈕便可登陸微博。
本節代碼地址爲:https://github.com/Python3WebSpider/CrackWeiboSlide。
本節介紹了一種經常使用的模板匹配識別圖片的方式,模擬了鼠標拖拽動做來實現驗證碼的識別。若是遇到相似的驗證碼,咱們能夠採用一樣的思路進行識別。
本資源首發於崔慶才的我的博客靜覓: Python3網絡爬蟲開發實戰教程 | 靜覓
如想了解更多爬蟲資訊,請關注個人我的微信公衆號:進擊的Coder
weixin.qq.com/r/5zsjOyvEZ… (二維碼自動識別)