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
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)
|