云码验证码识别_综合版.py 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. """
  4. 综合验证码识别器 - 自动识别并调用云码接口
  5. 支持两种BUFF验证码类型:
  6. 1. 顺序点击验证码(type=317)
  7. 2. 文字特征点选验证码(type=94)
  8. """
  9. import base64
  10. import json
  11. import time
  12. import requests
  13. from selenium import webdriver
  14. from selenium.webdriver.common.by import By
  15. from selenium.webdriver.common.action_chains import ActionChains
  16. from selenium.webdriver.support.ui import WebDriverWait
  17. from selenium.webdriver.support import expected_conditions as EC
  18. class BuffCaptchaSolver:
  19. """BUFF验证码综合识别器"""
  20. def __init__(self, token: str):
  21. """
  22. 初始化
  23. Args:
  24. token: 云码平台API Token
  25. """
  26. self.token = token
  27. self.api_url = "http://api.jfbym.com/api/YmServer/customApi"
  28. def detect_captcha_type(self, driver) -> str:
  29. """
  30. 检测验证码类型
  31. Args:
  32. driver: Selenium WebDriver实例
  33. Returns:
  34. str: 'icon_point' - 顺序点击验证码
  35. 'space' - 文字特征点选验证码
  36. None - 未检测到验证码
  37. """
  38. try:
  39. # 等待验证码容器出现
  40. wait = WebDriverWait(driver, 10)
  41. captcha_container = wait.until(
  42. EC.presence_of_element_located((By.CLASS_NAME, "yidun"))
  43. )
  44. # 方法1:检查CSS类名(最可靠)
  45. class_name = captcha_container.get_attribute("class")
  46. if "yidun--icon_point" in class_name:
  47. return "icon_point"
  48. elif "yidun--space" in class_name:
  49. return "space"
  50. # 方法2:检查提示文字
  51. try:
  52. tips_element = driver.find_element(By.CSS_SELECTOR, ".yidun_tips__text")
  53. tips_text = tips_element.text.strip()
  54. if tips_text.startswith("请依次点击"):
  55. return "icon_point"
  56. elif tips_text.startswith("请点击"):
  57. return "space"
  58. except:
  59. pass
  60. return None
  61. except Exception as e:
  62. print(f"验证码检测失败: {e}")
  63. return None
  64. def extract_captcha_image(self, driver) -> str:
  65. """
  66. 提取验证码图片并转为Base64
  67. Args:
  68. driver: Selenium WebDriver实例
  69. Returns:
  70. str: Base64编码的图片数据
  71. """
  72. try:
  73. img_element = driver.find_element(By.CSS_SELECTOR, ".yidun_bg-img")
  74. img_src = img_element.get_attribute("src")
  75. print(f"验证码图片地址: {img_src}")
  76. if img_src.startswith("data:image"):
  77. base64_data = img_src.split(",")[1]
  78. return base64_data
  79. elif img_src.startswith("http"):
  80. response = requests.get(img_src)
  81. if response.status_code == 200:
  82. return base64.b64encode(response.content).decode()
  83. return None
  84. except Exception as e:
  85. print(f"提取验证码图片失败: {e}")
  86. return None
  87. def extract_tip_text(self, driver) -> str:
  88. """
  89. 提取验证码提示文字
  90. Args:
  91. driver: Selenium WebDriver实例
  92. Returns:
  93. str: 提示文字
  94. """
  95. try:
  96. tips_element = driver.find_element(By.CSS_SELECTOR, ".yidun_tips__text")
  97. tip_text = tips_element.text.strip()
  98. print(f"验证码提示文字: {tip_text}")
  99. return tip_text
  100. except:
  101. return ""
  102. def call_api(self, image_base64: str, captcha_type: str, tip_text: str = "") -> dict:
  103. """
  104. 调用云码API
  105. Args:
  106. image_base64: Base64编码的图片数据
  107. captcha_type: 验证码类型
  108. tip_text: 提示文字
  109. Returns:
  110. dict: API响应结果
  111. """
  112. # 根据类型选择接口ID
  113. type_ids = {
  114. "icon_point": "317",
  115. "space": "94"
  116. }
  117. type_id = type_ids.get(captcha_type)
  118. if not type_id:
  119. return {"code": -1, "msg": "未知的验证码类型"}
  120. payload = {
  121. "token": self.token,
  122. "type": type_id,
  123. "image": image_base64
  124. }
  125. # 文字点选类型可能需要传递提示文字
  126. if captcha_type == "space" and tip_text:
  127. payload["extra"] = tip_text
  128. headers = {"Content-Type": "application/json"}
  129. try:
  130. response = requests.post(
  131. self.api_url,
  132. json=payload,
  133. headers=headers,
  134. timeout=30
  135. )
  136. return response.json()
  137. except Exception as e:
  138. print(f"API调用失败: {e}")
  139. return {"code": -1, "msg": str(e)}
  140. def parse_result(self, api_result: dict) -> list:
  141. """
  142. 解析API返回结果
  143. Args:
  144. api_result: API响应结果
  145. Returns:
  146. list: 坐标列表
  147. """
  148. try:
  149. if api_result.get("code") == 10000:
  150. data = api_result.get("data", [])
  151. if data:
  152. result_str = data[0].get("data", "")
  153. if result_str.startswith("[") and result_str.endswith("]"):
  154. return json.loads(result_str)
  155. else:
  156. try:
  157. return eval(result_str)
  158. except:
  159. print(f"无法解析返回数据: {result_str}")
  160. return []
  161. else:
  162. print(f"识别失败: {api_result.get('msg')}")
  163. return []
  164. except Exception as e:
  165. print(f"解析结果失败: {e}")
  166. return []
  167. def click_coordinates(self, driver, coordinates: list):
  168. """
  169. 执行点击操作
  170. Args:
  171. driver: Selenium WebDriver实例
  172. coordinates: 坐标列表
  173. """
  174. try:
  175. img_element = driver.find_element(By.CSS_SELECTOR, ".yidun_bg-img")
  176. actions = ActionChains(driver)
  177. for i, coord in enumerate(coordinates):
  178. x, y = coord
  179. actions.move_to_element_with_offset(img_element, x, y)
  180. actions.click()
  181. if i < len(coordinates) - 1:
  182. actions.pause(0.3)
  183. actions.perform()
  184. print(f"完成点击,共点击 {len(coordinates)} 次")
  185. return True
  186. except Exception as e:
  187. print(f"点击操作失败: {e}")
  188. return False
  189. def solve(self, driver) -> bool:
  190. """
  191. 完整的验证码解决流程
  192. Args:
  193. driver: Selenium WebDriver实例
  194. Returns:
  195. bool: 解决成功返回True
  196. """
  197. # 1. 检测验证码类型
  198. captcha_type = self.detect_captcha_type(driver)
  199. if captcha_type == "icon_point":
  200. type_name = "顺序点击验证码(第一种)"
  201. type_id = "317"
  202. elif captcha_type == "space":
  203. type_name = "文字特征点选验证码(第二种)"
  204. type_id = "94"
  205. else:
  206. print("❌ 无法识别验证码类型或未检测到验证码")
  207. return False
  208. print("=" * 60)
  209. print(f"✅ 检测到 {type_name}")
  210. print(f" 云码接口ID: {type_id}")
  211. print("=" * 60)
  212. # 2. 提取验证码图片
  213. image_base64 = self.extract_captcha_image(driver)
  214. if not image_base64:
  215. print("❌ 提取验证码图片失败")
  216. return False
  217. print("✅ 验证码图片提取成功")
  218. # 3. 提取提示文字(仅第二种需要)
  219. tip_text = ""
  220. if captcha_type == "space":
  221. tip_text = self.extract_tip_text(driver)
  222. # 4. 调用云码API
  223. print("🔄 正在调用云码API识别...")
  224. api_result = self.call_api(image_base64, captcha_type, tip_text)
  225. if not api_result or api_result.get("code") != 10000:
  226. print(f"❌ API识别失败: {api_result}")
  227. return False
  228. print("✅ API识别成功")
  229. # 5. 解析结果
  230. coordinates = self.parse_result(api_result)
  231. if not coordinates:
  232. print("❌ 解析坐标失败")
  233. return False
  234. print(f"📍 获取到 {len(coordinates)} 个点击坐标: {coordinates}")
  235. # 6. 执行点击
  236. if self.click_coordinates(driver, coordinates):
  237. print("✅ 点击完成")
  238. return True
  239. else:
  240. print("❌ 点击失败")
  241. return False
  242. def main():
  243. """主函数示例"""
  244. # 配置
  245. TOKEN = "wVRyAIjI3hhJBicXndcs1ro9BN5UNMXIjW6SDWBFFyQ" # 云码Token
  246. # 创建识别器
  247. solver = BuffCaptchaSolver(TOKEN)
  248. # 使用示例
  249. driver = webdriver.Chrome()
  250. # 访问指定URL
  251. target_url = "https://buff.163.com/goods/900482?from=market#tab=selling"
  252. print(f"正在访问: {target_url}")
  253. driver.get(target_url)
  254. try:
  255. # 等待验证码出现(最多30秒)
  256. WebDriverWait(driver, 30).until(
  257. EC.presence_of_element_located((By.CLASS_NAME, "yidun"))
  258. )
  259. # 等待验证码完全加载
  260. time.sleep(2)
  261. # 自动识别并解决验证码
  262. if solver.solve(driver):
  263. print("\n🎉 验证码解决成功!")
  264. else:
  265. print("\n❌ 验证码解决失败")
  266. except Exception as e:
  267. print(f"错误: {e}")
  268. finally:
  269. input("\n按Enter键关闭浏览器...")
  270. driver.quit()
  271. if __name__ == "__main__":
  272. main()