极验3.0滑验证码破解:selenium+计算滑动缺口坐标算法=80%正确率

极滑2.0的破解思路:
一. 模拟点击验证按钮
  直接用Selenium模拟点击按钮
二. 识别滑动缺口的位置
  2.0旧版本: 利用原图和缺口图对比检测方式来识别缺口的位置,通过遍历俩张图片,找出相同位置像素RGB差距超过此阀值的像素点,那么此像素点的位置就是缺口的位置。
  3.0新版本: 通过截屏获取带有缺口的验证码图片,通过验证码图片的像素进行识别,凹槽位置的RGB三个色素基本都是小于150, 通过遍历x的轴,如果x轴从左到右边如果有连续x_max/8.6个像素中的RGB中的三个色素都是小于150,那个该x就是缺口的左边的距离,下面将详细讲解这个。
三. 模拟拖动滑块
   模拟人的移动轨迹通过验证,一般是先加速后减速。


破解的重要点就是:识别滑动缺口的位置。
测试案例(凹槽像素x轴为80, y轴在45±15):
在这里插入图片描述
额外知识:全黑的RGB为(0,0,0), 全白的RGB(255,255,255)
我们通过分析像素的R、G、B来得出规律,我们打印一下坐标轴y=50,x轴上个点像素的RGB:

x  y    像素R、G、B
72 50 (126, 126, 125)
73 50 (219, 219, 219)
73 50 (219, 219, 219)
77 50 (165, 166, 164)
81 50 (80, 80, 80)
82 50 (76, 76, 75)
83 50 (58, 58, 56)
84 50 (101, 102, 100)
85 50 (124, 125, 122)
86 50 (118, 119, 116)
87 50 (105, 107, 103)
88 50 (96, 96, 94)
89 50 (82, 83, 81)
90 50 (77, 77, 77)
91 50 (77, 77, 77)
92 50 (83, 83, 83)
93 50 (104, 104, 104)
94 50 (135, 135, 135)
95 50 (128, 128, 128)
96 50 (85, 85, 85)
97 50 (84, 84, 84)
98 50 (139, 139, 138)
99 50 (139, 139, 137)
100 50 (139, 139, 139)
101 50 (123, 123, 123)
102 50 (83, 83, 83)
103 50 (91, 91, 91)
104 50 (141, 141, 141)
105 50 (140, 140, 140)
106 50 (135, 135, 135)
107 50 (98, 98, 98)
108 50 (73, 73, 73)
109 50 (119, 119, 119)
110 50 (140, 140, 140)
111 50 (127, 127, 127)
112 50 (85, 85, 85)
113 50 (88, 88, 88)
114 50 (137, 137, 137)
115 50 (135, 135, 135)
116 50 (133, 133, 133)
117 50 (132, 132, 132)
118 50 (129, 129, 129)
119 50 (123, 123, 123)
120 50 (87, 87, 87)
121 50 (57, 57, 57)
122 50 (145, 145, 145)
123 50 (234, 234, 234)

可以再尝试其他y轴的来进行分析,我们会发现,凹槽位置的像素点的R、G、B都是小于150,而且基本会连续40个x轴上的像素的R、G、B都是小于150,电脑屏幕大小不同,连续个数不同,基本都是连续image.size[0]/6.45个像素, 这样子我们就可以从这个思路写算法来找出从凹槽的x坐标。
算法: 向右边连续有30到50个像素的R、G、B都是小于150,这就是我们所想要的数据,也就是凹槽缺口的左位置:

