#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ 综合验证码识别器 - 自动识别并调用云码接口 支持两种BUFF验证码类型: 1. 顺序点击验证码(type=317) 2. 文字特征点选验证码(type=94) """ import base64 import json import time import requests 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 class BuffCaptchaSolver: """BUFF验证码综合识别器""" def __init__(self, token: str): """ 初始化 Args: token: 云码平台API Token """ self.token = token self.api_url = "http://api.jfbym.com/api/YmServer/customApi" def detect_captcha_type(self, driver) -> str: """ 检测验证码类型 Args: driver: Selenium WebDriver实例 Returns: str: 'icon_point' - 顺序点击验证码 'space' - 文字特征点选验证码 None - 未检测到验证码 """ try: # 等待验证码容器出现 wait = WebDriverWait(driver, 10) captcha_container = wait.until( EC.presence_of_element_located((By.CLASS_NAME, "yidun")) ) # 方法1:检查CSS类名(最可靠) class_name = captcha_container.get_attribute("class") if "yidun--icon_point" in class_name: return "icon_point" elif "yidun--space" in class_name: return "space" # 方法2:检查提示文字 try: tips_element = driver.find_element(By.CSS_SELECTOR, ".yidun_tips__text") tips_text = tips_element.text.strip() if tips_text.startswith("请依次点击"): return "icon_point" elif tips_text.startswith("请点击"): return "space" except: pass return None except Exception as e: print(f"验证码检测失败: {e}") return None def extract_captcha_image(self, driver) -> str: """ 提取验证码图片并转为Base64 Args: driver: Selenium WebDriver实例 Returns: str: Base64编码的图片数据 """ try: img_element = driver.find_element(By.CSS_SELECTOR, ".yidun_bg-img") img_src = img_element.get_attribute("src") print(f"验证码图片地址: {img_src}") if img_src.startswith("data:image"): base64_data = img_src.split(",")[1] return base64_data elif img_src.startswith("http"): response = requests.get(img_src) if response.status_code == 200: return base64.b64encode(response.content).decode() return None except Exception as e: print(f"提取验证码图片失败: {e}") return None def extract_tip_text(self, driver) -> str: """ 提取验证码提示文字 Args: driver: Selenium WebDriver实例 Returns: str: 提示文字 """ try: tips_element = driver.find_element(By.CSS_SELECTOR, ".yidun_tips__text") tip_text = tips_element.text.strip() print(f"验证码提示文字: {tip_text}") return tip_text except: return "" def call_api(self, image_base64: str, captcha_type: str, tip_text: str = "") -> dict: """ 调用云码API Args: image_base64: Base64编码的图片数据 captcha_type: 验证码类型 tip_text: 提示文字 Returns: dict: API响应结果 """ # 根据类型选择接口ID type_ids = { "icon_point": "317", "space": "94" } type_id = type_ids.get(captcha_type) if not type_id: return {"code": -1, "msg": "未知的验证码类型"} payload = { "token": self.token, "type": type_id, "image": image_base64 } # 文字点选类型可能需要传递提示文字 if captcha_type == "space" and tip_text: payload["extra"] = tip_text headers = {"Content-Type": "application/json"} try: response = requests.post( self.api_url, json=payload, headers=headers, timeout=30 ) return response.json() except Exception as e: print(f"API调用失败: {e}") return {"code": -1, "msg": str(e)} def parse_result(self, api_result: dict) -> list: """ 解析API返回结果 Args: api_result: API响应结果 Returns: list: 坐标列表 """ try: if api_result.get("code") == 10000: data = api_result.get("data", []) if data: result_str = data[0].get("data", "") if result_str.startswith("[") and result_str.endswith("]"): return json.loads(result_str) else: try: return eval(result_str) except: print(f"无法解析返回数据: {result_str}") return [] else: print(f"识别失败: {api_result.get('msg')}") return [] except Exception as e: print(f"解析结果失败: {e}") return [] def click_coordinates(self, driver, coordinates: list): """ 执行点击操作 Args: driver: Selenium WebDriver实例 coordinates: 坐标列表 """ try: img_element = driver.find_element(By.CSS_SELECTOR, ".yidun_bg-img") actions = ActionChains(driver) for i, coord in enumerate(coordinates): x, y = coord actions.move_to_element_with_offset(img_element, x, y) actions.click() if i < len(coordinates) - 1: actions.pause(0.3) actions.perform() print(f"完成点击,共点击 {len(coordinates)} 次") return True except Exception as e: print(f"点击操作失败: {e}") return False def solve(self, driver) -> bool: """ 完整的验证码解决流程 Args: driver: Selenium WebDriver实例 Returns: bool: 解决成功返回True """ # 1. 检测验证码类型 captcha_type = self.detect_captcha_type(driver) if captcha_type == "icon_point": type_name = "顺序点击验证码(第一种)" type_id = "317" elif captcha_type == "space": type_name = "文字特征点选验证码(第二种)" type_id = "94" else: print("❌ 无法识别验证码类型或未检测到验证码") return False print("=" * 60) print(f"✅ 检测到 {type_name}") print(f" 云码接口ID: {type_id}") print("=" * 60) # 2. 提取验证码图片 image_base64 = self.extract_captcha_image(driver) if not image_base64: print("❌ 提取验证码图片失败") return False print("✅ 验证码图片提取成功") # 3. 提取提示文字(仅第二种需要) tip_text = "" if captcha_type == "space": tip_text = self.extract_tip_text(driver) # 4. 调用云码API print("🔄 正在调用云码API识别...") api_result = self.call_api(image_base64, captcha_type, tip_text) if not api_result or api_result.get("code") != 10000: print(f"❌ API识别失败: {api_result}") return False print("✅ API识别成功") # 5. 解析结果 coordinates = self.parse_result(api_result) if not coordinates: print("❌ 解析坐标失败") return False print(f"📍 获取到 {len(coordinates)} 个点击坐标: {coordinates}") # 6. 执行点击 if self.click_coordinates(driver, coordinates): print("✅ 点击完成") return True else: print("❌ 点击失败") return False def main(): """主函数示例""" # 配置 TOKEN = "wVRyAIjI3hhJBicXndcs1ro9BN5UNMXIjW6SDWBFFyQ" # 云码Token # 创建识别器 solver = BuffCaptchaSolver(TOKEN) # 使用示例 driver = webdriver.Chrome() # 访问指定URL target_url = "https://buff.163.com/goods/900482?from=market#tab=selling" print(f"正在访问: {target_url}") driver.get(target_url) try: # 等待验证码出现(最多30秒) WebDriverWait(driver, 30).until( EC.presence_of_element_located((By.CLASS_NAME, "yidun")) ) # 等待验证码完全加载 time.sleep(2) # 自动识别并解决验证码 if solver.solve(driver): print("\n🎉 验证码解决成功!") else: print("\n❌ 验证码解决失败") except Exception as e: print(f"错误: {e}") finally: input("\n按Enter键关闭浏览器...") driver.quit() if __name__ == "__main__": main()