jQuery火箭图标返回顶部代码

Python爬虫学习笔记之微信宫格验证码的识别(存在问题)

本节我们将介绍新浪微博宫格验证码的识别。微博宫格验证码是一种新型交互式验证码,每个宫格之间会有一条

指示连线,指示了应该的滑动轨迹。我们要按照滑动轨迹依次从起始宫格滑动到终止宫格,才可以完成验证,如

下图所示。

 

鼠标滑动后的轨迹会以黄色的连线来标识,如下图所示。

 

访问新浪微博移动版登录页面,就可以看到如上验证码,链接为  https://passport.weibo.cn/signin/login

一、本节目标

 

我们的目标是用程序来识别并通过微博宫格验证码的验证。

 

二、准备工作

 

本次我们使用的Python库是Selenium,使用的浏览器为Chrome,请确保已经正确安装好Selenium库、Chrome浏览器,并配置好ChromeDriver。

 

三、识别思路

 

识别从探寻规律入手。规律就是,此验证码的四个宫格一定是有连线经过的,每一条连线上都会相应的指示箭头,连线的形状多样,包括C型、Z型、X型等,如下图所示。

       我们发现,同一类型的连线轨迹是相同的,唯一不同的就是连线的方向,如下图所示。这两种验证码的连线轨迹是相同的。但是由于连线上面的指示箭头不同,导致滑动的宫格顺序有所不同。

如果要完全识别滑动宫格顺序,就需要具体识别出箭头的朝向。而整个验证码箭头朝向一共有8种,而且会出现在不同的位置。如果要写一个箭头方向识别算法,需要考虑不同箭头所在的位置,找

出各个位置箭头的像素点坐标,计算像素点变化规律,这个工作量就会变得比较大。这时我们可以考虑用模板匹配的方法,就是将一些识别目标提前保存并做好标记,这称作模板。这里将验证码图

片做好拖动顺序的标记当做模板。对比要新识别的目标和每一个模板,如果找到匹配的模板,则就成功识别出要新识别的目标。在图像识别中,模板匹配也是常用的方法,实现简单且易用性好。

我们必须要收集到足够多的模板,模板匹配方法的效果才会好。而对于微博宫格验证码来说,宫格只有4个,验证码的样式最多4×3×2×1=24种,则我们可以将所有模板都收集下来。

接下来我们需要考虑的就是,用何种模板来进行匹配,只匹配箭头还是匹配整个验证码全图呢?我们权衡一下这两种方式的匹配精度和工作量。

d47e62d2b349aca45e42305ed6714efbe5ed61d9首先是精度问题。如果是匹配箭头,比对的目标只有几个像素点范围的箭头,我们需要精确知道各个箭头所在的像素点,一旦像素点有偏差,那么会直接错位,导致匹配结果大打折扣。如果

是匹配全图,我们无需关心箭头所在位置,同时还有连线帮助辅助匹配。显然,全图匹配的精度更高。
d47e62d2b349aca45e42305ed6714efbe5ed61d9其次是工作量的问题。如果是匹配箭头,我们需要保存所有不同朝向的箭头模板,而相同位置箭头的朝向可能不一,相同朝向的箭头位置可能不一,那么我们需要算出每个箭头的位置并将其

逐个截出保存成模板,依次探寻验证码对应位置是否有匹配模板。如果是匹配全图,我们不需要关心每个箭头的位置和朝向,只需要将验证码全图保存下来即可,在匹配的时候也不需要计算箭头的

位置。显然,匹配全图的工作量更少。

综上考虑,我们选用全图匹配的方式来进行识别。找到匹配的模板之后,我们就可以得到事先为模板定义的拖动顺序,然后模拟拖动即可。

获取模板:

 

 1 import os
 2 import time
 3 from io import BytesIO
 4 from PIL import Image
 5 from selenium import webdriver
 6 from selenium.common.exceptions import TimeoutException
 7 from selenium.webdriver import ActionChains
 8 from selenium.webdriver.common.by import By
 9 from selenium.webdriver.support.ui import WebDriverWait
