| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341 |
- #!/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()
|