yidun_slider_captcha.py 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. """易盾滑块验证码:云码识别 + 模拟拖拽。"""
  2. import base64
  3. import math
  4. import random
  5. import time
  6. import requests
  7. from commons.Logger import logger
  8. CAPTCHA_TOKEN = "zPzmt1mG1ouCU6GTzsZN2Lmm8pdZypapPcLJTBRETco"
  9. CAPTCHA_API_URL = "http://api.jfbym.com/api/YmServer/customApi"
  10. SLIDER_OFFSET_FIX = 10
  11. def call_captcha_api(image_bytes):
  12. """调用云码平台识别滑块距离,失败返回 None。"""
  13. try:
  14. b64 = base64.b64encode(image_bytes).decode()
  15. resp = requests.post(
  16. CAPTCHA_API_URL,
  17. json={"token": CAPTCHA_TOKEN, "type": "22222", "image": b64},
  18. headers={"Content-Type": "application/json"},
  19. timeout=15,
  20. ).json()
  21. logger.info("验证码 API 返回: %s", resp)
  22. if not isinstance(resp, dict):
  23. return None
  24. data = resp.get("data")
  25. if isinstance(data, dict):
  26. dist = data.get("data")
  27. else:
  28. dist = data
  29. if dist is None:
  30. logger.error("验证码 API 未返回距离字段: %s", resp)
  31. return None
  32. try:
  33. d = float(dist)
  34. except (TypeError, ValueError):
  35. logger.error("验证码距离无法解析为数字: %r", dist)
  36. return None
  37. if not math.isfinite(d):
  38. logger.error("验证码距离非有限数值: %r", dist)
  39. return None
  40. return d
  41. except Exception as e:
  42. logger.exception("验证码 API 调用失败: %s", e)
  43. return None
  44. def generate_human_track(distance):
  45. try:
  46. distance = float(distance)
  47. except (TypeError, ValueError):
  48. return []
  49. if distance <= 0 or not math.isfinite(distance):
  50. return []
  51. tracks = []
  52. current = 0
  53. mid = distance * 0.7
  54. t = 0.2
  55. v = 0
  56. move_points = []
  57. while current < mid:
  58. a = random.uniform(2, 4)
  59. v0 = v
  60. v = v0 + a * t
  61. move = v0 * t + 0.5 * a * t * t
  62. current += move
  63. move_points.append(move)
  64. while current < distance:
  65. a = -random.uniform(0.5, 1.5)
  66. v0 = v
  67. v = v0 + a * t
  68. if v < 0.5:
  69. v = 0.5
  70. move = v0 * t + 0.5 * a * t * t
  71. current += move
  72. move_points.append(move)
  73. total_points = len(move_points)
  74. for i, move in enumerate(move_points):
  75. y_offset = random.randint(-2, 2) if i % random.randint(2, 4) == 0 else 0
  76. if i < total_points * 0.3:
  77. duration = random.uniform(0.01, 0.03)
  78. elif i > total_points * 0.7:
  79. duration = random.uniform(0.03, 0.08)
  80. else:
  81. duration = random.uniform(0.02, 0.05)
  82. if random.random() < 0.05:
  83. duration += random.uniform(0.05, 0.1)
  84. tracks.append((move, y_offset, duration))
  85. if random.random() < 0.7:
  86. tracks.append((-random.randint(1, 3), 0, 0.05))
  87. return tracks
  88. def simulate_slider_drag(driver, slider_element, target_distance):
  89. if target_distance <= 0:
  90. logger.warning("滑块目标距离无效: %s", target_distance)
  91. return
  92. driver.actions.move_to(slider_element).hold()
  93. for offset_x, offset_y, duration in generate_human_track(target_distance):
  94. driver.actions.move(offset_x, offset_y, duration=duration / 1000)
  95. driver.actions.release()
  96. def solve_slider_captcha(driver):
  97. """检测并处理易盾滑块验证码,成功返回 True。"""
  98. driver.wait.doc_loaded()
  99. time.sleep(2)
  100. yidun = driver.ele("xpath://div[@class='yidun_modal']", timeout=3)
  101. if not yidun:
  102. return True
  103. logger.info("检测到滑块验证码,开始处理")
  104. jpg_bytes = yidun.get_screenshot(as_bytes="jpg")
  105. distance = call_captcha_api(jpg_bytes)
  106. if distance is None:
  107. logger.error("验证码识别失败")
  108. return False
  109. logger.info("滑块距离: %s", distance)
  110. slider = driver.ele(
  111. "xpath://div[contains(@class,'yidun_slider--hover')]", timeout=5
  112. )
  113. if not slider:
  114. logger.error("未找到滑块元素")
  115. return False
  116. try:
  117. drag_distance = float(distance) + SLIDER_OFFSET_FIX
  118. except (TypeError, ValueError):
  119. logger.error("滑块距离非数字: %r", distance)
  120. return False
  121. if not math.isfinite(drag_distance) or drag_distance <= 0:
  122. logger.error("滑块距离无效: %s", drag_distance)
  123. return False
  124. simulate_slider_drag(driver, slider, drag_distance-6)
  125. time.sleep(3)
  126. return True