10 from selenium.webdriver.support import expected_conditions as EC
11 from os import listdir
12 
13 USERNAME = ''
14 PASSWORD = ''
15 
16 TEMPLATES_FOLDER = 'templates/'
17 
18 class CrackWeiboSlide():
19     def __init__(self):
20         self.url = 'https://passport.weibo.cn/signin/login?entry=mweibo&r=https://m.weibo.cn/'
21         self.browser = webdriver.Chrome()
22         self.wait = WebDriverWait(self.browser, 20)
23         self.username = USERNAME
24         self.password = PASSWORD
25 
26     def __del__(self):
27         self.browser.close()
28 
29     def open(self):
30         """
31         打开网页输入用户名密码登陆
32         :return: None
33         """
34         self.browser.get(self.url)
35         username = self.wait.until(EC.presence_of_element_located((By.ID, 'loginName')))
36         password = self.wait.until(EC.presence_of_element_located((By.ID, 'loginPassword')))
37         submit = self.wait.until(EC.element_to_be_clickable((By.ID, 'loginAction')))
38         username.send_keys(self.username)
39         password.send_keys(self.password)
40         submit.click()
41 
42     def get_position(self):
43         """
44         获取验证码位置
45         :return: 验证码位置元组
46         """
47         try:
48             img = self.wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'patt-shadow')))
49         except TimeoutException:
50             print('未出现验证码')
51             self.opem()
52         time.sleep(2)
53         location = img.location
54         size = img.size
55         top, bottom, left, right = location['y'], location['y'] + size['height'], location['x'], location['x'] + size['width']
56         return (top, bottom, left, right)
57 
58     def get_screenshot(self):
59         """
60         获取网页截图
61         :return: 截图对象
62         """
63         screenshot = self.browser.get_screenshot_as_png()
64         screenshot = Image.open(BytesIO(screenshot))
65         return screenshot
66 
67     def get_image(self, name='captcha.png'):
68         """
69         获取验证码图片
70         :return:图片对象
71         """
72         top, bottom, left, right = self.get_position()
73         print('验证码位置', top, bottom, left, right)
74         screenshot = self.get_screenshot()
75         captcha = screenshot.crop((left, top, right, bottom))
76         captcha.save(name)
77         return captcha
78 
79     def main(self):
80         """
81         批量获取验证码
82         :return: 图片对象
83         """
84         count = 0
85         while True:
86             self.open()
87             self.get_image(str(count) + '.png')
88             count += 1
89 
90 if __name__ == '__main__':
91     crack = CrackWeiboSlide()
92     crack.main()

 

这里需要将USERNAMEPASSWORD修改为自己微博的用户名和密码。运行一段时间后,本地多了很多以数字命名的验证码,如下图所示。

 

我们将图片命名为4132.png,代表滑动顺序为4-1-3-2。按照这样的规则,我们将验证码整理为如下24张图,如下图所示。

 

好了,获取模板就到此结束了,接下来该模板匹配了

方法解释:

(1)调用get_image()方法,得到验证码图片对象。然后,对验证码图片对象进行模板匹配

(2)TEMPLATES_FOLDER就是模板所在的文件夹。这里通过listdir()方法获取所有模板的文件名称,然后对其进行遍历,通过same_image()

   方法对验证码和模板进行比对。如果匹配成功,那么就将匹配到的模板文件名转换为列表。如模板文件3124.png匹配到了,则返回结果

   为[3, 1, 2, 4]。

 

(3)same_image()方法接收两个参数,image为待检测的验证码图片对象,template是模板对象。由于二者大小是完全一致的,所以在这里我

   们遍历了图片的所有像素点。比对二者同一位置的像素点,如果像素点相同,计数就加1。最后计算相同的像素点占总像素的比例。如果

   该比例超过一定阈值,那就判定图片完全相同,则匹配成功。这里阈值设定为0.99,即如果二者有0.99以上的相似比,则代表匹配成功。

 

