极验验证码的处理

碰到极验验证码的问题

参考文章:
Python爬虫 | 滑动验证码破解
但是该文章中的滑动轨迹不适应我的项目,之后参考
破解极验(geetest)验证码

整个处理的流程:

  1. 分析使用selenium模拟操作,直到弹出极验验证码
  2. 对验证码进行切图处理,共三张图片(有可能需要进行js操作)
  3. 进行图片像素比对,这里有两个参数可能需要进行调整:(1) 比对像素时可以跳过的像素列数(如60)
    (2) 像素误差容忍值(这里是60,未修改)
  4. 根据项目对滑块需要滑动的距离进行调整
  5. 计算滑动轨迹(我这里使用比较老版本的方案反而奏效了,也可能是前一篇中最终的滑动距离不对)
  6. 通过验证码之后需要进行的操作

在工作过程中碰到极验验证码的问题,以下是处理的代码

# -*- coding: UTF-8 -*-
# Date   : 2020/6/1 10:12
# Editor : gmj
# Desc   :
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.common.by import By
from selenium.webdriver import ActionChains
from selenium import webdriver
from PIL import Image
from io import BytesIO
import time
import random

login_url = '登录网址'
TEL = ''  # 用户名
PASSWORD = ''  # 密码



class CrackGeetest():
    def __init__(self, login_url, username, password):
        self.url = login_url
        self.browser = webdriver.Chrome()
        self.wait = WebDriverWait(self.browser, 10)
        self.username = username
        self.password = password

    def open(self):
        """
        打开浏览器,并输入用户名和密码,需要根据具体的网站来登录直到弹出验证码
        :return:None
        """
        self.browser.get(self.url)
        self.browser.maximize_window()
        try:
            login = self.browser.find_element_by_xpath(
                "//a[text()='快速登录']/..")
            login.click()
            phone = self.browser.find_element_by_xpath("//input[@name='mobile']")
            phone.send_keys(self.username)
            time.sleep(1)
            password = self.browser.find_element_by_xpath("//input[@name='password']")
            password.send_keys(self.password)
            time.sleep(1)
            submit = self.browser.find_element_by_xpath("//span[text()='一周内自动登录']/../..")
            submit.click()
            submit = self.browser.find_element_by_xpath("//button[@type='submit']")
            submit.click()
            time.sleep(0.5)
            # cookies = self.browser.get_cookies()
        except Exception as e:
            raise e

    def get_geetest(self, button_xpath):
        """
        点击按钮,弹出没有缺口的图片
        我这个项目不需要用到此方法
        """
        button = self.wait.until(self.browser.find_element_by_xpath(button_xpath))
        button.click()

    def __del__(self):
        self.browser.close()

    def get_position(self, xpath_path):
        """
        获取验证码位置
        :return:验证码位置元组
        """
        img = self.wait.until(self.browser.find_element_by_xpath(xpath_path))
        time.sleep(1)
        # location属性可以返回该图片对象(既这张图片)在浏览器中的位置,以字典的形式返回,
        # {‘x’:30,‘y’:30} 这里我们图片的位置是(30,30)。
        # 坐标轴是以屏幕左上角为原点,x轴向右递增,y轴像下递增。(相对整个html的坐标)
        location = img.location
        # size属性同样返回一个字典,{‘height’:30,‘width’:30 } 即图片对象的高度,宽度。
        # 通过sizes属性获取大小
        size = img.size
        # 通过location和size获取上下左右
        top, bottom, left, right = location['y'], location['y'] + size['height'], location['x'], location['x'] + size[
            'width']
        return (top, bottom, left, right)

    def get_screenshot(self):
        """
        获取网页截图
        :return: 截图对象
        """
        png = self.browser.get_screenshot_as_png()
        screenshot = Image.open(BytesIO(png))  # 转换为字节对象
        return screenshot

    def get_geetest_image(self, name='captcha.jpg', xpath_path=''):
        """
        获取验证码图片
        :return: 图片对象
        """
        top, bottom, left, right = self.get_position(xpath_path)  # 定位到这个位置之后,再截图
        # print('验证码位置', top, bottom, left, right)
        screenshot = self.get_screenshot()  # 获取到截图
        # Image.crop() 从图像中提取出某个矩形大小的图像。它接收一个四元素的元组作为参数,各元素为(left, upper, right, lower),坐标系统的原点(0, 0)是左上角。
        captcha = screenshot.crop((top, bottom, left, right))  # 因为上面截图,截取到的是整个页面,现在只把验证码图片取到
        captcha.save(name)  # 把图片存储起来,name是传进来的变量
        return captcha

    def get_slider(self):
        """
        获取滑块 每个项目不一样,需要进行处理
        :return: 滑块对象
        """
        # slider = self.wait.until(self.browser.find_element_by_xpath(slider_xpath))
        slider = self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'div.geetest_slider_button')))
        return slider

    def is_pixel_equal(self, img1, img2, x, y):
        """
        判断两个像素是否相同
        :param image1: 图片1
        :param image2: 图片2
        :param x: 位置x
        :param y: 位置y
        :return: 像素是否相同
        """
        # 取两个图片的像素点 (img.load()[x, y]获取某一点的像素值)
        pixel1 = img1.load()[x, y]  # 上面的 i 和 j
        pixel2 = img2.load()[x, y]
        threshold = 60  # 控制误差
        if (abs(pixel1[0] - pixel2[0] < threshold) and abs(pixel1[1] - pixel2[1] < threshold)  # 0 1 2 是RGB的三种颜色
                and abs(pixel1[2] - pixel2[2] < threshold)):
            return True
        else:  # 有一个超过阈值,则认为2个像素点不一致
            return False

    def get_gap(self, img1, img2):
        """
        获取缺口偏移量
        :param img1: 不带缺口图片
        :param img2: 带缺口图片
        :return:
        """
        # 从图片的60处开始往右,这样就可以把被拖动滑块跳过去,因为被拖动滑块处像素也不一样,相当于从60开始,一列列做对比
        left = 60
        for i in range(left, img1.size[0]):  # i 相当于横坐标      size属性是一个列表,下标0是横坐标(宽,图片的最右边),下标1是纵坐标
            for j in range(img1.size[1]):  # j 相当于纵坐标      从0开始
                if not self.is_pixel_equal(img1, img2, i, j):
                    # 如果相同位置的像素不一致,则替换为i
                    left = i
                    return left
        return left

    def get_track(self, distance):
        '''
        根据缺口的位置模拟x轴移动的轨迹,需要进行测试,适应自己的项目
        '''
        list = []
        # 间隔通过随机范围函数来获得
        x = random.randint(5, 10)
        while distance - x >= 5:
            list.append(x)
            distance = distance - x
            x = random.randint(4, 6)
        for i in range(distance):
            list.append(1)
        return list

    def get_track2(self, distance):
        '''
        根据缺口的位置模拟x轴移动的轨迹,需要进行测试,适应自己的项目
        '''
        # 6步不能通过,进行7步处理
        old_distance = distance
        # print(distance)
        my_list = []
        # 间隔通过随机范围函数来获得
        if distance > 40:
            kd = int(distance * 4 / 5)
            my_list.append(int(kd / 3 - 10))
            my_list.append(int(kd / 3))
            my_list.append(int(kd / 3 + 10))
            distance = distance - kd
        my_list.append(int(distance / 4) + 2)
        my_list.append(int(distance / 4))
        my_list.append(int(distance / 4))
        my_list.append(old_distance - sum(my_list))
        # print(sum(my_list), old_distance)
        # print(my_list)
        return my_list

    def move_to_gap(self, slider, track):
        """
        拖动滑块到缺口处
        :param slider: 滑块
        :param track: 轨迹
        :return:
        """
        ActionChains(self.browser).click_and_hold(slider).perform()  # 利用动作链,获取slider,perform是
        for x in track:
            ActionChains(self.browser).move_by_offset(xoffset=x, yoffset=0).perform()  # xoffset横坐标,yoffset纵坐标。使得鼠标向前推进
        time.sleep(0.3)  # 推动到合适位置之后,暂停一会
        ActionChains(self.browser).release().perform()  # 抬起鼠标左键

    def after_geetest(self):
        """
        在极验验证码之后做的事
        :return:
        """
        pass

    def get_ele_by_css(self, css_):
        return self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, css_)))

    def get_ele_by_xpath(self, xpath_path):
        return self.wait.until(self.browser.find_element_by_xpath(xpath_path))

    def split_picture_of_element_from_screen(self, pic_name, element, need_print=False):
        try:
            pic_url = self.browser.save_screenshot('.\\now_screen.png')
            # 获取元素位置信息
            left = element.location['x']
            top = element.location['y']
            right = left + element.size['width']
            bottom = top + element.size['height']
            if need_print:
                print("%s:截图成功!" % pic_url)
                print('图:' + pic_name)
                print('Left %s' % left)
                print('Top %s' % top)
                print('Right %s' % right)
                print('Bottom %s' % bottom)
            im = Image.open('.\\now_screen.png')
            im = im.crop((left, top, right, bottom))  # 元素裁剪
            im.save(pic_name)  # 元素截图
        except BaseException as msg:
            print("%s:截图失败!" % msg)

    def crack(self):
        # todo 在弹出验证码之前需要处理的在open函数中
        self.open()
        # 弹出验证码后向下执行
        time.sleep(0.5)
        c_background = self.get_ele_by_css('canvas.geetest_canvas_bg.geetest_absolute')
        c_slice = self.get_ele_by_css('canvas.geetest_canvas_slice.geetest_absolute')
        c_full_bg = self.get_ele_by_css('canvas.geetest_canvas_fullbg.geetest_fade.geetest_absolute')
        destination = 'destination.png'
        slice_name = 'slice.png'
        background = 'background.png'

        self.browser.execute_script("arguments[0].style=arguments[1]", c_slice, "display: none;")
        self.split_picture_of_element_from_screen(destination, c_background)

        self.browser.execute_script("arguments[0].style=arguments[1]", c_slice, "display: block;")
        self.split_picture_of_element_from_screen(slice_name, c_slice)

        self.browser.execute_script("arguments[0].style=arguments[1]", c_full_bg, "display: block;")
        self.split_picture_of_element_from_screen(background, c_full_bg)
        background_image = Image.open(f'.\\{background}')
        destination_image = Image.open(f'.\\{destination}')
        # 距离根据多次试验,进行修正
        distance = self.get_gap(background_image, destination_image) - 5
        print('dis:', distance)

        trace = self.get_track(distance)
        # todo 需要调整滑块的筛选样式
        slider = self.get_slider()
        self.move_to_gap(slider, trace)
        # todo 进行之后的处理
        time.sleep(5)
        cookie = self.browser.get_cookies()
        print(cookie)


if __name__ == '__main__':
    print('开始验证')
    crack = CrackGeetest(login_url, TEL, PASSWORD)
    crack.crack()
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值