滑块验证码的破解和检测就像一对矛和盾,矛在不断变得锋利,盾也在不断变厚重,相爱相杀,此消彼长。正所谓道高一尺,魔高一丈,就看谁更技高一筹了。

目前 python 自动化,做滑块验证码的滑动一般常用的方法有以下这些。

selenium 

selenium 的 ActionChains 滑动很方便,但是这种方法很容易被检测出来。好多时候滑动明明到了正确的位置,却报错失败。对于简单的没有检测机制的网站来说是可以用的,检测机制稍微复杂一些的就只有望洋兴叹了。

pyautogui

pyautogui 的滑动可以模拟人在界面按照像素点进行操作,被检测的几率减少了很多。虽然有些不稳定,但是勉强可以接受。最致命的缺点在于,只要屏幕锁屏了,pyautogui 就失效了。就算添加了屏幕保持等机制,在退出远程时,仍然可能会出现屏幕分辨率的问题导致操作和截图达不到想要的结果。

javascript

当然,js 的语法很灵活,在 selenium 直接处理滑块或许不是很容易,但是可以执行 js 代码,来间接解决滑块滑动的检测机制。比如下面示例中的 随机分步操作、模拟人类延迟、添加随机抖动、添加微小调整等。可以很大程度减少被检测的几率。

下面示例的 js 代码片段是 AI 写的,但是验证了确实很香很好用!

