一、介绍
今天主要介绍的是微博客户端在登录时出现的四宫格手绘验证码,不多说直接看看验证码长成什么样。
二、思路
1、由于微博上的手绘验证码只有四个宫格,且每个宫格之间都有有向线段连接,所以我们可以判断四个宫格不同方向的验证码一共有24种,
我们将四个宫格进行标号,得到的结果如下:
则我们可以排列出24种不同的手绘方向的验证码,分别为一下24种
1234 | 2134 | 3124 | 4321 |
1243 | 2143 | 3142 | 4312 |
1342 | 2314 | 3214 | 4123 |
1324 | 2341 | 3241 | 4132 |
1423 | 2413 | 3412 | 4213 |
1432 | 2431 | 3421 | 4231 |
2、我们通过获取到微博客户端的24种手绘验证码后需要进行模板匹配,这样通过全图匹配的方式进行滑动。
三、代码实现
1、首先是要通过微博移动端(https://passport.weibo.cn/signin/login)批量获取手绘验证码,但是这个验证码不一定出现,
只有在账号存在风险或者频繁登录的时候才会出现。获取手绘验证码的代码如下:
注意:需要将模拟浏览器所以元素(用户名框,密码框)加载完了才能发送用户名和密码,否则报错
1 # -*- coding:utf-8 -*- 2 import time 3 from io import BytesIO 4 from PIL import Image 5 from selenium import webdriver 6 from selenium.webdriver.common.by import By 7 from selenium.common.exceptions import TimeoutException 8 from selenium.webdriver.support.ui import WebDriverWait 9 from selenium.webdriver.support import expected_conditions as EC 10 11 12 class CrackWeiboSlide(): 13 def __init__(self): 14 self.url = "https://passport.weibo.cn/signin/login?entry=mweibo&r=https://m.weibo.cn/" 15 self.browser = webdriver.Chrome(r"D:chromedriver.exe") 16 self.browser.maximize_window() 17 self.wait = WebDriverWait(self.browser,5) 18 19 20 def __del__(self): 21 self.browser.close() 22 23 def open(self): 24 # 打开模拟浏览器 25 self.browser.get(self.url) 26 # 获取用户名元素 27 username = self.wait.until(EC.presence_of_element_located((By.XPATH,'//*[@id="loginName"]'))) 28 # 获取密码框元素 29 password = self.wait.until(EC.presence_of_element_located((By.XPATH,'//*[@id="loginPassword"]'))) 30 # 获取登录按钮元素 31 submit = self.wait.until(EC.element_to_be_clickable((By.XPATH,'//*[@id="loginAction"]'))) 32 # 提交数据并登录 33 username.send_keys("15612345678") 34 password.send_keys("xxxxxxxxxxxx") 35 submit.click() 36 37 38 def get_image(self,name = "captcha.png"): 39 try: 40 # 获取验证码图片元素 41 img = self.wait.until(EC.presence_of_element_located((By.CLASS_NAME,"patt-shadow"))) 42 time.sleep(1) 43 # 获取验证码图片所在的位置 44 location = img.location 45 # 获取验证码图片的大小 46 size = img.size 47 top = location["y"] # 上 48 bottom = location["y"] + size["height"] # 下 49 left = location["x"] # 左 50 right = location["x"] + size["width"] # 右 51 print("验证码的位置:", left, top, right, bottom) 52 # 将当前窗口进行截屏 53 screenshot = self.browser.get_screenshot_as_png() 54 # 读取截图 55 screenshot = Image.open(BytesIO(screenshot)) 56 # 剪切九宫格图片验证码 57 captcha = screenshot.crop((left, top, right, bottom)) 58 # 将剪切的九宫格验证码保存到指定位置 59 captcha.save(name) 60 print("微博登录验证码保存完成!!!") 61 return captcha 62 except TimeoutException: 63 print("没有出现验证码!!") 64 # 回调打开模拟浏览器函数 65 self.open() 66 67 68 def main(self): 69 count = 1 70 while True: 71 # 调用打开模拟浏览器函数 72 self.open() 73 # 调用获取验证码图片函数 74 self.get_image(str(count) + ".png") 75 count += 1 76 77 78 if __name__ == '__main__': 79 crack = CrackWeiboSlide() 80 crack.main()
得到的24种手绘验证码,同时需要对这些手绘验证码根据上边的编号进行命名
上图就是我们需要的模板,接下来我们进行遍历模板匹配即可
2、模板匹配
通过遍历手绘验证码模板进行匹配
1 import os 2 import time 3 from io import BytesIO 4 from PIL import Image 5 from selenium import webdriver 6 from selenium.webdriver import ActionChains 7 from selenium.webdriver.common.by import By 8 from selenium.common.exceptions import TimeoutException 9 from selenium.webdriver.support.ui import WebDriverWait 10 from selenium.webdriver.support import expected_conditions as EC 11 12 class CrackWeiboSlide(): 13 def __init__(self): 14 self.url = "https://passport.weibo.cn/signin/login?entry=mweibo&r=https://m.weibo.cn/" 15 self.browser = webdriver.Chrome(r"D:chromedriver.exe") 16 self.browser.maximize_window() 17 self.wait = WebDriverWait(self.browser,5) 18 19 20 def __del__(self): 21 self.browser.close() 22 23 def open(self): 24 # 打开模拟浏览器 25 self.browser.get(self.url) 26 # 获取用户名元素 27 username = self.wait.until(EC.presence_of_element_located((By.XPATH,'//*[@id="loginName"]'))) 28 # 获取密码框元素 29 password = self.wait.until(EC.presence_of_element_located((By.XPATH,'//*[@id="loginPassword"]'))) 30 # 获取登录按钮元素 31 submit = self.wait.until(EC.element_to_be_clickable((By.XPATH,'//*[@id="loginAction"]'))) 32 # 提交数据并登录 33 username.send_keys("15612345678") 34 password.send_keys("xxxxxxxxxxxx") 35 submit.click() 36 37 38 def get_image(self,name = "captcha.png"): 39 try: 40 # 获取验证码图片元素 41 img = self.wait.until(EC.presence_of_element_located((By.CLASS_NAME,"patt-shadow"))) 42 time.sleep(1) 43 44 # 获取验证码图片所在的位置 45 location = img.location 46 47 # 获取验证码图片的大小 48 size = img.size 49 top = location["y"] # 上 50 bottom = location["y"] + size["height"] # 下 51 left = location["x"] # 左 52 right = location["x"] + size["width"] # 右 53 print("验证码的位置:", left, top, right, bottom) 54 55 # 将当前窗口进行截屏 56 screenshot = self.browser.get_screenshot_as_png() 57 58 # 读取截图 59 screenshot = Image.open(BytesIO(screenshot)) 60 61 # 剪切九宫格图片验证码 62 captcha = screenshot.crop((left, top, right, bottom)) 63 64 # 将剪切的九宫格验证码保存到指定位置 65 captcha.save(name) 66 print("微博登录验证码保存完成!!!") 67 68 # 返回微博移动端的验证码图片 69 return captcha 70 except TimeoutException: 71 print("没有出现验证码!!") 72 73 # 回调打开模拟浏览器函数 74 self.open() 75 76 def is_pixel_equal(self,image,template,i,j): 77 78 # 取出两张图片的像素点 79 pixel1 = image.load()[i,j] # 移动客户端获取的验证码 80 pixel2 = template.load()[i,j] # 模板文件里的验证码 81 threshold = 20 # 阈值 82 pix_r = abs(pixel1[0] - pixel2[0]) # R 83 pix_g = abs(pixel1[1] - pixel2[1]) # G 84 pix_b = abs(pixel1[2] - pixel2[2]) # B 85 if (pix_r< threshold) and (pix_g< threshold ) and (pix_b< threshold) : 86 return True 87 else: 88 return False 89 90 def same_image(self,image,template): 91 """ 92 :param image: 微博移动端获取的验证码图片 93 :param template: 通过模板文件获取的验证码图片 94 """ 95 threshold = 0.99 # 相似度阈值 96 count = 0 97 # 遍历微博移动端获取的验证码图片的宽度和高度 98 for i in range(image.width): 99 for j in range(image.height): 100 101 # 判断两张图片的像素是否相等 102 if self.is_pixel_equal(image,template,i,j): 103 count += 1 104 result = float(count)/(image.width*image.height) 105 if result >threshold: 106 print("匹配成功!!!") 107 return True 108 else: 109 return False 110 111 112 def detect_image(self,image): 113 # 遍历手绘验证码模板文件内的所有验证码图片 114 for template_name in os.listdir(r"D:photo emplates"): 115 print("正在匹配",template_name) 116 117 # 打开验证码图片 118 template = Image.open(r"D:photo emplates{}".format(template_name)) 119 120 if self.same_image(image,template): 121 # 返回这张图片的顺序,如4—>3—>1—>2 122 numbers = [int(number) for number in list(template_name.split(".")[0])] 123 print("按照顺序进行拖动",numbers) 124 return numbers 125 126 def move(self,numbers): 127 # 获得四个按点 128 circles = self.browser.find_element_by_css_selector('.patt-wrap .patt-circ') 129 dx = dy = 0 130 # 由于是四个宫格,所以需要循环四次 131 for index in range(4): 132 circle = circles[numbers[index] - 1] 133 # 如果是第一次循环 134 if index == 0: 135 # 点击第一个点 136 action = ActionChains(self.browser).move_to_element_with_offset(circle,circle.size["width"]/2,circle.size['height']/2) 137 action.click_and_hold().perform() 138 else: 139 # 小幅度移动次数 140 times = 30 141 # 拖动 142 for i in range(times): 143 ActionChains(self.browser).move_by_offset(dx/times,dy/times).perform() 144 time.sleep(1/times) 145 146 # 如果是最后一次循环 147 if index == 3: 148 # 松开鼠标 149 ActionChains(self.browser).release().perform() 150 else: 151 # 计算下一次偏移 152 dx = circles[numbers[index + 1] - 1].location['x'] - circle.location['x'] 153 dy = circles[numbers[index + 1] - 1].location['y'] - circle.location['y'] 154 155 156 def main(self): 157 # 调用打开模拟浏览器函数 158 self.open() 159 image = self.get_image("captcha.png") # 微博移动端的验证码图片 160 numbers = self.detect_image(image) 161 self.move(numbers) 162 time.sleep(10) 163 print('识别结束') 164 165 166 if __name__ == '__main__': 167 crack = CrackWeiboSlide() 168 crack.main()
四、识别结果
通过循环四次后绘出四条方向,最终得到效果图