算法暴露接口(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

import math
import time
import json
import random
from kuaishou.ks_http import download_q
from utils.rotate_captcha import export_single_discern
from xhs.xhs_param import get_xs, encrypt_info, decrypt_info
from loguru import logger
class SignalCaptcha(object):
"""
小红书单旋转验证码
"""
def __init__(self, web_session, verify_uuid,
a1, webId, source="", is_proxy=False):
self.is_proxy = is_proxy
self.source = source
self.verify_uuid = verify_uuid
self.register_url = "https://edith.xiaohongshu.com/api/redcaptcha/v2/captcha/register"
self.check_url = "https://edith.xiaohongshu.com/api/redcaptcha/v2/captcha/check"
self.headers = {
"authority": "edith.xiaohongshu.com",
"accept": "application/json, text/plain, */*",
"accept-language": "zh-CN,zh;q=0.9",
"cache-control": "no-cache",
"content-type": "application/json;charset=UTF-8",
"origin": "https://www.xiaohongshu.com",
"pragma": "no-cache",
"referer": "https://www.xiaohongshu.com/",
"sec-ch-ua": "\"Not_A Brand\";v=\"8\", \"Chromium\";v=\"120\", \"Google Chrome\";v=\"120\"",
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": "\"macOS\"",
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-site",
"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",
"x-s": "XYW_eyJzaWduU3ZuIjoiNTEiLCJzaWduVHlwZSI6IngxIiwiYXBwSWQiOiJsb2dpbiIsInNpZ25WZXJzaW9uIjoiMSIsInBheWxvYWQiOiJkNzkxNmQ0MDAyOTZmZDg4ZmIzYjcxOGY2MWRlOGU2MTEwYzZmMTBlMjZiOWI0NTQ1OTg0M2VjMTI2NmRlMGZiNDNkZGI3MmE5MWYyOTJkZTM4Yjk3MDlmZjcyMDBmMTRjOWUzYmZkYTFmYWExZWI5MGQ3NGFhMzFiNTRjNzJjZDBkNzRhYTMxYjU0YzcyY2RhYzQ4OWI5ZGE4Y2U1ZTQ4ZjRhZmI5YWNmYzNlYTI2ZmUwYjI2NmE2YjRjYzNjYjVhMDE2NzA1NzIyMWMxZjhkMzY4YzFkMjBjZGE4MGY5OWIxMWYyZWFhMGE5ZmQ3MWQ3NmZmZTgwMmI3MjM3NWQ2OWZiY2JjYzk1OTUxMmNhZDRmZWFjNWU1MjY1MDljYTliZDA3MzE0OGZjMDEzYzJlMzU4ZWE1ZDg5ZGQ4MzZkNWU2OTM4NGJkN2FmNjQ3MjY0ZmNjZWQxMmVkMWFiNmFjY2U1YTE1MWZmZDMzODNlZDE4OGMyMWE4OTlmY2E2Y2UifQ==",
"x-s-common": "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",
"x-t": "1709171462456"
}
self.pic_headers = {
"authority": "picasso-static.xiaohongshu.com",
"accept": "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8",
"accept-language": "zh-CN,zh;q=0.9",
"cache-control": "no-cache",
"origin": "https://www.xiaohongshu.com",
"pragma": "no-cache",
"sec-ch-ua": "\"Not_A Brand\";v=\"8\", \"Chromium\";v=\"120\", \"Google Chrome\";v=\"120\"",
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": "\"macOS\"",
"sec-fetch-dest": "image",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-site",
"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"
}
self.cookies = {
"a1": a1,
"webId": webId,
"web_session": web_session,
}
@staticmethod
def get_slide_track(distance):
"""
根据滑动距离生成滑动轨迹
:param distance: 需要滑动的距离
:return: 滑动轨迹<type 'list'>: [[x,y,t], ...]
x: 已滑动的横向距离
y: 已滑动的纵向距离, 除起点外, 均为0
t: 滑动过程消耗的时间, 单位: 毫秒
"""
t = 246
count = random.randint(4, 10)
_y_list = [0, 0, -1, 1, 1, 1, 1, 0]
y = 0
x = 0
slide_track = [[0, 0, 246]]
for i in range(count):
if x >= distance:
break
t += random.randint(50, 200)
y += _y_list[random.randint(0, 7)]
x += random.randint(30, 60)
slide_track.append([x, y, t])
if x != distance:
slide_track[-1][0] = distance
return slide_track, slide_track[-1][-1]
@staticmethod
def get_captcha_info(fg_path, bg_path):
"""
图片识别 以及对于captcha_info加密参数的封装
:param fg_path:
:param bg_path:
:return:
"""
angle = export_single_discern(fg_path, bg_path)
distance = math.ceil(angle * 0.79)
logger.info(f"处理后的 距离为 : {distance}")
track, last_time = SignalCaptcha.get_slide_track(distance)
_time = last_time + random.randint(100, 200) # 处理字段
logger.info(f"_time: {_time}")
logger.info(f"track : {track}")
captcha_info = {
"mouseEnd": encrypt_info(str(distance), _type="mouseEnd"),
"time": encrypt_info(str(_time), _type="time"),
"track": encrypt_info(str(track).replace(" ", ""), _type="track"),
"width": encrypt_info("286", _type="width")
}
captcha_info = json.dumps(captcha_info, ensure_ascii=False, separators=(',', ':'))
return captcha_info
def check(self, rid, captcha_info):
"""
验证函数:最终提交验证码验证请求
:param rid:
:param captcha_info:
:return:
"""
data = {
"rid": rid,
"verifyType": 102,
"verifyBiz": "461",
"verifyUuid": self.verify_uuid,
"biz": "sns_web",
"sourceSite": self.source,
"version": "1.0.1",
"captchaInfo": captcha_info
}
headers = self.headers.copy()
data = json.dumps(data, separators=(',', ':'), ensure_ascii=False)
h = get_xs(self.cookies["a1"], "/api/redcaptcha/v2/captcha/check", data, str(int(time.time() * 1000)))
headers.update(h)
logger.info(data)
response = download_q(self.check_url, headers=headers, cookies=self.cookies,
data=data, is_proxy=self.is_proxy)
return response.json()
def register(self):
"""
初始化函数:获得验证码初始化信息请求
:return:
"""
data = {
"secretId": "000",
"verifyType": "102",
"verifyUuid": self.verify_uuid,
"verifyBiz": "461",
"biz": "sns_web",
"sourceSite": self.source,
"version": "1.0.1"
}
data = json.dumps(data, separators=(',', ':'), ensure_ascii=False)
h = get_xs(self.cookies["a1"], "/api/redcaptcha/v2/captcha/register", data, str(int(time.time() * 1000)))
headers = self.headers.copy()
headers["x-s-common"] = ""
headers.update(h)
response = download_q(self.register_url, headers=headers, cookies=self.cookies,
data=data, is_proxy=self.is_proxy)
return response.json()
def run(self):
"""
main: 执行函数
:return:
"""
try:
register_data = self.register()
logger.info(f"初始化验证码信息:{register_data}")
data = register_data["data"]
rid = data["rid"]
captcha_info = data["captchaInfo"]
source = json.loads(decrypt_info(captcha_info)[:-2]) # 因为有补位则去掉补位结果
logger.info(f"解密验证码初始信息:{source}")
bg_url = source["backgroundUrl"]
fg_url = source["captchaUrl"]
bg_path = download_q(bg_url, headers=self.pic_headers, cookies={}).content
fg_path = download_q(fg_url, headers=self.pic_headers, cookies={}).content
captcha_info = SignalCaptcha.get_captcha_info(fg_path, bg_path)
logger.info(f"生成验证信息:{captcha_info}")
result = self.check(rid, captcha_info)
logger.info(f"验证结果:{result}")
except Exception as e:
result = {"msg": "失败", "data": {}}
logger.error(e)
return result
if __name__ == '__main__':
verify_uuid = "22318d30-daf4-4fc1-8148-c26d86b0a51a*CiZjkIoB"
web_session = "030037a2d377ba382693826598224a7950081e"
a1 = ""
webId = ""
source = "https://www.xiaohongshu.com/explore/65c02be2000000002c028b93"
A = SignalCaptcha(web_session, verify_uuid, a1, webId, source=source)
res = A.run()
print(res)