4-小红书签名体系与风控实战
4-小红书签名体系与风控实战(2026-10 实测)
实测时间:2026-10-03/04 | webBuild 6.56.2 | Chrome 154 | 项目:Z:\学习资源和笔记\Python爬虫全栈学习\项目\小红书
老版本资料(MediaCrawler 时代)的window._webmsxyw全局签名 +edith单域 + v1 接口已部分过时,本文以实测为准。
一、风控全景:五层体系
┌─ 签名层 ── X-s/X-t(两代并存)+ X-S-Common(设备指纹包)+ traceid 双头 |
域名分流(2026-10 实测)
| 域名 | 用途 |
|---|---|
so.xiaohongshu.com |
搜索(v2 接口独占此域) |
edith.xiaohongshu.com |
其余业务 API(feed/评论/登录/用户) |
as.xiaohongshu.com |
安全脚本(ds) |
fe-static.xhscdn.com |
静态资源(vendor/ace 签名脚本) |
t2.xiaohongshu.com / apm-fe |
埋点/监控(不模拟,未见因此触发风控) |
二、签名层:两代 X-s 并存(关键发现)
2.1 浏览器在用的:XYS_ v1(seccore_signv2)
vendor-dynamic.js 里的组装链(源码冻结在 analysis/js_frozen/):
seccore_signv2(uri, data): |
mnsv2由as.xiaohongshu.com/api/sec/v1/ds?appId=xhs-pc-web(OB 混淆 JSVMP,59.8KB)动态挂载,
源码中搜不到字符串(运行时拼属性名)。Node 补环境只挂_dsf/_dsn/_dsl,mnsv2 挂载需更完整 DOM——未深挖。- 工具函数(webpack 模块 384):
Pu=MD5、lz=encodeUtf8(encodeURIComponent 后按字节展开)、tb=CRC32 变体(多项式 0xedb88320,-1^c^poly)、xE=自定义字母表 base64
(字母表ZmserbBoHQtNP+wOcza/LpngG8yJq42KWYj0DSfdikx3VT16IlUAFM97hECvuRX5,与 MediaCrawler 一致未变)。
2.2 我们产出的:XYW_ v2(ace.js)——部分接口接受
fe-static.xhscdn.com/as/v1/f218/a15/public/04b29480233f4def5c875875b6bdc3b1.js(148KB,_ace_ 系 VMP)
挂载 window._webmsxyw(uri, data),返回:
{"X-s": "XYW_" + 标准base64({"signSvn":"56","signType":"x2","appId":"xhs-pc-web","signVersion":"1","payload":"<416hex>"}), |
实测分接口(2026-10-03/04):评论 GET 某时段收 code=0;feed/search/login 拒(461/-1)——
v2 是灰度签名,不能全局依赖。
2.3 XYS_ v1 的落地:浏览器 oracle + Python 组装(主通道,全接口 code=0)
mnsv2 由 vendor-dynamic.js 内嵌 288KB JSVMP(signV2Init() eval 执行,webpack 模块 4455)挂载——
Node 补环境执行挂载失败于 VM 环境分流(_AUuXfEG27Xa3x 解释器黑盒,0 次挂载尝试)。
落地架构(Boss stoken 同款供给思路):
Python 爬虫 ──POST(uri,data)──> 本地队列 127.0.0.1:17777(mns_oracle.py serve) |
- md5/payload/SSK(x6/x7)/base64 全部本地纯算,mnsv2 是唯一黑盒走 oracle
- worker fetch localhost 需 CORS 头
Access-Control-Allow-Private-Network: true(Chrome PNA) - 浏览器导航清 worker(页面级注入),签名页需保持不动
- XYS_ 内部结构(解包实测):
{x0:"4.4.3"(SDK版本), x1:"xhs-pc-web", x2:"Windows", x3:<mns0301_串>, x4:typeof data, x5:md5(payload)},登录后附加 x6/x7(SSK) - 时效宽松:实测 35 分钟前的签名仍可重放(绑 uri+body 不可跨请求复用)
2.4 补环境要点(Node 24)
- 脚本尾部自带 Node 兼容(
glb = typeof window === 'undefined' ? global : window),最小环境即可跑通 - Node 21+ 的
navigator是只读 getter,需Object.defineProperty(globalThis, 'navigator', {...})覆盖 - 最大坑:POST body 必须以对象传给
_webmsxyw(浏览器 axios 拦截器传的是对象,VM 内部自行
JSON.stringify);传字符串会走不同分支 → 签名与 body 不匹配 → 406 - 对拍验证:浏览器与 Node 同输入产出 payload 逐字节一致(同 208 字节、同 head hex)——环境等价实证
2.5 X-S-Common 结构全解(可本地生成)
解码(自定义 b64 逆 + unquote)后:
{"s0":5, "s1":"", |
x9 的大坑(本轮 406 根因之一):ed.tb 是全长 CRC32 变体(多项式 0xedb88320、-1^c^poly、JS 32 位有符号可为负)——不是 MediaCrawler 老版 mrc 的”前 57 字符截断”
(那是另一处 x9)。实测对拍:全长版与浏览器逐位一致(-587810826),截断版必错。
x8 指纹三形态:初始 ~100 字符(不成熟)→ 页面运行后 1600 字符(成熟 b1)→ 特定 URL
1928 字符(FingerprintV3 miniUa)。b1 前缀由环境决定(同机同前缀),但换 a1 必须整套换档案
(新 a1 配旧 x8 = 服务端识破 code:-1)。
2.5 SSK 密钥交换(2026 新增)
- 登录
qrcode/status请求带client_public_key_base64(X25519 公钥) - 服务端返回加密 SSK → 前端 X25519 共享密钥 + secretbox(XSalsa20-Poly1305)解密 → localStorage
webSsk - 每个签名算
x6/x7 = sha1组合(encSskSign/encSsk,逐 appId) - 补环境直接从浏览器导出 webSsk 复用即可(device.json),无需重放交换
三、数据接口(2026-10 实测)
| 接口 | 方法/域 | 关键参数 | 响应要点 |
|---|---|---|---|
| 搜索 | POST so./api/sns/web/v2/search/notes |
keyword/page/page_size/search_id/sort/note_type/image_formats/session_id | items[].note_card(display_title/interact_info/cover/xsec_token/corner_tag_info) |
| 详情 | POST edith./api/sns/web/v1/feed |
source_note_id/xsec_token/xsec_source/extra.need_body_topic | note_card(title/desc/time/tag_list/image_list/video.stream/interact_info/ip_location) |
| 评论 | GET edith./api/sns/web/v2/comment/page |
note_id/cursor/xsec_token | comments[](content/like_count/create_time/user_info)+ has_more/cursor;子评论 sub/page |
| 用户笔记 | GET edith./api/sns/web/v1/user_posted |
user_id/num/cursor | notes[](note_id/xsec_token/display_title) |
search_id 来源:v2 响应不返回;它由搜索页 SSR HTML 注入(__INITIAL_STATE__.search.searchContext.searchId)——
爬虫须先 GET /search_result?keyword= 页面提取(顺带 cookie 预热),同关键词翻页复用。
xsec_token 机制:服务端逐笔记签发的访问令牌,从搜索/用户列表响应里带出,feed/评论请求必须回传——
纯构造 note_id 直调 feed 会被拒(-104 类)。
四、风控实证(脚本流量视角)
| 信号 | 语义 | 应对 |
|---|---|---|
| 406 + code:-1 | 签名/头与请求不匹配 | 修签名(对象传参);X-S-Common 缺失在浏览器侧表现为 -104 |
| 461 + code:0 空data | 触发 redcaptcha 验证挑战(verifyBiz=461,见下) | 扫码解锁(redcaptcha 流程);勿连发重试 |
| 471 | 验证流程瞬断(浏览器实测自动重试恢复) | 退避重试 |
| 300011 | 账号异常(伴随 APP 被踢/短信警告) | 全局止损;真人 APP 重登 + 浏览器扫码降级 |
| -100 | web_session 轮换/失效 | 吸收 Set-Cookie / 重登 |
| -104 | 签名与 cookie 会话不一致 / 缺 x-s-common | 对齐后重试 |
461 的真实语义:redcaptcha 验证挑战(重要修正)
设备指纹突变(清 cookie)触发浏览器 302 到:
/website-login/captcha?verifyUuid=<uuid>&verifyType=124&verifyBiz=461 |
- verifyType=124 = APP 扫码身份确认(redcaptcha 自研,非滑块/点选)
- 协议:
POST /api/redcaptcha/v2/qr/init(verifyUuid→rid 渲染二维码)→ APP 扫码确认 →POST /api/redcaptcha/v2/qr/status/query轮询 → 461 解除 - 对抗哲学:身份确认型验证码,机器不可纯算破解——只能减少触发(环境做真)+ 扫码解锁
环境注册:shield/webprofile(真人化关键差异)
真人浏览器每次页面导航都上报 POST as.xiaohongshu.com/api/sec/v1/shield/webprofile:
{"platform":"Windows","sdkVersion":"4.3.5","svn":"2","profileData":"<加密hex ~11KB>"} |
服务端凭 profileData 维护 a1↔环境映射。纯 API 脚本从不上报 = 环境档案缺失/突变 =
异常评分。应对:会话启动时重放导出的 webprofile(http_client.warm_profile 已实现)。
打标是累积的(本轮实证时间线)
几分钟内十几条脚本请求(其中约一半是无效签名试探 + 过期 cookie 的必败请求)→ 461 验证挑战
持续 → 升级 300011(账号异常 + APP 被踢 + 风控短信)。无效请求也在烧信用——先本地对拍再上服务端。
461 期间浏览器同 cookie 正常(挑战只挂在脚本流量维度)。
五、可迁移方法论
- 新旧签名并存时先测新版接受度:XYW_ v2 服务端直接收,省掉 mnsv2 JSVMP 按天级的还原预算。
- 补环境对拍标准:同输入 payload 逐字节一致 = 环境等价(本轮 208 字节 hex head 全同)。
- SSR 页面是隐藏参数源:search_id 这类”响应不返回、请求必带”的参数,先翻
__INITIAL_STATE__。 - 同名脚本的版本路由:
04b2...js在as/v1/3e44/和as/v1/f218/a15/两路径下内容不同——
扣脚本认准实际加载的 URL(hash 校验)。 - 风控信号要先分层再处置:461(环境打标)/ 300011(账号处置)/ -100(会话轮换)三者解法完全不同。
- 止损规则代码化(Boss 沉淀):单请求最多刷 2 次签名、461 连续 2 次全局停、300011 直接终止。
refer
- 项目:Z:\学习资源和笔记\Python爬虫全栈学习\项目\小红书(analysis/ 有全部抓包样本与 JS 冻结)
- MediaCrawler(x-s 自定义 b64/CRC 对照样本):https://github.com/NanmiCoder/MediaCrawler
- 关联:《2-企查查反爬实战》《3-Boss直聘安全网关》《补环境对拍方法论》《浏览器指纹伪造方案》
声明:
- 若文章存在错误,望诸君不吝指正^
blog仅供个人记录学习所用- 部分笔记由于年代久远,做的笔记找不到最初是引用谁的,若是不允许引用转载,请联系我