def solve_captcha(self):
    try:
        try:
            WebDriverWait(self.driver, config.login_waiting_time).until(
                EC.visibility_of_element_located((By.XPATH, xpath.xpath_pdd_captcha_dialog)))
        except Exception as e:
            logger.info("没有出现图形验证码弹窗")
            return True
        # 等待元素加载
        xpath_bg_element = xpath.xpath_pdd_login_slide_img_bg
        WebDriverWait(self.driver, config.login_waiting_time).until(
            EC.visibility_of_element_located((By.XPATH, xpath_bg_element)))
        bg_element = self.driver.find_element(By.XPATH, xpath_bg_element)
        xpath_slider_element = xpath.xpath_pdd_login_slide_img_bg
        slider_element = self.driver.find_element(By.XPATH, xpath_slider_element)
        xpath_slider_button = xpath.xpath_pdd_login_slide_btn
        slider_button = self.driver.find_element(By.XPATH, xpath_slider_button)
        track_element = self.driver.find_element(By.XPATH, '//div[@class="slide-handlebar"]')

        # 获取并处理图片
        bg_path, slider_path, bg_base64, slide_base64 = self.get_images(bg_element, slider_element)
        distance = self.captcha_recognize(bg_path, slider_path)
        logger.info(f"验证码接口识别的距离:{distance}")
        # 根据背景图的页面尺寸和实际尺寸调整缩放比
        bg_width = bg_element.size["width"]
        real_width = cv2.imread(bg_path).shape[1]
        scale = bg_width / real_width
        logger.info(f"本地背景图宽度:{real_width},页面背景图宽度:{bg_width},缩放比:{scale}")
        # 统一转换成页面的尺寸规格,这里的 delta 大略是滑块初始位置距离背景图左边缘的距离,所以滑动时要减掉这段距离
        delta = -5
        real_distance = int(distance) * scale + delta
        logger.info(f"真实滑动距离:{real_distance}")

        # 分段拖动滑块
        # track = self.get_track(real_distance)
        # self.slide_verify_by_track(slider_button, track)
        js_drag_script = """
        // 参数解构
        var slider = arguments[0];
        var track = arguments[1];
        var dragDistance = arguments[2];
        var steps = arguments[3]; //分步数量
        var jitterIntensity = arguments[4];// 抖动强度
        
        // 创建鼠标事件(添加更详细的事件参数)
        function createEvent(type, x, y) {
            var event = new MouseEvent(type, {
                'view': window,
                'bubbles': true,
                'cancelable': true,
                'composed': true,
                'detail': type === 'mousedown' ? 1 : 0,
                'screenX': x + window.screenX,
                'screenY': y + window.screenY,
                'clientX': x,
                'clientY': y
            });
            return event;
        }
        
        // 获取元素中心点坐标
        function getCenter(element) {
            var rect = element.getBoundingClientRect();
            return {
                x: rect.left + rect.width / 2,
                y: rect.top + rect.height / 2
            };
        }
        
        // 生成随机抖动
        function getRandomJitter(intensity) {
            return {
                x: (Math.random() * 2 - 1) * intensity, // -intensity到+irtensity之间的值
                y: (Math.random() * 2 - 1) * intensity * 0.5 // 垂直抖动幅度为水平的一半
            };
        }
        
        // 模拟人类操作延迟
        function humanDelay(min, max) {
            var delay = Math.random() * (max - min) + min;
            var start = Date.now();
            while (Date.now() - start < delay) {
                // 忙等待
            }
        }
        
        // 触发事件
        var startPos = getCenter(slider);
        slider.dispatchEvent(createEvent('mousedown', startPos.x, startPos.y));
        
        //添加初始延迟(模拟人手抓住滑块后的停顿)
        humanDelay(80, 150);
        
        //分步移动滑块(带随机抖动)
        var currentX = startPos.x;
        var stepSize = dragDistance / steps;
        
        
        for (var i = 1; i <= steps; i++) {
            //计算新的位置并添加随机抖动
            var jitter = getRandomJitter(jitterIntensity);
            var newX = startPos.x + stepSize * i + jitter.x;
            var newY = startPos.y + jitter.y;
            
            // 确保不会超出轨道边界
            var trackRect = track.getBoundingClientRect();
            if (newX < trackRect.left) newX = trackRect.left + 2;
            if (newX > trackRect.right) newX = trackRect.right - 2;
        
            //触发移动事件
            track.dispatchEvent(createEvent('mousemove', newX, newY));
            currentX = newX;
            
            //添加人类化延迟(随进度变化)
            if (i < steps * 0.2 || i > steps * 0.8) {
                // 开始和结束阶段移动较快
                humanDelay(5, 15);
            } else {
                // 中间阶段移动较慢(像人类犹豫)
                humanDelay(20, 40);
            }
            
            // 随机添加微小的额外抖动(10 % 的概率)
            if (Math.random() < 0.1) {
                var extraJitter = getRandomJitter(jitterIntensity * 0.5);
                track.dispatchEvent(createEvent('mousemove', currentX + extraJitter.x, startPos.y + extraJitter.y));
                humanDelay(5, 10);
            }
        }
        
        //添加释放前的微小调整(模拟人类精确定位)
        for (var j = 0; j < 3; j++) {
            var adjustment = getRandomJitter(jitterIntensity * 0.3);
            track.dispatchEvent(createEvent('mousemove', currentX + adjustment.x, startPos.y + adjustment.y));
            humanDelay(10, 20);
        }
        
        // 释放鼠标(添加随机偏移)
        var releaseOffset = {
            x: (Math.random() * 4 - 2),
            y: (Math.random() * 2 - 1)
        }
        
        track.dispatchEvent(createEvent('mouseup', currentX + releaseOffset.x, startPos.y + releaseOffset.y));
        
        // 返回最终位置
        return currentX;
        """
        jitter_intensity = random.uniform(2.0, 4.0)
        steps = random.randint(40, 70)
        self.driver.execute_script(js_drag_script, slider_button, track_element, real_distance, steps, jitter_intensity)
        time.sleep(random.uniform(0.5, 1.5))
        self.driver.execute_script(f"window.scrollBy(0, {random.randint(100, 300)});")
        return True
    except Exception as e:
        logger.info("滑块验证码处理报错")
        self.save_error_img()
        logger.info(traceback.format_exc())
        return False

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