爬虫:通过滑动或者点触验证码的方法及实现(点触+滑动)
一、极验验证码(geetest)的三种验证模式
以极验官网登录(https://auth.geetest.com/login/)为例:
在点击 “点击按钮进行验证”按钮后,会出现三种情况
1.点击直接通过
在一段时间的前几次登录时,点击按钮,会直接通过验证:
2.点击出现滑动验证码
登录几次后,再次登录时,就会出现滑动验证码:
3.点击出现点触验证码:
登录过多时,就会出现点触验证码,这也是极验最难的一种验证码:
二、分析
利用Python的selenium模块进行自动登录。
1.判断是否通过验证:
有一个类名为geetest_success_radar_tip的div标签,在加载后内容为空,验证成功后,内容变为“验证成功”,可以通过其内容来判断,是否验证成功。
下面是我截取的自己写的一段代码,主要是表达出含义,没有截取所有代码,理解就好:
self.success = self.browser.find_element_by_css_selector('.geetest_success_radar_tip') # 获取显示结果的标签 def is_success(self): time.sleep(1) if self.success.text == "验证成功": return True else: return False
2.点击直接通过
在点击“点击验证按钮”后,对是否验证成功进行一次判断,如果验证通过,则可以进行下一步动作。
3.滑动验证
滑动验证的标签是canvas,类名是geetest_canvas_slice,在点击“点击验证”后,如果是滑动验证,这个标签将会被加载,如果不是,则这个标签不会被加载。
所以可以通过查看是否存在这个标签,来判断验证是否是滑动验证:
def is_slide(self): time.sleep(1) try: slie_img = self.browser.find_element_by_css_selector('canvas.geetest_canvas_slice') if slie_img: return slie_img except NoSuchElementException: return False
如果是滑动验证,则可以通过比对有缺口图片和原图片,从而确定缺口的位置,然后模拟滑动滑块,从而达到验证的目的。具体操作,我在上一篇随笔中已经写过,请参考:
https://www.cnblogs.com/ohahastudy/p/11493971.html
4.点触验证
和滑动验证类似,点触验证可以通过判断是否存在类名为geetest_item_img的img标签来判断该验证为点触验证。
def is_pick(self): try: pick_img = self.browser.find_element_by_css_selector('img.geetest_item_img') return pick_img except NoSuchElementException: return False
保存该图片后,我们可以发现,该图片实际上是由两部分组成:
图片中验证码的识别,我是通过超级鹰这个平台来识别的。按照超级鹰的文档,将图片发过去,并指明类型,在几秒之后会返回一个结果,内容含有需要识别模块的坐标,而其顺序也是按照上图中白色部分指定的顺序,可以说是非常贴心了。
获取坐标后,将坐标提取出来,然后通过模拟点击即可。
三、具体实现:
from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.wait import WebDriverWait from PIL import Image from six import BytesIO import time from selenium.webdriver import ActionChains from selenium.common.exceptions import NoSuchElementException import requests import chaojiying #超级鹰提供的模块,需要去官网下载 class Verifycode(): def __init__(self): self.browser = webdriver.Chrome() def get_url(self, url, user, password): self.browser.get(url) self.browser.maximize_window() wait = WebDriverWait(self.browser, 10) wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'geetest_radar_btn'))) user_input, pwd_input, *_ = self.browser.find_elements_by_css_selector('input.ivu-input') print(user_input) btn = self.browser.find_element_by_css_selector('.geetest_radar_btn') user_input.send_keys(user) pwd_input.send_keys(password) btn.click() time.sleep(1) self.success = self.browser.find_element_by_css_selector('.geetest_success_radar_tip') # 获取显示结果的标签 def is_success(self): time.sleep(1) if self.success.text == "验证成功": return True else: return False def get_position(self, img_label): location = img_label.location size = img_label.size top, bottom, left, right = location['y'], location['y'] + size['height'], location['x'], location['x'] + size[ 'width'] return (left, top, right, bottom) def get_screenshot(self): screenshot = self.browser.get_screenshot_as_png() f = BytesIO() f.write(screenshot) return Image.open(f) def get_position_scale(self, screen_shot): height = self.browser.execute_script('return document.documentElement.clientHeight') width = self.browser.execute_script('return document.documentElement.clientWidth') x_scale = screen_shot.size[0] / (width + 10) y_scale = screen_shot.size[1] / (height) return (x_scale, y_scale) def get_slideimg_screenshot(self, screenshot, position, scale): x_scale, y_scale = scale position = [position[0] * x_scale, position[1] * y_scale, position[2] * x_scale, position[3] * y_scale] return screenshot.crop(position) def compare_pixel(self, img1, img2, x, y): pixel1 = img1.load()[x, y] pixel2 = img2.load()[x, y] threshold = 50 if abs(pixel1[0] - pixel2[0]) <= threshold: if abs(pixel1[1] - pixel2[1]) <= threshold: if abs(pixel1[2] - pixel2[2]) <= threshold: return True return False def compare(self, full_img, slice_img): left = 65 for i in range(full_img.size[0]): for j in range(full_img.size[1]): if not self.compare_pixel(full_img, slice_img, i, j): return i return left def get_track(self, distance): """ 根据偏移量获取移动轨迹 :param distance: 偏移量 :return: 移动轨迹 """ # 移动轨迹 track = [] # 当前位移 current = 0 # 减速阈值 mid = distance * 4 / 5 # 计算间隔 t = 0.2 # 初速度 v = 0 while current < distance: if current < mid: # 加速度为正 2 a = 4 else: # 加速度为负 3 a = -3 # 初速度 v0 v0 = v # 当前速度 v = v0 + at v = v0 + a * t # 移动距离 x = v0t + 1/2 * a * t^2 move = v0 * t + 1 / 2 * a * t * t # 当前位移 current += move # 加入轨迹 track.append(round(move)) return track def move_to_gap(self, slider, tracks): """ 拖动滑块到缺口处 :param slider: 滑块 :param tracks: 轨迹 :return: """ ActionChains(self.browser).click_and_hold(slider).perform() for x in tracks: ActionChains(self.browser).move_by_offset(xoffset=x, yoffset=0).perform() time.sleep(0.5) ActionChains(self.browser).release().perform() def is_slide(self): time.sleep(1) try: slie_img = self.browser.find_element_by_css_selector('canvas.geetest_canvas_slice') if slie_img: return slie_img except NoSuchElementException: return False def slide_code(self): slice_img_label = self.browser.find_element_by_css_selector('div.geetest_slicebg') # 找到滑动图片标签 self.browser.execute_script( "document.getElementsByClassName('geetest_canvas_slice')[0].style['display'] = 'none'") # 将小块隐藏 full_img_label = self.browser.find_element_by_css_selector('canvas.geetest_canvas_fullbg') # 原始图片的标签 position = self.get_position(slice_img_label) # 获取滑动验证图片的位置 screenshot = self.get_screenshot() # 截取整个浏览器图片 position_scale = self.get_position_scale(screenshot) # 获取截取图片宽高和浏览器宽高的比例 slice_img = self.get_slideimg_screenshot(screenshot, position, position_scale) # 截取有缺口的滑动验证图片 self.browser.execute_script( "document.getElementsByClassName('geetest_canvas_fullbg')[0].style['display'] = 'block'") # 显示原图 screenshot = self.get_screenshot() # 获取整个浏览器图片 full_img = self.get_slideimg_screenshot(screenshot, position, position_scale) # 截取原图 self.browser.execute_script( "document.getElementsByClassName('geetest_canvas_slice')[0].style['display'] = 'block'") # 将小块重新显示 left = self.compare(full_img, slice_img) # 将原图与有缺口图片进行比对,获得缺口的最左端的位置 left = left / position_scale[0] # 将该位置还原为浏览器中的位置 slide_btn = self.browser.find_element_by_css_selector('.geetest_slider_button') # 获取滑动按钮 track = self.get_track(left) # 获取滑动的轨迹 self.move_to_gap(slide_btn, track) # 进行滑动 time.sleep(2) def is_pick(self): try: pick_img = self.browser.find_element_by_css_selector('img.geetest_item_img') return pick_img except NoSuchElementException: return False def pick_code(self): time.sleep(1) pick_img_label = self.browser.find_element_by_css_selector('img.geetest_item_img') #获取点触图片标签 src = pick_img_label.get_attribute('src') #获取点触图片链接 img_content = requests.get(src).content #获取图片二进制内容 f = BytesIO() f.write(img_content) img0 = Image.open(f) #将图片以文件的形式打开,主要是为了获取图片的大小 scale = [pick_img_label.size['width'] / img0.size[0], pick_img_label.size['height'] / img0.size[1]] #获取图片与浏览器该标签大小的比例 cjy = chaojiying.Chaojiying_Client('*******', '******', '901489') #登录超级鹰 result = cjy.PostPic(img_content, '9005') #发送图片并获取结果 if result['err_no'] == 0: #对结果进行分析 position = result['pic_str'].split('|') # position = ['110,234','145,247','25,185'] position = [[int(j) for j in i.split(',')] for i in position] # position = [[110,234],[145,247],[25,185]] for items in position: #模拟点击 ActionChains(self.browser).move_to_element_with_offset(pick_img_label, items[0] * scale[0], items[1] * scale[1]).click().perform() time.sleep(1) certern_btn = self.browser.find_element_by_css_selector('div.geetest_commit_tip') certern_btn.click() return cjy,result if __name__ == '__main__': verifycode = Verifycode() verifycode.get_url('https://gtaccount.geetest.com/', '11', '11') if verifycode.is_success(): print('success') elif verifycode.is_slide(): verifycode.slide_code() if verifycode.is_success(): print('slide success') else: print('slide failure') elif verifycode.is_pick(): cjy,result = verifycode.pick_code() if verifycode.is_success(): print('pick click success') else: print('pick click failure') if result['err_no'] == 0: r = cjy.ReportError(result['pic_id']) print(r)