提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
最近在做电商爬虫, 电商爬虫呢 有一个很重要的反扒措施。 就是登录, 而登录最常见的就是滑动验证码。 滑动验证码又可以简单分为两种:
通过调整 js可以获取到验证码原图 这种方式 虽然我没做过, 思路还是比较简单的, 通过遍历比较两种图片的像素点, 得到不一样的地方 就可以确定缺口位置没有办法得到原图 这种方式就比较麻烦了, 也是我写这篇文章的前提了。 当然我也是参考别人的代码, 写出来的,还不懂原理。本片文章只给代码, 自己找网站实验区
代码如下(示例):
import json import time # 图像处理标准库 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 目录文件 import 类 ''' 无头模式可以成功 ''' class Login(object): def __init__(self): # self.display = Display(visible=0, size=(800, 800)) # self.display.start() # 创建一个参数对象,用来控制chrome以无界面模式打开 chrome_options = webdriver.ChromeOptions() chrome_options.add_argument('--headless') # # 浏览器不提供可视化页面 chrome_options.add_argument('--disable-gpu') # 禁用GPU加速,GPU加速可能会导致Chrome出现黑屏,且CPU占用率高达80%以上 chrome_options.add_argument('--no-sandbox') # chrome_options.add_argument('--disable-gpu') chrome_options.add_argument('--disable-dev-shm-usage') self.browser = webdriver.Chrome(chrome_options=chrome_options) # self.browser = webdriver.Chrome() self.wait = WebDriverWait(self.browser, 20) self.url = '登录网址' self.sli = Code() # 重试次数 self.count = 6 def login(self): # 请求网址 self.browser.get(self.url) # 点击输入密码界面 login_status = self.wait.until( Ec.presence_of_element_located((By.XPATH, '//div[@class="switch-type"]')) ) login_status.click() print('点击输入密码界面') # time.sleep(1) # 输入账号 username = self.wait.until( Ec.element_to_be_clickable((By.XPATH, '//input[@class="zent-input"]')) ) username.send_keys('账号') print('账号已输入') # time.sleep(1) # 输入密码 password = self.wait.until( Ec.element_to_be_clickable((By.XPATH, '//input[@name="password"]')) ) password.send_keys('密码') print('密码已输入') # 登录框 submit = self.wait.until( Ec.element_to_be_clickable((By.XPATH, '//button[@type="submit"]')) ) submit.click() print('点击登录按钮') time.sleep(3) def login_chk(self): ''' 滑动验证码解决 ''' k = 1 # while True: while k < self.count: # 获取滑动前页面的url网址 start_url = self.browser.current_url # 1. 获取原图 bg_img = self.wait.until( Ec.presence_of_element_located((By.XPATH, '//div[@class="bg"]/img')) ) # 获取滑块链接 front_img = self.wait.until( Ec.presence_of_element_located( (By.XPATH, "//div[@class='block']/img"))) # 获取验证码滑动距离 distance = self.sli.get_element_slide_distance(front_img, bg_img) print('滑动距离是', distance) # 2. 乘缩放比例, -去 滑块前面的距离 下面给介绍 distance = distance * 280 / 560 - 25 print('实际滑动距离是', distance) # 滑块对象 element = self.browser.find_element_by_xpath( '//div[@class="slide-block"]') # 滑动函数 self.sli.slide_verification(self.browser, element, distance) # TODO 验证是否通过滑块 # 滑动之后的url链接 time.sleep(2) end_url = self.browser.current_url if start_url == end_url: print('第%s次验证失败...' % k, '\n') k = k + 1 else: # 看是否能获取登陆成功后的网页内容 success = self.wait.until( Ec.element_to_be_clickable( (By.XPATH, '//*[@id="js-react-container"]/div/div/div[2]/div[2]/div[2]/div')) ) # success = self.browser.find_element_by_xpath( # '//*[@id="js-react-container"]/div/div/div[2]/div[2]/div[2]/div') if success: success.click() print('登录成功') self.get_cookies() print(self.cookies) else: print('失败') break def get_cookies(self): ''' 登录成功后 保存账号的cookies :return: ''' cookies = self.browser.get_cookies() self.cookies = '' for cookie in cookies: self.cookies += '{}={};'.format(cookie.get('name'), cookie.get('value')) def run(self): # # 1.输入账号密码 self.login() # # 2. 解决滑动验证码 self.login_chk() def __del__(self): self.browser.close() print('界面关闭') # self.display.stop()获取实际滑动距离
这是一张验证码底图: 560 * 316 是他的原始图片大小 280 * 158 是实际在页面上展示的大小
红线部分是需要减去的滑动距离, 通过画图或者截图工具得出
所以实际滑动距离是 原始滑动距离 * 缩放比例 - 前缀距离
不BB了,直接给代码,该安装的包安装上去
import os import random import time import cv2 import numpy as np import requests from selenium.webdriver.common.action_chains import ActionChains class Code(): ''' 滑动验证码破解 ''' def __init__(self, slider_ele=None, background_ele=None, count=1, save_image=False): ''' :param slider_ele: :param background_ele: :param count: 验证重试次数 :param save_image: 是否保存验证中产生的图片, 默认 不保存 ''' self.count = count self.save_images = save_image self.slider_ele = slider_ele self.background_ele = background_ele def get_slide_locus(self, distance): distance += 8 v = 0 m = 0.3 # 保存0.3内的位移 tracks = [] current = 0 mid = distance * 4 / 5 while current <= distance: if current < mid: a = 2 else: a = -3 v0 = v s = v0 * m + 0.5 * a * (m ** 2) current += s tracks.append(round(s)) v = v0 + a * m # 由于计算机计算的误差,导致模拟人类行为时,会出现分布移动总和大于真实距离,这里就把这个差添加到tracks中,也就是最后进行一步左移。 # tracks.append(-(sum(tracks) - distance * 0.5)) # tracks.append(10) return tracks def slide_verification(self, driver, slide_element, distance): ''' :param driver: driver对象 :param slide_element: 滑块元祖 :type webelement :param distance: 滑动距离 :type: int :return: ''' # 获取滑动前页面的url网址 start_url = driver.current_url print('滑动距离是: ', distance) # 根据滑动的距离生成滑动轨迹 locus = self.get_slide_locus(distance) print('生成的滑动轨迹为:{},轨迹的距离之和为{}'.format(locus, distance)) # 按下鼠标左键 ActionChains(driver).click_and_hold(slide_element).perform() time.sleep(0.5) # 遍历轨迹进行滑动 for loc in locus: time.sleep(0.01) ActionChains(driver).move_by_offset(loc, random.randint(-5, 5)).perform() ActionChains(driver).context_click(slide_element) # 释放鼠标 ActionChains(driver).release(on_element=slide_element).perform() # # 判断是否通过验证,未通过下重新验证 # time.sleep(2) # # 滑动之后的yurl链接 # end_url = driver.current_url # if start_url == end_url and self.count > 0: # print('第{}次验证失败,开启重试'.format(6 - self.count)) # self.count -= 1 # self.slide_verification(driver, slide_element, distance) def onload_save_img(self, url, filename="image.png"): ''' 下载图片并保存 :param url: 图片网址 :param filename: 图片名称 :return: ''' try: response = requests.get(url) except Exception as e: print('图片下载失败') raise e else: with open(filename, 'wb') as f: f.write(response.content) def get_element_slide_distance(self, slider_ele, background_ele, correct=0): ''' 根据传入滑块, 和背景的节点, 计算滑块的距离 :param slider_ele: 滑块节点参数 :param background_ele: 背景图的节点 :param correct: :return: ''' # 获取验证码的图片 slider_url = slider_ele.get_attribute('src') background_url = background_ele.get_attribute('src') # 下载验证码链接 slider = 'slider.jpg' background = 'background.jpg' self.onload_save_img(slider_url, slider) self.onload_save_img(background_url, background) # 进行色度图片, 转化为numpy 中的数组类型数据 slider_pic = cv2.imread(slider, 0) background_pic = cv2.imread(background, 0) # 获取缺口数组的形状 width, height = slider_pic.shape[::-1] # 将处理之后的图片另存 slider01 = 'slider01.jpg' slider02 = 'slider02.jpg' background01 = 'background01.jpg' cv2.imwrite(slider01, slider_pic) cv2.imwrite(background01, background_pic) # 读取另存的滑块 slider_pic = cv2.imread(slider01) # 进行色彩转化 slider_pic = cv2.cvtColor(slider_pic, cv2.COLOR_BGR2GRAY) # 获取色差的绝对值 slider_pic = abs(255 - slider_pic) # 保存图片 cv2.imwrite(slider02, slider_pic) # 读取滑块 slider_pic = cv2.imread(slider02) # 读取背景图 background_pic = cv2.imread(background01) # 必脚两张图的重叠部分 result = cv2.matchTemplate(slider_pic, background_pic, cv2.TM_CCOEFF_NORMED) # 通过数组运算,获取图片缺口位置 top, left = np.unravel_index(result.argmax(), result.shape) # 背景图缺口坐标 print('当前滑块缺口位置', (left, top, left + width, top + height)) # 判读是否需求保存识别过程中的截图文件 if self.save_images: loc = [(left + correct, top + correct), (left + width - correct, top + height - correct)] self.image_crop(background, loc) else: # 删除临时文件 os.remove(slider01) os.remove(slider02) os.remove(background01) os.remove(background) os.remove(slider) # print('删除') # os.remove(slider) # 返回需要移动的位置距离 return left def image_crop(self, image, loc): cv2.rectangle(image, loc[0], loc[1], (7, 249, 151), 2) cv2.imshow('Show', image) # cv2.imshow('Show2', slider_pic) cv2.waitKey(0) cv2.destroyAllWindows()我也是参考别人的代码的出来的, 写文章之前 刚试验过 可以成功。