(4)通过上面的方法,依次匹配24个模板。如果验证码图片正常,我们总能找到一个匹配的模板,这样就可以得到宫格的滑动顺序了。

(5)接下来,根据滑动顺序拖动鼠标,连接各个宫格

   这里方法接收的参数就是宫格的点按顺序,如[3,1,2,4]。首先我们利用find_elements_by_css_selector()方法获取到4个宫格元素,它

   是一个列表形式,每个元素代表一个宫格。接下来遍历宫格的点按顺序,做一系列对应操作。其中如果当前遍历的是第一个宫格,那就直

   接鼠标点击并保持动作,否则移动到下一个宫格。如果当前遍历的是最后一个宫格,那就松开鼠标,如果不是最后一个宫格,则计算移动

   到下一个宫格的偏移量。通过4次循环,我们便可以成功操作浏览器完成宫格验证码的拖拽填充,松开鼠标之后即可识别成功.

(6)鼠标会慢慢从起始位置移动到终止位置。最后一个宫格松开之后,验证码的识别便完成了。至此,微博宫格验证码的识别就全部完成。验

   证码窗口会自动关闭。直接点击登录按钮即可登录微博。

 

方法代码(为什么验证不成功?请大家帮我看一下):

 

  1 import os
  2 import time
  3 from io import BytesIO
  4 from PIL import Image
  5 from selenium import webdriver
  6 from selenium.common.exceptions import TimeoutException
  7 from selenium.webdriver import ActionChains
  8 from selenium.webdriver.common.by import By
  9 from selenium.webdriver.support.ui import WebDriverWait
 10 from selenium.webdriver.support import expected_conditions as EC
 11 from os import listdir
 12 
 13 USERNAME = ''
 14 PASSWORD = ''
 15 
 16 TEMPLATES_FOLDER = 'templates/'
 17 
 18 
 19 class CrackWeiboSlide():
 20     def __init__(self):
 21         self.url = 'https://passport.weibo.cn/signin/login?entry=mweibo&r=https://m.weibo.cn/'
 22         self.browser = webdriver.Chrome()
 23         self.wait = WebDriverWait(self.browser, 20)
 24         self.username = USERNAME
 25         self.password = PASSWORD
 26 
 27     def __del__(self):
 28         self.browser.close()
 29 
 30     def open(self):
 31         """
 32         打开网页输入用户名密码并点击
 33         :return: None
 34         """
 35         self.browser.get(self.url)
 36         username = self.wait.until(EC.presence_of_element_located((By.ID, 'loginName')))
 37         password = self.wait.until(EC.presence_of_element_located((By.ID, 'loginPassword')))
 38         submit = self.wait.until(EC.element_to_be_clickable((By.ID, 'loginAction')))
 39         username.send_keys(self.username)
 40         password.send_keys(self.password)
 41         submit.click()
 42 
 43     def get_position(self):
 44         """
 45         获取验证码位置
 46         :return: 验证码位置元组
 47         """
 48         try:
 49             img = self.wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'patt-shadow')))
 50         except TimeoutException:
 51             print('未出现验证码')
 52             self.open()
 53         time.sleep(2)
 54         location = img.location
 55         size = img.size
 56         top, bottom, left, right = location['y'], location['y'] + size['height'], location['x'], location['x'] + size[
 57             'width']
 58         return (top, bottom, left, right)
 59 
 60     def get_screenshot(self):
 61         """
 62         获取网页截图
 63         :return: 截图对象
 64         """
 65         screenshot = self.browser.get_screenshot_as_png()
 66         screenshot = Image.open(BytesIO(screenshot))
 67         return screenshot
 68 
 69     def get_image(self, name='captcha.png'):
 70         """
 71         获取验证码图片
 72         :return: 图片对象
 73         """
 74         top, bottom, left, right = self.get_position()
 75         print('验证码位置', top, bottom, left, right)
 76         screenshot = self.get_screenshot()
 77         captcha = screenshot.crop((left, top, right, bottom))
 78         captcha.save(name)
 79         return captcha
 80 
 81     def is_pixel_equal(self, image1, image2, x, y):
 82         """
 83         判断两个像素是否相同
 84         :param image1: 图片1
 85         :param image2: 图片2
 86         :param x: 位置x
 87         :param y: 位置y
 88         :return: 像素是否相同
 89         """
 90         # 取两个图片的像素点
 91         pixel1 = image1.load()[x, y]
 92         pixel2 = image2.load()[x, y]
 93         threshold = 20
 94         if abs(pixel1[0] - pixel2[0]) < threshold and abs(pixel1[1] - pixel2[1]) < threshold and abs(
 95                 pixel1[2] - pixel2[2]) < threshold:
 96             return True
 97         else:
 98             return False
 99 
