自动化极验3点选验证码的识别与验证方案

python 复制代码
import time
import cv2
import numpy as np
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import requests
from PIL import Image
import io
import base64
import re

class GeetestClickVerifier:
    """
    极验3点选验证码识别与验证类
    支持文字点选、图标点选等验证类型
    """
    
    def __init__(self, driver_path='chromedriver'):
        """
        初始化验证器
        
        Args:
            driver_path: ChromeDriver路径
        """
        # 设置Chrome选项
        options = webdriver.ChromeOptions()
        options.add_argument('--disable-blink-features=AutomationControlled')
        options.add_experimental_option("excludeSwitches", ["enable-automation"])
        options.add_experimental_option('useAutomationExtension', False)
        
        # 初始化WebDriver
        self.driver = webdriver.Chrome(executable_path=driver_path, options=options)
        self.driver.execute_cdp_cmd('Page.addScriptToEvaluateOnNewDocument', {
            'source': '''
                Object.defineProperty(navigator, 'webdriver', {get: () => undefined})
            '''
        })
        self.wait = WebDriverWait(self.driver, 10)
        self.actions = ActionChains(self.driver)
        
    def load_page(self, url):
        """
        加载目标页面
        
        Args:
            url: 目标页面URL
        """
        self.driver.get(url)
        time.sleep(2)
        
    def detect_geetest_widget(self):
        """
        检测页面中的极验验证码组件
        
        Returns:
            dict: 包含验证码元素的字典
        """
        elements = {}
        
        # 查找极验验证码相关元素
        selectors = {
            'widget': '.geetest_btn',  # 触发验证的按钮
            'captcha_popup': '.geetest_popup',  # 验证码弹窗
            'full_bg': '.geetest_bg',  # 背景图
            'slice_bg': '.geetest_slice_bg',  # 拼图背景
            'refresh_btn': '.geetest_refresh',  # 刷新按钮
            'voice_btn': '.geetest_voice',  # 语音验证按钮
        }
        
        for key, selector in selectors.items():
            try:
                elements[key] = self.driver.find_element(By.CSS_SELECTOR, selector)
            except:
                elements[key] = None
                
        return elements
    
    def trigger_verification(self):
        """
        触发验证码显示
        """
        try:
            # 点击验证按钮
            verify_btn = self.wait.until(
                EC.element_to_be_clickable((By.CLASS_NAME, 'geetest_btn'))
            )
            verify_btn.click()
            time.sleep(2)
            return True
        except Exception as e:
            print(f"触发验证失败: {e}")
            return False
    
    def get_captcha_images(self):
        """
        获取验证码图片
        
        Returns:
            dict: 包含背景图、目标图等图片的字典
        """
        images = {}
        
        try:
            # 等待验证码弹窗出现
            self.wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'geetest_popup')))
            time.sleep(1)
            
            # 获取背景图片
            bg_element = self.driver.find_element(By.CLASS_NAME, 'geetest_bg')
            bg_style = bg_element.get_attribute('style')
            
            # 从style中提取背景图URL
            bg_match = re.search(r'url\("?(.*?)"?\)', bg_style)
            if bg_match:
                bg_url = bg_match.group(1)
                images['background'] = self.download_image(bg_url)
            
            # 获取提示文字
            try:
                prompt_element = self.driver.find_element(By.CLASS_NAME, 'geetest_prompt')
                images['prompt_text'] = prompt_element.text
                print(f"验证提示: {images['prompt_text']}")
            except:
                images['prompt_text'] = "请点击图中文字"
            
            return images
            
        except Exception as e:
            print(f"获取图片失败: {e}")
            return images
    
    def download_image(self, url):
        """
        下载图片
        
        Args:
            url: 图片URL
            
        Returns:
            numpy.ndarray: OpenCV格式的图片
        """
        try:
            if url.startswith('data:image'):
                # 处理base64编码的图片
                header, encoded = url.split(",", 1)
                image_data = base64.b64decode(encoded)
                image = Image.open(io.BytesIO(image_data))
            else:
                # 处理普通URL
                response = requests.get(url, timeout=10)
                image = Image.open(io.BytesIO(response.content))
            
            # 转换为OpenCV格式
            return cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR)
            
        except Exception as e:
            print(f"下载图片失败: {e}")
            return None
    
    def preprocess_image(self, image):
        """
        预处理图片
        
        Args:
            image: 输入图片
            
        Returns:
            numpy.ndarray: 预处理后的图片
        """
        if image is None:
            return None
            
        # 转换为灰度图
        gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
        
        # 二值化
        _, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
        
        # 去噪
        kernel = np.ones((2, 2), np.uint8)
        processed = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel)
        
        return processed
    
    def find_text_positions(self, image, target_text):
        """
        在图片中查找目标文字的位置
        
        Args:
            image: 背景图片
            target_text: 目标文字
            
        Returns:
            list: 文字位置列表,每个元素为(x, y, w, h)
        """
        positions = []
        
        if image is None or not target_text:
            return positions
        
        # 预处理图片
        processed = self.preprocess_image(image)
        if processed is None:
            return positions
        
        # 使用OCR或模板匹配识别文字位置
        # 这里使用简单的轮廓检测作为示例
        # 实际应用中可以使用pytesseract、paddleocr等OCR库
        
        # 查找轮廓
        contours, _ = cv2.findContours(processed, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
        
        for contour in contours:
            # 过滤小面积轮廓
            area = cv2.contourArea(contour)
            if area < 100:  # 根据实际情况调整阈值
                continue
                
            # 获取边界框
            x, y, w, h = cv2.boundingRect(contour)
            
            # 过滤异常尺寸
            if w < 20 or h < 20 or w > 200 or h > 200:
                continue
                
            positions.append((x, y, w, h))
        
        return positions
    
    def match_target_positions(self, image, target_text):
        """
        根据目标文字匹配位置
        
        Args:
            image: 背景图片
            target_text: 目标文字
            
        Returns:
            list: 匹配的位置坐标列表
        """
        # 这里实现具体的文字识别和匹配逻辑
        # 示例:简单的关键词匹配
        
        keyword_positions = []
        all_positions = self.find_text_positions(image, target_text)
        
        if not all_positions:
            return keyword_positions
        
        # 示例:选择几个位置作为目标位置
        # 实际应用中需要根据OCR识别结果进行匹配
        for i, (x, y, w, h) in enumerate(all_positions):
            if i < 3:  # 示例:选择前3个位置
                center_x = x + w // 2
                center_y = y + h // 2
                keyword_positions.append((center_x, center_y))
        
        return keyword_positions
    
    def click_positions(self, positions):
        """
        点击目标位置
        
        Args:
            positions: 目标位置列表
            
        Returns:
            bool: 是否点击成功
        """
        try:
            # 获取验证码容器
            captcha_container = self.driver.find_element(By.CLASS_NAME, 'geetest_widget')
            
            for idx, (x, y) in enumerate(positions):
                # 在容器内执行点击
                self.actions.move_to_element_with_offset(captcha_container, x, y)
                self.actions.click()
                self.actions.pause(0.5)  # 点击间隔
                
                print(f"点击位置 {idx + 1}: ({x}, {y})")
            
            self.actions.perform()
            time.sleep(1)
            return True
            
        except Exception as e:
            print(f"点击失败: {e}")
            return False
    
    def verify_success(self):
        """
        验证是否成功
        
        Returns:
            bool: 验证是否成功
        """
        try:
            # 等待验证结果
            time.sleep(2)
            
            # 检查成功提示
            success_elements = [
                (By.CLASS_NAME, 'geetest_success'),
                (By.CLASS_NAME, 'geetest_success_animate'),
                (By.XPATH, '//*[contains(text(), "验证成功")]')
            ]
            
            for by, value in success_elements:
                try:
                    element = self.driver.find_element(by, value)
                    if element.is_displayed():
                        print("验证成功!")
                        return True
                except:
                    continue
            
            return False
            
        except Exception as e:
            print(f"验证结果检查失败: {e}")
            return False
    
    def retry_verification(self):
        """
        重试验证
        """
        try:
            refresh_btn = self.driver.find_element(By.CLASS_NAME, 'geetest_refresh')
            refresh_btn.click()
            time.sleep(2)
            return True
        except:
            return False
    
    def run_verification(self, url, target_text=None):
        """
        运行完整验证流程
        
        Args:
            url: 目标页面URL
            target_text: 目标文字(可选)
            
        Returns:
            bool: 验证是否成功
        """
        print(f"开始验证: {url}")
        
        # 1. 加载页面
        self.load_page(url)
        
        # 2. 检测验证码组件
        elements = self.detect_geetest_widget()
        if not elements.get('widget'):
            print("未找到验证码组件")
            return False
        
        # 3. 触发验证
        if not self.trigger_verification():
            return False
        
        # 最大重试次数
        max_retries = 3
        for attempt in range(max_retries):
            print(f"\n--- 第 {attempt + 1} 次尝试 ---")
            
            # 4. 获取验证码图片
            images = self.get_captcha_images()
            if not images.get('background'):
                print("获取验证码图片失败")
                if not self.retry_verification():
                    break
                continue
            
            # 5. 确定目标文字
            prompt_text = images.get('prompt_text', '')
            if target_text:
                search_text = target_text
            elif prompt_text:
                # 从提示文字中提取关键词
                search_text = self.extract_keywords(prompt_text)
            else:
                search_text = "验证码"
            
            print(f"搜索目标: {search_text}")
            
            # 6. 识别目标位置
            bg_image = images['background']
            target_positions = self.match_target_positions(bg_image, search_text)
            
            if not target_positions:
                print("未找到目标位置")
                if not self.retry_verification():
                    break
                continue
            
            print(f"找到 {len(target_positions)} 个目标位置")
            
            # 7. 点击目标位置
            if not self.click_positions(target_positions):
                if not self.retry_verification():
                    break
                continue
            
            # 8. 验证结果
            if self.verify_success():
                return True
            
            # 等待下一次尝试
            time.sleep(1)
        
        print("验证失败")
        return False
    
    def extract_keywords(self, prompt):
        """
        从提示文字中提取关键词
        
        Args:
            prompt: 提示文字
            
        Returns:
            str: 提取的关键词
        """
        # 常见的点选验证提示模式
        patterns = [
            r'请依次点击(.*?)的汉字',
            r'请点击图中的(.*?)字',
            r'请点击(.*?)文字',
            r'请点击(.*?)图标',
            r'点击(.*?)进行验证'
        ]
        
        for pattern in patterns:
            match = re.search(pattern, prompt)
            if match:
                return match.group(1)
        
        # 如果没有匹配到模式,返回原提示
        return prompt
    
    def close(self):
        """关闭浏览器"""
        self.driver.quit()


def test_geetest_verification():
    """
    测试极验3验证码验证
    """
    # 使用极验官方演示页面进行测试
    test_url = "https://www.geetest.com/demo/"
    
    # 初始化验证器
    verifier = GeetestClickVerifier()
    
    try:
        # 运行验证
        success = verifier.run_verification(
            url=test_url,
            target_text="验证"  # 指定目标文字
        )
        
        if success:
            print("\n✅ 验证测试成功完成")
        else:
            print("\n❌ 验证测试失败")
        
        return success
        
    except Exception as e:
        print(f"测试过程中发生错误: {e}")
        return False
        
    finally:
        # 等待用户查看结果
        input("\n按Enter键关闭浏览器...")
        verifier.close()


if __name__ == "__main__":
    # 运行测试
    test_result = test_geetest_verification()
    
    if test_result:
        print("测试通过!")
    else:
        print("测试失败,请检查配置和网络连接。")

关键技术点说明:

  1. Selenium自动化控制:

    • 使用Selenium WebDriver进行浏览器自动化操作

    • 添加反检测机制,隐藏自动化特征

    • 通过ActionChains模拟人类点击行为

  2. 验证码组件识别:

    • 通过CSS选择器定位极验验证码相关元素

    • 支持检测多种极验验证码组件

    • 自动触发验证码弹出

  3. 图片处理流程:

    • 从网页中提取背景图片和数据URI

    • 支持base64编码图片解码

    • 图片预处理(灰度化、二值化、去噪)

  4. 文字识别策略:

    • 轮廓检测定位文字区域

    • 可扩展集成OCR库(如pytesseract、paddleocr)

    • 关键词匹配算法

  5. 验证流程管理:

    • 完整的验证状态机

    • 失败重试机制

    • 成功验证检测

  6. 反反爬虫策略:

    • 添加随机延迟模拟人类操作

    • 处理动态加载内容

    • 错误恢复机制

使用说明:

  1. 安装依赖: pip install selenium opencv-python numpy pillow requests

  2. 下载对应版本的ChromeDriver

  3. 根据实际验证码类型调整目标文字识别逻辑

  4. 可通过继承和重写相关方法适应不同站点的验证码

注意:实际应用中可能需要根据具体网站的验证码实现进行调整,特别是文字识别部分可能需要使用更先进的OCR技术。

相关推荐
川石课堂软件测试1 小时前
自动化测试的基本概念及常用框架
数据库·python·功能测试·测试工具·单元测试·自动化·流程图
灰勒塔德1 小时前
jetson orin nano super开发指南
linux·服务器·python
8278209372 小时前
python scp 备份
开发语言·python
未来之窗软件服务2 小时前
服务器运维(十七)web服务对比和选择——东方仙盟炼气期
运维·服务器·服务器运维·仙盟创梦ide·东方仙盟
poggioxay2 小时前
JAVA零基础入门知识3(持续更新中)
java·开发语言·python
serve the people2 小时前
TensorFlow 基础训练循环(简化版 + 补全代码)
人工智能·python·tensorflow
木里先森2 小时前
解决报错:/lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.32‘ not found
linux·python
爱打代码的小林2 小时前
numpy库数组笔记
笔记·python·numpy
shizhan_cloud2 小时前
IF 条件语句的知识与实践
linux·运维