def get_gap(image):
    """
    获取缺口偏移量
    :param image: 带缺口图片
    :return:
    """
    # left_list保存所有符合条件的x轴坐标
    left_list = []
    # 我们需要获取的是凹槽的x轴坐标,就不需要遍历所有y轴,遍历几个等分点就行
    for i in [10 * i for i in range(1, int(image.size[1]/11)]:
    	# x轴从x为image.size[0]/5.16的像素点开始遍历,因为凹槽不会在x轴为50以内
        for j in range(image.size[0]/5.16, image.size[0] - int(image.size[0]/8.6)):
            if is_pixel_equal(image, j, i, left_list):
                break
    return left_list


def is_pixel_equal(image, x, y, left_list):
    """
    判断两个像素是否相同
    :param image: 图片
    :param x: 位置x
    :param y: 位置y
    :return: 像素是否相同
    """
    # 取两个图片的像素点
    pixel1 = image.load()[x, y]
    threshold = 150
    # count记录一次向右有多少个像素点R、G、B都是小于150的
    count = 0
    # 如果该点的R、G、B都小于150,就开始向右遍历,记录向右有多少个像素点R、G、B都是小于150的
    if pixel1[0] < threshold and pixel1[1] < threshold and pixel1[2] < threshold:
        for i in range(x + 1, image.size[0]):
            piexl = image.load()[i, y]
            if piexl[0] < threshold and piexl[1] < threshold and piexl[2] < threshold:
                count += 1
            else:
                break
    if int(image.size[0]/8.6) < count < int(image.size[0]/4.3):
        left_list.append((x, count))
        return True
    else:
        return False

if __name__ == '__main__':
	# 这里的测试图片,需要读者自行更改
    image = Image.open("captcha1.png")
    image = image.convert("RGB")
    left_list = get_gap(image)
    print(left_list)

结果为:

[(80, 43), (76, 45), (80, 42), (80, 41)]

其中(x, z)中的x为凹槽左侧的位置,z是count,就是从该x点坐标起有多少连续像素点的R、G、B都是小于150的,因为我们遍历y轴,所有我们的得到几个值,其中,z值最接近40的,结果最符合。
我们可以用这个代码进行列表的排序,越接近40的越在前面:

left_list = sorted(left_list, key=lambd x: abs(x[1]-40))

结果变为:

[(80, 41), (80, 42), (80, 43), (76, 45)]

这里我们就去列表第一个元素中的x轴,这个x就是凹槽的左侧的x轴的下标,得到这个值,下面的内容就容易办了,我就直接上完整代码了喔,有什么不懂的可以留言给我

import time
from io import BytesIO
from selenium import webdriver
from selenium.webdriver import ActionChains
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from PIL import Image

EMAIL = 'cqc@cuiqingcai.com'
PASSWORD = '123456'
BORDER_1 = 7
BORDER_2 = 12


class CrackGeetest(object):

    def __init__(self):
        self.url = 'https://account.geetest.com/login'
        self.browser = webdriver.Chrome()
        self.browser.maximize_window()
        self.wait = WebDriverWait(self.browser, 5)
        self.email = EMAIL
        self.password = PASSWORD
        self.success = False
        self.try_num = 2
        self.now_num = 2
        self.flesh_num = 1

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

    # 获取初始验证按钮
    def get_geetest_button(self):
        """
        获取初始验证按钮
        :return:
        """
        button = self.wait.until(EC.element_to_be_clickable((By.CLASS_NAME, 'geetest_radar_tip')))
        return button

    # 获取截图中验证码的上下左右的位置
    def get_position(self):
        """
        获取验证码位置
        :return: 验证码位置元组
        """
        img = self.wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'geetest_canvas_img')))
        time.sleep(0.5)
        location = img.location
        size = img.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: 截图对象
        """
        screenshot = self.browser.get_screenshot_as_png()
        screenshot = Image.open(BytesIO(screenshot))
        return screenshot

    # 获取滑块对象
    def get_slider(self):
        """
        获取滑块
        :return: 滑块对象
        """
        try:
            slider = self.wait.until(EC.element_to_be_clickable((By.CLASS_NAME, 'geetest_slider_button')))
        except Exception:
            self.crack()
            return
        return slider

    # 获取验证码图片
    def get_geetest_image(self, name='captcha.png'):
        """
        获取验证码图片
        :return: 图片对象
        """
        top, bottom, left, right = self.get_position()
        screenshot = self.get_screenshot()
        captcha = screenshot.crop((left, top, right, bottom))
        captcha.save(name)
        return captcha

    # 打开页面,输入账号与密码
    def open(self):
        """
        打开网页输入用户名密码
        :return: None
        """
        self.browser.get(self.url)
        time.sleep(0.5)
        email = self.browser.find_elements_by_xpath("//i[@class='icon-email']/../../input")[0]
        password = self.browser.find_element_by_xpath("//i[@class='icon-password']/../../input")
        email.send_keys(self.email)
        password.send_keys(self.password)

    # 根据偏移量获取移动轨迹
    @staticmethod
    def get_track(distance):
        """
        根据偏移量获取移动轨迹
        :param distance: 偏移量
        :return: 移动轨迹
        """
        # 移动轨迹
        track = []
        # 当前位移
        current = 0
        # 减速阈值
        mid = distance * 4 / 5
        # 计算间隔
        t = 0.2
        # 初速度
        v = 0

        while current < distance:
            if current < mid:
                # 加速度为正2
                a = 2
            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, track):
        """
        拖动滑块到缺口处
        :param slider: 滑块
        :param track: 轨迹
        :return:
        """
        ActionChains(self.browser).click_and_hold(slider).perform()
        for x in track:
            ActionChains(self.browser).move_by_offset(xoffset=x, yoffset=0).perform()
        time.sleep(0.5)
        ActionChains(self.browser).release().perform()

    # 获取缺口偏移量
    def get_gap(self, image):
        """
        获取缺口偏移量
        :param image: 带缺口图片
        :return:
        """
        left_list = []
        for i in [10 * i for i in range(1, 14)]:
            for j in range(50, image.size[0] - 30):
                if self.is_pixel_equal(image, j, i, left_list):
                    break
        return left_list

    # 判断缺口偏移量
    @staticmethod
    def is_pixel_equal(image, x, y, left_list):
        """
        判断两个像素是否相同
        :param image: 图片
        :param x: 位置x
        :param y: 位置y
        :return: 像素是否相同
        """
        x_max = image.size[0]
        # 取两个图片的像素点
        pixel1 = image.load()[x, y]
        threshold = 150
        count = 0
        if pixel1[0] < threshold and pixel1[1] < threshold and pixel1[2] < threshold:
            for i in range(x + 1, image.size[0]):
                piexl = image.load()[i, y]
                if piexl[0] < threshold and piexl[1] < threshold and piexl[2] < threshold:
                    count += 1
                else:
                    break
        if int(x_max/8.6) < count < int(x_max/4.3):
            left_list.append((x, count))
            return True
        else:
            return False

    def crack(self):
        # 输入用户名密码
        self.open()

        # 点击验证按钮
        time.sleep(1)
        button = self.get_geetest_button()
        button.click()

        # BOREDER有俩种情况,一种是7,一种是14
        def slider_try(gap, BORDER):
            if self.now_num:
                # 减去缺口位置
                gap -= BORDER
                # 计算滑动距离
                track = self.get_track(int(gap))

                # 拖动滑块
                slider = self.get_slider()
                self.move_to_gap(slider, track)
                try:
                    self.success = self.wait.until(
                        EC.text_to_be_present_in_element((By.CLASS_NAME, 'geetest_success_radar_tip_content'), '验证成功'))
                except Exception as e:
                    self.now_num -= 1
                    test_num = self.try_num - self.now_num
                    if self.now_num == 0:
                        print("第%d次尝试失败, 验证失败" % test_num)
                    else:
                        print("验证失败,正在进行第%d次尝试" % test_num)

        while not self.success and self.now_num > 0:

            # 获取验证码图片
            try:
                image = self.get_geetest_image()
            except Exception as e:
                # todo: 判断是其他验证,或者是自动识别通过
                self.success = True
                print("自动识别通过,无需滑动%s" % e)
                time.sleep(5)
                return

            # 获取缺口位置
            left_list = self.get_gap(image)
            x_max = image.size[0]
            left_list = sorted(left_list, key=lambda x: abs(x[1]-int(x_max/6.45)))
            print(left_list)
            if not left_list:
                print("left_lsit为空, 无法获取gap")
                break
            gap = left_list[0][0]

            # 第一中请求,gap减少7
            slider_try(gap, BORDER_1)
            # 成功后退出
            if not self.success:
                # 尝试gap减少14
                slider_try(gap, BORDER_2)
            if self.success:
                test_num = self.try_num - self.now_num + 1
                print("第{}次刷新,第{}次尝试,验证通过".format(self.flesh_num, test_num))
                time.sleep(5)
                self.success = True

        if not self.success:
            print("重新刷新页面,这是第%d次刷新" % self.flesh_num)
            self.flesh_num += 1
            self.now_num = 2
            self.try_num = 2
            self.crack()


if __name__ == '__main__':
    crack = CrackGeetest()
    crack.crack()
    del crack

  • 5
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值