| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304 |
- #!/usr/bin/env python3
- # -*- coding: utf-8 -*-
- """
- 云码验证码识别 - 第一种验证码(顺序点击图标)
- 接口文档: https://www.jfbym.com/test/317.html
- """
- 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 YunmaIconPointCaptcha:
- """云码顺序点击验证码识别器"""
- def __init__(self, token: str):
- """
- 初始化
-
- Args:
- token: 云码平台API Token
- """
- self.token = token
- self.api_url = "http://api.jfbym.com/api/YmServer/customApi"
- self.type_id = "317" # 顺序点击验证码接口ID
- def identify_type(self, driver) -> bool:
- """
- 识别是否为顺序点击验证码
-
- Args:
- driver: Selenium WebDriver实例
-
- Returns:
- bool: 是顺序点击验证码返回True
- """
- try:
- # 查找验证码容器
- captcha_container = driver.find_element(By.CLASS_NAME, "yidun")
- class_name = captcha_container.get_attribute("class")
-
- # 检查是否包含icon_point类名
- if "yidun--icon_point" in class_name:
- return True
-
- # 备选:检查提示文字
- try:
- tips_element = driver.find_element(By.CSS_SELECTOR, ".yidun_tips__text")
- tips_text = tips_element.text.strip()
- if tips_text.startswith("请依次点击"):
- return True
- except:
- pass
-
- return False
-
- except Exception as e:
- print(f"识别验证码类型失败: {e}")
- return False
- 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")
-
- # 获取图片src
- img_src = img_element.get_attribute("src")
- print(f"验证码图片地址: {img_src}")
-
- # 如果是base64格式
- if img_src.startswith("data:image"):
- # 提取base64数据部分
- base64_data = img_src.split(",")[1]
- return base64_data
-
- # 如果是URL,下载图片
- elif img_src.startswith("http"):
- response = requests.get(img_src)
- if response.status_code == 200:
- # 转为base64
- return base64.b64encode(response.content).decode()
-
- return None
-
- except Exception as e:
- print(f"提取验证码图片失败: {e}")
- return None
- def call_api(self, image_base64: str) -> dict:
- """
- 调用云码API识别验证码
-
- Args:
- image_base64: Base64编码的图片数据
-
- Returns:
- dict: API响应结果
- """
- payload = {
- "token": self.token,
- "type": self.type_id,
- "image": image_base64
- }
-
- 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: 坐标列表 [[x1, y1], [x2, y2], ...]
- """
- 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:
- # 可能是其他格式,尝试eval
- 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")
- location = img_element.location
- size = img_element.size
-
- # 创建Actions链
- 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
- """
- print("=" * 50)
- print("开始处理顺序点击验证码(第一种)")
- print("=" * 50)
-
- # 1. 识别验证码类型
- if not self.identify_type(driver):
- print("❌ 不是顺序点击验证码")
- return False
-
- print("✅ 确认为顺序点击验证码")
-
- # 2. 提取验证码图片
- image_base64 = self.extract_captcha_image(driver)
- if not image_base64:
- print("❌ 提取验证码图片失败")
- return False
-
- print("✅ 验证码图片提取成功")
-
- # 3. 调用云码API
- print("🔄 正在调用云码API识别...")
- api_result = self.call_api(image_base64)
-
- if not api_result or api_result.get("code") != 10000:
- print(f"❌ API识别失败: {api_result}")
- return False
-
- print("✅ API识别成功")
-
- # 4. 解析结果
- coordinates = self.parse_result(api_result)
- if not coordinates:
- print("❌ 解析坐标失败")
- return False
-
- print(f"📍 获取到 {len(coordinates)} 个点击坐标: {coordinates}")
-
- # 5. 执行点击
- if self.click_coordinates(driver, coordinates):
- print("✅ 点击完成")
- return True
- else:
- print("❌ 点击失败")
- return False
- def main():
- """主函数示例"""
- # 配置
- TOKEN = "wVRyAIjI3hhJBicXndcs1ro9BN5UNMXIjW6SDWBFFyQ" # 云码Token
-
- # 创建识别器
- solver = YunmaIconPointCaptcha(TOKEN)
-
- # 使用示例
- driver = webdriver.Chrome()
- driver.get("https://buff.163.com")
-
- try:
- # 等待验证码出现
- WebDriverWait(driver, 10).until(
- EC.presence_of_element_located((By.CLASS_NAME, "yidun"))
- )
-
- # 等待验证码完全加载
- time.sleep(2)
-
- # 解决验证码
- if solver.solve(driver):
- print("🎉 验证码解决成功!")
- else:
- print("❌ 验证码解决失败")
-
- except Exception as e:
- print(f"错误: {e}")
-
- finally:
- input("按Enter键关闭浏览器...")
- driver.quit()
- if __name__ == "__main__":
- main()
|