selenium 无原图滑动验证码解决

selenium 无原图滑动验证码解决

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

最近在做电商爬虫, 电商爬虫呢 有一个很重要的反扒措施。 就是登录, 而登录最常见的就是滑动验证码。
滑动验证码又可以简单分为两种:

  1. 通过调整 js可以获取到验证码原图
    这种方式 虽然我没做过, 思路还是比较简单的, 通过遍历比较两种图片的像素点, 得到不一样的地方 就可以确定缺口位置
  2. 没有办法得到原图
    这种方式就比较麻烦了, 也是我写这篇文章的前提了。 当然我也是参考别人的代码, 写出来的,还不懂原理。

本片文章只给代码, 自己找网站实验区

1. selenium获取坐标元素

代码如下(示例):

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 是实际在页面上展示的大小

在这里插入图片描述
红线部分是需要减去的滑动距离, 通过画图或者截图工具得出

所以实际滑动距离是 原始滑动距离 * 缩放比例 - 前缀距离

2.验证码处理

不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()

总结

我也是参考别人的代码的出来的, 写文章之前 刚试验过 可以成功。

  • 2
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值