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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. """
  4. BUFF验证码自动识别器 - 使用Playwright
  5. 支持两种验证码类型:
  6. 1. 顺序点击验证码(type=317)
  7. 2. 文字特征点选验证码(type=94)
  8. 使用方法:
  9. 1. 安装依赖: pip install playwright && playwright install chromium
  10. 2. 运行脚本: python 云码验证码识别_综合版.py
  11. """
  12. import base64
  13. import json
  14. import time
  15. import sys
  16. from playwright.sync_api import sync_playwright
  17. from playwright_stealth import stealth
  18. import requests
  19. class BuffCaptchaSolver:
  20. """BUFF验证码综合识别器"""
  21. def __init__(self, token: str):
  22. """
  23. 初始化
  24. Args:
  25. token: 云码平台API Token
  26. """
  27. self.token = token
  28. self.api_url = "http://api.jfbym.com/api/YmServer/customApi"
  29. def detect_captcha_type(self, page) -> str:
  30. """
  31. 检测验证码类型
  32. Args:
  33. page: Playwright Page对象
  34. Returns:
  35. str: 'icon_point' - 顺序点击验证码
  36. 'space' - 文字特征点选验证码
  37. None - 未检测到验证码
  38. """
  39. try:
  40. # 等待验证码容器出现
  41. page.wait_for_selector(".yidun", timeout=10000)
  42. # 方法1:检查CSS类名(最可靠)
  43. class_name = page.evaluate("""
  44. () => {
  45. const el = document.querySelector('.yidun');
  46. return el ? el.className : '';
  47. }
  48. """)
  49. if "yidun--icon_point" in class_name:
  50. return "icon_point"
  51. elif "yidun--space" in class_name:
  52. return "space"
  53. # 方法2:检查提示文字
  54. try:
  55. tips_text = page.evaluate("""
  56. () => {
  57. const el = document.querySelector('.yidun_tips__text');
  58. return el ? el.textContent.trim() : '';
  59. }
  60. """)
  61. if tips_text.startswith("请依次点击"):
  62. return "icon_point"
  63. elif tips_text.startswith("请点击"):
  64. return "space"
  65. except:
  66. pass
  67. return None
  68. except Exception as e:
  69. print(f"验证码检测失败: {e}")
  70. return None
  71. def extract_captcha_image(self, page) -> str:
  72. """
  73. 提取验证码图片并转为Base64
  74. Args:
  75. page: Playwright Page对象
  76. Returns:
  77. str: Base64编码的图片数据
  78. """
  79. try:
  80. # 获取图片src
  81. img_src = page.evaluate("""
  82. () => {
  83. const img = document.querySelector('.yidun_bg-img');
  84. return img ? img.src : null;
  85. }
  86. """)
  87. if not img_src:
  88. print("❌ 未找到验证码图片")
  89. return None
  90. print(f"验证码图片地址: {img_src}")
  91. if img_src.startswith("data:image"):
  92. # 提取base64数据部分
  93. base64_data = img_src.split(",")[1]
  94. return base64_data
  95. elif img_src.startswith("http"):
  96. # 下载图片
  97. response = requests.get(img_src)
  98. if response.status_code == 200:
  99. return base64.b64encode(response.content).decode()
  100. return None
  101. except Exception as e:
  102. print(f"提取验证码图片失败: {e}")
  103. return None
  104. def extract_tip_text(self, page) -> str:
  105. """
  106. 提取验证码提示文字
  107. Args:
  108. page: Playwright Page对象
  109. Returns:
  110. str: 提示文字
  111. """
  112. try:
  113. tip_text = page.evaluate("""
  114. () => {
  115. const el = document.querySelector('.yidun_tips__text');
  116. return el ? el.textContent.trim() : '';
  117. }
  118. """)
  119. print(f"验证码提示文字: {tip_text}")
  120. return tip_text
  121. except:
  122. return ""
  123. def call_api(self, image_base64: str, captcha_type: str, tip_text: str = "") -> dict:
  124. """
  125. 调用云码API
  126. Args:
  127. image_base64: Base64编码的图片数据
  128. captcha_type: 验证码类型
  129. tip_text: 提示文字
  130. Returns:
  131. dict: API响应结果
  132. """
  133. # 根据类型选择接口ID
  134. type_ids = {
  135. "icon_point": "317",
  136. "space": "94"
  137. }
  138. type_id = type_ids.get(captcha_type)
  139. if not type_id:
  140. return {"code": -1, "msg": "未知的验证码类型"}
  141. payload = {
  142. "token": self.token,
  143. "type": type_id,
  144. "image": image_base64
  145. }
  146. # 文字点选类型可能需要传递提示文字
  147. if captcha_type == "space" and tip_text:
  148. payload["extra"] = tip_text
  149. headers = {"Content-Type": "application/json"}
  150. try:
  151. response = requests.post(
  152. self.api_url,
  153. json=payload,
  154. headers=headers,
  155. timeout=30
  156. )
  157. return response.json()
  158. except Exception as e:
  159. print(f"API调用失败: {e}")
  160. return {"code": -1, "msg": str(e)}
  161. def parse_result(self, api_result: dict) -> list:
  162. """
  163. 解析API返回结果
  164. Args:
  165. api_result: API响应结果
  166. Returns:
  167. list: 坐标列表
  168. """
  169. try:
  170. if api_result.get("code") == 10000:
  171. data = api_result.get("data", [])
  172. if data:
  173. result_str = data[0].get("data", "")
  174. print(f"云码返回坐标: {result_str}")
  175. if result_str.startswith("[") and result_str.endswith("]"):
  176. return json.loads(result_str)
  177. else:
  178. try:
  179. return eval(result_str)
  180. except:
  181. print(f"无法解析返回数据: {result_str}")
  182. return []
  183. else:
  184. print(f"识别失败: {api_result.get('msg')}")
  185. return []
  186. except Exception as e:
  187. print(f"解析结果失败: {e}")
  188. return []
  189. def click_coordinates(self, page, coordinates: list):
  190. """
  191. 执行点击操作
  192. Args:
  193. page: Playwright Page对象
  194. coordinates: 坐标列表
  195. """
  196. try:
  197. # 获取验证码图片元素
  198. img_element = page.query_selector(".yidun_bg-img")
  199. box = img_element.bounding_box()
  200. if not box:
  201. print("❌ 无法获取验证码图片位置")
  202. return False
  203. # 执行点击
  204. for i, coord in enumerate(coordinates):
  205. x, y = coord
  206. # 计算相对于图片的绝对坐标
  207. abs_x = box["x"] + x
  208. abs_y = box["y"] + y
  209. # 移动并点击
  210. page.mouse.click(abs_x, abs_y)
  211. print(f"点击坐标: ({abs_x}, {abs_y})")
  212. # 每次点击后等待
  213. if i < len(coordinates) - 1:
  214. time.sleep(0.3)
  215. print(f"✅ 完成点击,共点击 {len(coordinates)} 次")
  216. return True
  217. except Exception as e:
  218. print(f"点击操作失败: {e}")
  219. return False
  220. def solve(self, page) -> bool:
  221. """
  222. 完整的验证码解决流程
  223. Args:
  224. page: Playwright Page对象
  225. Returns:
  226. bool: 解决成功返回True
  227. """
  228. # 1. 检测验证码类型
  229. captcha_type = self.detect_captcha_type(page)
  230. if captcha_type == "icon_point":
  231. type_name = "顺序点击验证码(第一种)"
  232. type_id = "317"
  233. elif captcha_type == "space":
  234. type_name = "文字特征点选验证码(第二种)"
  235. type_id = "94"
  236. else:
  237. print("❌ 无法识别验证码类型或未检测到验证码")
  238. return False
  239. print("=" * 60)
  240. print(f"✅ 检测到 {type_name}")
  241. print(f" 云码接口ID: {type_id}")
  242. print("=" * 60)
  243. # 2. 提取验证码图片
  244. image_base64 = self.extract_captcha_image(page)
  245. if not image_base64:
  246. print("❌ 提取验证码图片失败")
  247. return False
  248. print("✅ 验证码图片提取成功")
  249. # 3. 提取提示文字(仅第二种需要)
  250. tip_text = ""
  251. if captcha_type == "space":
  252. tip_text = self.extract_tip_text(page)
  253. # 4. 调用云码API
  254. print("🔄 正在调用云码API识别...")
  255. api_result = self.call_api(image_base64, captcha_type, tip_text)
  256. if not api_result or api_result.get("code") != 10000:
  257. print(f"❌ API识别失败: {api_result}")
  258. return False
  259. print("✅ API识别成功")
  260. # 5. 解析结果
  261. coordinates = self.parse_result(api_result)
  262. if not coordinates:
  263. print("❌ 解析坐标失败")
  264. return False
  265. print(f"📍 获取到 {len(coordinates)} 个点击坐标")
  266. # 6. 执行点击
  267. if self.click_coordinates(page, coordinates):
  268. print("✅ 点击完成")
  269. return True
  270. else:
  271. print("❌ 点击失败")
  272. return False
  273. def main():
  274. """主函数"""
  275. # 配置
  276. TOKEN = "wVRyAIjI3hhJBicXndcs1ro9BN5UNMXIjW6SDWBFFyQ" # 云码Token
  277. TARGET_URL = "https://buff.163.com/goods/900482?from=market#tab=selling"
  278. # 创建识别器
  279. solver = BuffCaptchaSolver(TOKEN)
  280. with sync_playwright() as p:
  281. # 启动浏览器(无头模式=False,可以看到浏览器窗口)
  282. print("🚀 启动浏览器...")
  283. browser = p.chromium.launch(
  284. headless=False, # False=显示浏览器窗口
  285. args=[
  286. '--no-sandbox',
  287. '--disable-setuid-sandbox',
  288. '--disable-dev-shm-usage',
  289. '--disable-gpu',
  290. '--window-size=1920,1080'
  291. ]
  292. )
  293. # 创建上下文
  294. context = browser.new_context(
  295. viewport={'width': 1920, 'height': 1080},
  296. user_agent='Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36'
  297. )
  298. # 创建页面
  299. page = context.new_page()
  300. # 应用stealth防止被检测
  301. stealth(page)
  302. try:
  303. # 访问目标URL
  304. print(f"📍 正在访问: {TARGET_URL}")
  305. page.goto(TARGET_URL)
  306. # 等待页面加载
  307. page.wait_for_load_state("networkidle")
  308. print("✅ 页面加载完成")
  309. # 等待验证码出现(最多30秒)
  310. print("🔍 等待验证码出现...")
  311. # 尝试等待验证码
  312. try:
  313. page.wait_for_selector(".yidun", timeout=30000)
  314. print("✅ 检测到验证码!")
  315. except:
  316. print("❌ 30秒内未检测到验证码")
  317. input("\n按Enter键关闭浏览器...")
  318. browser.close()
  319. return
  320. # 等待验证码完全加载
  321. time.sleep(2)
  322. # 自动识别并解决验证码
  323. if solver.solve(page):
  324. print("\n🎉 验证码解决成功!")
  325. else:
  326. print("\n❌ 验证码解决失败")
  327. except Exception as e:
  328. print(f"错误: {e}")
  329. import traceback
  330. traceback.print_exc()
  331. finally:
  332. input("\n按Enter键关闭浏览器...")
  333. browser.close()
  334. if __name__ == "__main__":
  335. print("=" * 60)
  336. print("BUFF验证码自动识别器(Playwright版本)")
  337. print("=" * 60)
  338. main()