100     def same_image(self, image, template):
101         """
102         识别相似验证码
103         :param image: 待识别验证码
104         :param template: 模板
105         :return:
106         """
107         # 相似度阈值
108         threshold = 0.99
109         count = 0
110         for x in range(image.width):
111             for y in range(image.height):
112                 # 判断像素是否相同
113                 if self.is_pixel_equal(image, template, x, y):
114                     count += 1
115         result = float(count) / (image.width * image.height)
116         if result > threshold:
117             print('成功匹配')
118             return True
119         return False
120 
121     def detect_image(self, image):
122         """
123         匹配图片
124         :param image: 图片
125         :return: 拖动顺序
126         """
127         for template_name in listdir(TEMPLATES_FOLDER):
128             print('正在匹配', template_name)
129             template = Image.open(TEMPLATES_FOLDER + template_name)
130             if self.same_image(image, template):
131                 # 返回顺序
132                 numbers = [int(number) for number in list(template_name.split('.')[0])]
133                 print('拖动顺序', numbers)
134                 return numbers
135 
136     def move(self, numbers):
137         """
138         根据顺序拖动
139         :param numbers:
140         :return:
141         """
142         # 获得四个按点
143         circles = self.browser.find_elements_by_css_selector('.patt-wrap .patt-circ')
144         dx = dy = 0
145         for index in range(4):
146             circle = circles[numbers[index] - 1]
147             # 如果是第一次循环
148             if index == 0:
149                 # 点击第一个按点
150                 ActionChains(self.browser).move_to_element_with_offset(circle, circle.size['width'] / 2, circle.size['height'] / 2).click_and_hold().perform()
151             else:
152                 # 小幅移动次数
153                 times = 30
154                 # 拖动
155                 for i in range(times):
156                     ActionChains(self.browser).move_by_offset(dx / times, dy / times).perform()
157                     time.sleep(1 / times)
158             # 如果是最后一次循环
159             if index == 3:
160                 # 松开鼠标
161                 ActionChains(self.browser).release().perform()
162             else:
163                 # 计算下一次偏移
164                 dx = circles[numbers[index + 1] - 1].location['x'] - circle.location['x']
165                 dy = circles[numbers[index + 1] - 1].location['y'] - circle.location['y']
166 
167     def crack(self):
168         """
169         破解入口
170         :return:
171         """
172         self.open()
173         # 获取验证码图片
174         image = self.get_image('captcha.png')
175         numbers = self.detect_image(image)
176         self.move(numbers)
177         time.sleep(10)
178         print('识别结束')
179 
180 
181 if __name__ == '__main__':
182     crack = CrackWeiboSlide()
183     crack.crack()

 

错误提示:

最后,本节代码来自:https://github.com/Python3WebSpider/CrackWeiboSlide

 

posted @ 2018-08-21 13:18  迷途纸鸢  阅读(865)  评论(0编辑  收藏  举报