python3爬虫之验证码的识别——selenium自动识别验证码并点击提交,附源代码

时间:2024-02-24 21:06:18

 

https://aq.yy.com/p/reg/account.do?appid=&url=&fromadv=udbclsd_r

yy语音的注册页面,账号、密码、重复密码及提交按钮的实现这里不再讲解,利用selenium非常容易实现

本文只讲解如何识别绿色框里图片中文字的识别,并使用鼠标正确点击

思路:

1. 利用爬虫技术将绿色图片下载到本地

2. 使用第三方工具(本文使用超级鹰)识别图片中的文字,并返回每个文字的坐标位置

3. 根据坐标位置,使用鼠标点击

这么一说是不是显得非常简单啦!那么就一步一步来

 

1. 搞到这个图片,此处有一坑!

爬虫所下载下来的图片是这样的,应该是将图片切片重新排列了,然后应该是有一定的算法可以重新排列回来

但是这个算法不太好找,并且对于其他类型的网站不一定是通用的

 

 那么我们换另外一种思路:既然原图搞不来,那么我们就利用截图!

如何截图呢?首先要定位这个元素并且获知其四个点的坐标位置

定位这个元素很简单,直接使用selenium定位class即可

image_element = browser.find_element_by_class_name(\'pw_subBg\')

下面的问题就是获取这个元素四个点的(x,y)坐标或者其他可以确定位置的坐标

 

通过location方法可以获取这个元素左上角坐标;通过size可以获取这个元素的宽(width)和高(height)

location = image_element.location
size = image_element.size
print(location,size)

输出结果:{\'x\': 108.5625, \'y\': 295} {\'height\': 128, \'width\': 272}

怎样计算呢?下面画出一幅坐标图就清楚明了了!

 

这里写出了所需位置的四个值(top, bottom, left, right),并且给出了计算公式,如果还看不明白那就帮不了你了诶……

 

有了位置坐标,再执行截图语句并保存图片到本地就ok了

screenshot = browser.get_screenshot_as_png()
screenshot = Image.open(BytesIO(screenshot))
captcha = screenshot.crop((left,top,right,bottom))
captcha.save(\'captcha1.png\')

 

验证一下保存的图片,好像截取的位置不太对,那么需要人工去设置一个偏移量

这个偏移量是我大概试出来的,暂没有研究一些好的自动测量方法

top, bottom, left, right = location[\'y\']+128, location[\'y\'] + size[\'height\']+128, location[\'x\']+181, location[\'x\'] + size[
    \'width\']+181 # 手动测试偏移量

修正后的结果:423 551 289.5625 561.5625

 

问了度娘,这个偏移量的大小和电脑分辨率、浏览器、是否是无头模式有关系。因此可能每个人运行程序所设置的偏移量都不一样

截图结果如下:(因为每次运行程序都刷新了页面,因此本例中验证码可能不一样)

 

 

 2. 调用第三方平台识别汉字并且返回所识别汉字的坐标

 前提是已经下好了超级鹰的demo,并调试成功。详见我的文章《》,此次不再详述

但是需要改一个地方就是识别类型,可以改成910x,这个是返回汉字坐标值的

 

bytes_array = BytesIO()
captcha.save(bytes_array,format(\'PNG\'))
chaojiying = Chaojiying(\'账户\', \'密码\', \'软件id\')
result = chaojiying.PostPic(bytes_array.getvalue(),9103)
print(result)

运行结果:{\'err_no\': 0, \'err_str\': \'OK\', \'pic_id\': \'2077320412830000020\', \'pic_str\': \'60,19|109,16|187,92\', \'md5\': \'c3d41675003cd44058347e591cf405e7\'}

 

看pic_str字段,返回了3个坐标值,用 | 隔开了,需要对这个字符串进行处理

locations = result.get(\'pic_str\').split(\'|\')
for i in locations:
    location = i.split(\',\')
    print(location)

 

3. 通过坐标值进行自动化点击操作

ActionChains(browser).move_to_element_with_offset(image_element,int(location[0]),int(location[1])).click().perform()

 

 当然,仅到这一步还是没有完成的

注册的需求还需要识别右上角的小图中的文字,和大图中的文字做匹配,匹配成功了才点击,不成功的不点击

这里有个坑就是:超级鹰返回的要么是识别的文字,要么是坐标信息,无法同时返回两者。那么,每次返回的是一一对应的吗?这个我们现在来验证一下(为了省积分,用a.jpg):

首先先运行下

    r1 = chaojiying.PostPic(im, 1902) 
    r2 = chaojiying.PostPic(im, 9004)  # 返回坐标

 运行结果:

r1 = {\'err_no\': 0, \'err_str\': \'OK\', \'pic_id\': \'3077320552830000021\', \'pic_str\': \'7261\', \'md5\': \'265c70b7f6d88426fa2a77a06f450972\'}
r2 = {\'err_no\': 0, \'err_str\': \'OK\', \'pic_id\': \'2077320552830000022\', \'pic_str\': \'11,22|28,20|47,21|71,21\', \'md5\': \'82948c8bf04e521

如果需要点击的数字或者汉字也是图片显示的,那么识别过程与大图一样;如果是直接给出了文本那就爬虫直接获取。这个步骤我省略了,这里假定需要点击数字7和2(无顺序)

def vs(list0,dict1,dict2):
    list1 = list(dict1.get(\'pic_str\'))
    list2 = dict2.get(\'pic_str\').split(\'|\')
    print(list1)
    print(list2)
    dd = dict(zip(list1,list2))
    print(dd)
    for i in list0:
        if i in dd:
            x = dd.get(i).split(\',\')[0]
            y = dd.get(i).split(\',\')[1]
            print(int(x),int(y))
            # 调用点击的模块

调用结果

28 20
71 21

完美的get到了需要点击数字的坐标

 


 

源代码(全)

  1 import time
  2 from io import BytesIO
  3 from PIL import Image
  4 from selenium import webdriver
  5 from selenium.webdriver import ActionChains
  6 from selenium.webdriver.common.by import By
  7 from selenium.webdriver.support.ui import WebDriverWait
  8 from selenium.webdriver.support import expected_conditions as EC
  9 from chaojiying import Chaojiying
 10 
 11 
 12 # 初始化变量
 13 
 14 EMAIL = \'diaongaodsing\'
 15 PASSWORD = \'mindomg301415\'
 16 REPASSWORD = PASSWORD
 17 
 18 # 超级鹰用户登录名、密码、软件ID、待识别的验证码类型
 19 CHAOJIYING_USERNAME = \'用户名\'
 20 CHAOJIYING_PASSWORD = \'密码\'
 21 CHAOJIYING_SOFT_ID = 软件ID
 22 CHAOJIYING_KIND_XY = 9103 # 返回坐标
 23 CHAOJIYING_KIND = 2003 #返回数字、字母或者汉字
 24 
 25 
 26 class CrackTouClick():
 27 
 28     def __init__(self):
 29         """
 30 
 31         """
 32         self.url = \'https://aq.yy.com/p/reg/account.do?appid=&url=&fromadv=udbclsd_r\' # 待爬取的页面
 33         self.browser = webdriver.Chrome()
 34         self.wait = WebDriverWait(self.browser, 20)
 35         self.input = EMAIL
 36         self.password = PASSWORD
 37         self.repassword = REPASSWORD
 38         self.chaojiying = Chaojiying(CHAOJIYING_USERNAME, CHAOJIYING_PASSWORD, CHAOJIYING_SOFT_ID)
 39     # def __del__(self):
 40     #     """
 41     #     析构函数,关闭浏览器
 42     #     """
 43     #     self.browser.close()
 44     def login(self):
 45         """
 46         打开网页输入用户名、密码和再次验证密码
 47         :return: None
 48         """
 49         self.browser.get(self.url)
 50         iframe = self.browser.find_elements_by_tag_name("iframe")[0]
 51         self.browser.switch_to.frame(iframe)
 52 
 53         input = self.browser.find_element_by_xpath(\'//*[@id="m_mainForm"]/div[2]/div[1]/span[2]/input\')
 54         password = self.browser.find_element_by_xpath(\'//*[@id="m_mainForm"]/div[2]/div[2]/span[2]/input\')
 55         repassword = self.browser.find_element_by_xpath(\'//*[@id="m_mainForm"]/div[2]/div[3]/span[2]/input\')
 56 
 57         input.send_keys(self.input)
 58         password.send_keys(self.password)
 59         repassword.send_keys(self.repassword)
 60         self.browser.find_element_by_class_name(\'field_title\').click()# 随便找一个地方单击一下,否则无法验证输入是否正确
 61     def get_image_element(self):
 62         """
 63         获取验证图片对象
 64         :return: 图片对象
 65         """
 66         image_element_b = self.browser.find_element_by_class_name(\'pw_subBg\') # 大图
 67         image_element_s = self.browser.find_element_by_class_name(\'pw_expic\') # 小图
 68         return image_element_s,image_element_b
 69     def get_position(self,image_element):
 70         """
 71         获取验证码位置
 72         :return: 验证码位置
 73         """
 74 
 75         time.sleep(2)
 76         location = image_element.location
 77         size = image_element.size
 78         top, bottom, left, right = location[\'y\'] + 128, location[\'y\'] +  size[\'height\'] + 128, location[\'x\'] + 181, location[\'x\'] + size[
 79             \'width\'] + 181
 80         return (top, bottom, left, right)
 81     def get_screen_image(self, image_element,name):
 82         """
 83         获取验证码截图图片
 84         :return: 图片对象
 85         """
 86         top, bottom, left, right = self.get_position(image_element)
 87         #print(\'图片位置\', top, bottom, left, right)
 88         screenshot = self.browser.get_screenshot_as_png()
 89         screenshot = Image.open(BytesIO(screenshot))
 90         captcha = screenshot.crop((left, top, right, bottom))
 91         captcha.save(str(name)+\'.png\')
 92         return captcha
 93 
 94     def get_recognation_result(self,image_element,chaojiying_kind):
 95         """
 96         用第三方平台超级鹰进行图片识别,返回识别结果
 97         :return: <dic> 识别结果(需要的字段是‘pic_str’)
 98         """
 99         image = self.get_screen_image(image_element,chaojiying_kind)
100         bytes_array = BytesIO()
101         image.save(bytes_array, format(\'PNG\'))
102         recognation_result = self.chaojiying.PostPic(bytes_array.getvalue(),chaojiying_kind)
103         print(\'识别结果:\',recognation_result)
104         return recognation_result
105 
106     # def click_points(self,image_element, recognation_result):
107     #     """
108     #     解析识别结果并进行点击
109     #     :param captcha_result: <dic>第三方识别结果
110     #     :return: None
111     #     """
112     #     locations = recognation_result.get(\'pic_str\').split(\'|\')
113     #     for i in locations:
114     #         location = i.split(\',\')
115     #         #print(location)
116     #         ActionChains(self.browser).move_to_element_with_offset(image_element, int(location[0]),int(location[1])).click().perform()
117     #         #print(\'ok\')
118 
119     def vs(self,image_element,res_s, res_b, res_b_xy):
120         """
121         对比,选出需要点击的汉字和坐标,并点击
122         :param image_element:
123         :param res_s: <dic> 小图识别结果(汉字)
124         :param res_b: <dic> 大图识别结果(汉字)
125         :param res_b_xy: <dic> 大图识别结果(坐标)
126         :return: None
127         """
128         list_res_s = list(res_s.get(\'pic_str\'))
129         list_res_b = list(res_b.get(\'pic_str\'))
130         list_res_b_xy = res_b_xy.get(\'pic_str\').split(\'|\')
131         #print(list_res_s)
132         # print(list_res_b)
133         # print(list_res_b_xy)
134         dic_res_b = dict(zip(list_res_b, list_res_b_xy))
135         print(\'字典格式:\',dic_res_b)
136         for i in list_res_s:
137             if i in dic_res_b:
138                 x = dic_res_b.get(i).split(\',\')[0]
139                 y = dic_res_b.get(i).split(\',\')[1]
140                 #print(int(x), int(y))
141                 ActionChains(self.browser).move_to_element_with_offset(image_element, int(x),int(y)).click().perform()
142 
143     def verify_info(self):
144         """
145         验证用户名、密码、再次密码是否符合规则,符合返回True,否则返回False
146         :return: <bool>
147         """
148         try:
149             input_v = self.browser.find_elements_by_class_name(\'icon_suc\')[0]
150             password_v = self.browser.find_elements_by_class_name(\'icon_suc\')[1]
151             repassword_v = self.browser.find_elements_by_class_name(\'icon_suc\')[2]
152             print(\'注册信息正确!\')
153             return True
154         except:
155             print(\'注册信息错误!\')
156             return False
157     def verify_recognation(self):
158         """
159         验证验证码是否正确,正确返回True,否则返回False
160         :return:
161         """
162         try:
163             self.wait.until(EC.text_to_be_present_in_element((By.CLASS_NAME, \'done_text\'), \'验证成功\'))
164             # 使用text_to_be_present_in_element方法不能使用find_element,因为发现一直在
165             print(\'验证码正确!\')
166             return True
167         except:
168             print(\'验证码错误!\')
169             return False
170     def get_verify_button(self):
171         """
172         获取验证“提交”按钮,并点击
173         :return: None
174         """
175         verify_button = self.wait.until(EC.element_to_be_clickable((By.CLASS_NAME, \'pw_submit\')))
176         verify_button.click()
177     def get_login_button(self):
178         """
179         获取“同意并注册账号”按钮,并点击
180         :return: None
181         """
182         submit_button = self.wait.until(EC.element_to_be_clickable((By.XPATH, \'//*[@id="m_mainForm"]/div[2]/div[7]/a/span\')))
183         submit_button.click()
184         print(\'登录成功\')
185 
186 
187     def crack(self):
188         """
189         破解入口
190         :return: None
191         """
192         self.login() # 登陆
193 
194         image_element_s = self.get_image_element()[0]# 获取小图
195         res_s = self.get_recognation_result(image_element_s,2003) # 获取第三方识别结果
196 
197         image_element_b = self.get_image_element()[1]# 获取大图
198         res_b = self.get_recognation_result(image_element_b,2006) # 获取第三方识别结果
199 
200         res_b_xy = self.get_recognation_result(image_element_b,9008) # 获取第三方识别结果
201 
202         self.vs(image_element_b,res_s,res_b,res_b_xy)
203 
204         self.get_verify_button() # 点击“验证”按钮
205         if self.verify_info() is True and self.verify_recognation() is True:
206             #如果信息符合规则且验证码正确,点击“注册”按钮
207             time.sleep(2)
208             self.get_login_button()
209 
210 
211 if __name__ == \'__main__\':
212     crack = CrackTouClick()
213     crack.crack()

 

 

测试了几次发现题分没了……赶紧去充钱,好在1元=1000题分

试了几次只有一次是完全正确的,但是单独识别某一个图是没有问题的呀!难道是我刷新的太快了吗……因为无法同时获取汉字和其坐标,导致两次返回的数量很有可能不一致!