算法暴露接口(xhs、dy、ks、wx、hnw)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

208 lines
10 KiB

7 months ago
  1. import math
  2. import time
  3. import json
  4. import random
  5. from kuaishou.ks_http import download_q
  6. from utils.rotate_captcha import export_single_discern
  7. from xhs.xhs_param import get_xs, encrypt_info, decrypt_info
  8. from loguru import logger
  9. class SignalCaptcha(object):
  10. """
  11. """
  12. def __init__(self, web_session, verify_uuid,
  13. a1, webId, source="", is_proxy=False):
  14. self.is_proxy = is_proxy
  15. self.source = source
  16. self.verify_uuid = verify_uuid
  17. self.register_url = "https://edith.xiaohongshu.com/api/redcaptcha/v2/captcha/register"
  18. self.check_url = "https://edith.xiaohongshu.com/api/redcaptcha/v2/captcha/check"
  19. self.headers = {
  20. "authority": "edith.xiaohongshu.com",
  21. "accept": "application/json, text/plain, */*",
  22. "accept-language": "zh-CN,zh;q=0.9",
  23. "cache-control": "no-cache",
  24. "content-type": "application/json;charset=UTF-8",
  25. "origin": "https://www.xiaohongshu.com",
  26. "pragma": "no-cache",
  27. "referer": "https://www.xiaohongshu.com/",
  28. "sec-ch-ua": "\"Not_A Brand\";v=\"8\", \"Chromium\";v=\"120\", \"Google Chrome\";v=\"120\"",
  29. "sec-ch-ua-mobile": "?0",
  30. "sec-ch-ua-platform": "\"macOS\"",
  31. "sec-fetch-dest": "empty",
  32. "sec-fetch-mode": "cors",
  33. "sec-fetch-site": "same-site",
  34. "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
  35. "x-s": "XYW_eyJzaWduU3ZuIjoiNTEiLCJzaWduVHlwZSI6IngxIiwiYXBwSWQiOiJsb2dpbiIsInNpZ25WZXJzaW9uIjoiMSIsInBheWxvYWQiOiJkNzkxNmQ0MDAyOTZmZDg4ZmIzYjcxOGY2MWRlOGU2MTEwYzZmMTBlMjZiOWI0NTQ1OTg0M2VjMTI2NmRlMGZiNDNkZGI3MmE5MWYyOTJkZTM4Yjk3MDlmZjcyMDBmMTRjOWUzYmZkYTFmYWExZWI5MGQ3NGFhMzFiNTRjNzJjZDBkNzRhYTMxYjU0YzcyY2RhYzQ4OWI5ZGE4Y2U1ZTQ4ZjRhZmI5YWNmYzNlYTI2ZmUwYjI2NmE2YjRjYzNjYjVhMDE2NzA1NzIyMWMxZjhkMzY4YzFkMjBjZGE4MGY5OWIxMWYyZWFhMGE5ZmQ3MWQ3NmZmZTgwMmI3MjM3NWQ2OWZiY2JjYzk1OTUxMmNhZDRmZWFjNWU1MjY1MDljYTliZDA3MzE0OGZjMDEzYzJlMzU4ZWE1ZDg5ZGQ4MzZkNWU2OTM4NGJkN2FmNjQ3MjY0ZmNjZWQxMmVkMWFiNmFjY2U1YTE1MWZmZDMzODNlZDE4OGMyMWE4OTlmY2E2Y2UifQ==",
  36. "x-s-common": "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",
  37. "x-t": "1709171462456"
  38. }
  39. self.pic_headers = {
  40. "authority": "picasso-static.xiaohongshu.com",
  41. "accept": "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8",
  42. "accept-language": "zh-CN,zh;q=0.9",
  43. "cache-control": "no-cache",
  44. "origin": "https://www.xiaohongshu.com",
  45. "pragma": "no-cache",
  46. "sec-ch-ua": "\"Not_A Brand\";v=\"8\", \"Chromium\";v=\"120\", \"Google Chrome\";v=\"120\"",
  47. "sec-ch-ua-mobile": "?0",
  48. "sec-ch-ua-platform": "\"macOS\"",
  49. "sec-fetch-dest": "image",
  50. "sec-fetch-mode": "cors",
  51. "sec-fetch-site": "same-site",
  52. "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
  53. }
  54. self.cookies = {
  55. "a1": a1,
  56. "webId": webId,
  57. "web_session": web_session,
  58. }
  59. @staticmethod
  60. def get_slide_track(distance):
  61. """
  62. :param distance:
  63. :return: <type 'list'>: [[x,y,t], ...]
  64. x:
  65. y: , , 0
  66. t: , :
  67. """
  68. t = 246
  69. count = random.randint(4, 10)
  70. _y_list = [0, 0, -1, 1, 1, 1, 1, 0]
  71. y = 0
  72. x = 0
  73. slide_track = [[0, 0, 246]]
  74. for i in range(count):
  75. if x >= distance:
  76. break
  77. t += random.randint(50, 200)
  78. y += _y_list[random.randint(0, 7)]
  79. x += random.randint(30, 60)
  80. slide_track.append([x, y, t])
  81. if x != distance:
  82. slide_track[-1][0] = distance
  83. return slide_track, slide_track[-1][-1]
  84. @staticmethod
  85. def get_captcha_info(fg_path, bg_path):
  86. """
  87. captcha_info加密参数的封装
  88. :param fg_path:
  89. :param bg_path:
  90. :return:
  91. """
  92. angle = export_single_discern(fg_path, bg_path)
  93. distance = math.ceil(angle * 0.79)
  94. logger.info(f"处理后的 距离为 : {distance}")
  95. track, last_time = SignalCaptcha.get_slide_track(distance)
  96. _time = last_time + random.randint(100, 200) # 处理字段
  97. logger.info(f"_time: {_time}")
  98. logger.info(f"track : {track}")
  99. captcha_info = {
  100. "mouseEnd": encrypt_info(str(distance), _type="mouseEnd"),
  101. "time": encrypt_info(str(_time), _type="time"),
  102. "track": encrypt_info(str(track).replace(" ", ""), _type="track"),
  103. "width": encrypt_info("286", _type="width")
  104. }
  105. captcha_info = json.dumps(captcha_info, ensure_ascii=False, separators=(',', ':'))
  106. return captcha_info
  107. def check(self, rid, captcha_info):
  108. """
  109. :param rid:
  110. :param captcha_info:
  111. :return:
  112. """
  113. data = {
  114. "rid": rid,
  115. "verifyType": 102,
  116. "verifyBiz": "461",
  117. "verifyUuid": self.verify_uuid,
  118. "biz": "sns_web",
  119. "sourceSite": self.source,
  120. "version": "1.0.1",
  121. "captchaInfo": captcha_info
  122. }
  123. headers = self.headers.copy()
  124. data = json.dumps(data, separators=(',', ':'), ensure_ascii=False)
  125. h = get_xs(self.cookies["a1"], "/api/redcaptcha/v2/captcha/check", data, str(int(time.time() * 1000)))
  126. headers.update(h)
  127. logger.info(data)
  128. response = download_q(self.check_url, headers=headers, cookies=self.cookies,
  129. data=data, is_proxy=self.is_proxy)
  130. return response.json()
  131. def register(self):
  132. """
  133. :return:
  134. """
  135. data = {
  136. "secretId": "000",
  137. "verifyType": "102",
  138. "verifyUuid": self.verify_uuid,
  139. "verifyBiz": "461",
  140. "biz": "sns_web",
  141. "sourceSite": self.source,
  142. "version": "1.0.1"
  143. }
  144. data = json.dumps(data, separators=(',', ':'), ensure_ascii=False)
  145. h = get_xs(self.cookies["a1"], "/api/redcaptcha/v2/captcha/register", data, str(int(time.time() * 1000)))
  146. headers = self.headers.copy()
  147. headers["x-s-common"] = ""
  148. headers.update(h)
  149. response = download_q(self.register_url, headers=headers, cookies=self.cookies,
  150. data=data, is_proxy=self.is_proxy)
  151. return response.json()
  152. def run(self):
  153. """
  154. main:
  155. :return:
  156. """
  157. try:
  158. register_data = self.register()
  159. logger.info(f"初始化验证码信息:{register_data}")
  160. data = register_data["data"]
  161. rid = data["rid"]
  162. captcha_info = data["captchaInfo"]
  163. source = json.loads(decrypt_info(captcha_info)[:-2]) # 因为有补位则去掉补位结果
  164. logger.info(f"解密验证码初始信息:{source}")
  165. bg_url = source["backgroundUrl"]
  166. fg_url = source["captchaUrl"]
  167. bg_path = download_q(bg_url, headers=self.pic_headers, cookies={}).content
  168. fg_path = download_q(fg_url, headers=self.pic_headers, cookies={}).content
  169. captcha_info = SignalCaptcha.get_captcha_info(fg_path, bg_path)
  170. logger.info(f"生成验证信息:{captcha_info}")
  171. result = self.check(rid, captcha_info)
  172. logger.info(f"验证结果:{result}")
  173. except Exception as e:
  174. result = {"msg": "失败", "data": {}}
  175. logger.error(e)
  176. return result
  177. if __name__ == '__main__':
  178. verify_uuid = "22318d30-daf4-4fc1-8148-c26d86b0a51a*CiZjkIoB"
  179. web_session = "030037a2d377ba382693826598224a7950081e"
  180. a1 = ""
  181. webId = ""
  182. source = "https://www.xiaohongshu.com/explore/65c02be2000000002c028b93"
  183. A = SignalCaptcha(web_session, verify_uuid, a1, webId, source=source)
  184. res = A.run()
  185. print(res)