先看懂 x-s 的结构
小红书 web 端每个 API 请求带 x-s 和 x-t 两个头。x-t 就是毫秒时间戳,没花样。x-s 值得细看——base64 解码后是一个 json,长这样:
{
"s0": "XYW_xxxx...", // 签名主体,XYW_ 是固定前缀
"s1": "xxx", // 环境摘要
"s2": "xxx", // 版本/算法标识
"s3": "xxx", // 计数器
"s4": 1787968800000 // 时间戳,与 x-t 一致
}字段编号和含义随版本有变化,但设计思想不变:签名值 + 签名的"元信息"打包传输,服务端根据元信息选择校验策略。这种设计的好处是算法升级时服务端可以灰度,坏处是——对逆向者来说结构全写在脸上。
定位:签名入口其实挂在 window 上
小红书前端是 webpack 打包,但签名函数最终暴露成了一个全局函数,名字形如 window._webmsxyw。最快的确认方式:
// Console 直接试
typeof window._webmsxyw // "function" 就是它
window._webmsxyw('/api/sns/web/v1/feed', {source_note_id: 'xxx'})
// 返回 { "X-s": "...", "X-t": "..." }如果这个函数存在,事情就简单了——不用读懂算法,直接在浏览器环境里调它。如果不确定,用老办法 hook 请求头:
const _setHeader = XMLHttpRequest.prototype.setRequestHeader;
XMLHttpRequest.prototype.setRequestHeader = function(k, v) {
if (k.toLowerCase() === 'x-s') debugger;
return _setHeader.apply(this, arguments);
};断下后沿调用栈往上两三层就是签名主流程。
算法层:它到底算了什么
跟进去看(代码有混淆但没上 VMP,耐心点能读),签名输入主要是三样:
- url path + 查询串:注意是 path 部分,不含域名
- 请求体:POST 的 json 原样参与,所以改 body 必须重签
- 时间戳:就是 x-t
流程是先拼接待签串,然后过两轮变换:一轮是带魔改常量的摘要(搜十进制 1732584193 即 0x67452301 搜不到、但附近有相似常量,说明 MD5 初始向量被动过手脚),另一轮是查表置换编码。最后拼上 XYW_ 前缀和元信息字段,整体 base64。
工程方案:别手搓,整段抠
x-s 体系手搓的性价比极低——算法版本迭代快(s2 字段就是干这个的),你刚还原完它就换常量。正确姿势是把整个签名模块抠到 Node 里跑:
// sign.js —— 抠出来的最小运行环境
global.window = global;
window.navigator = {
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...',
platform: 'Win32',
webdriver: false,
language: 'zh-CN',
languages: ['zh-CN', 'zh']
};
window.document = { cookie: '', createElement: () => ({ getContext: () => null }) };
window.location = { href: 'https://www.xiaohongshu.com/', host: 'www.xiaohongshu.com' };
window.screen = { width: 1920, height: 1080, colorDepth: 24 };
// 这里粘贴抠出来的签名 chunk(从 webpack runtime 里抽整个模块)
require('./xhs_sign_chunk.js');
const result = window._webmsxyw(process.argv[2], JSON.parse(process.argv[3] || '{}'));
console.log(JSON.stringify(result));Python 侧用 subprocess 或起个常驻 Node 服务调用即可。
精髓:签名对了还不够
x-s 只是入场券,小红书的风控是组合拳:
a1cookie 是设备标识。首次访问种下,和 x-s 里的环境摘要有关联校验。协议模拟时 a1 要从真实浏览器采集后固定,别每次随机。- 计数器字段单调递增。同一个 a1 下,s3(或对应字段)是递增的,跳变太大会触发校验失败——所以签名服务要维护每个设备的状态,不能无状态随机生成。
web_session决定数据权限。未登录能看,登录能搜,但登录态的风控阈值反而更低(因为行为更可归因)。批量场景要算清楚登录态的成本收益。- x-s 和 x-s-common 要一起带。新版本部分接口还校验
x-s-common(设备环境公共摘要),只签 x-s 会返回 461 或空数据。
总结
- x-s 解码后是"签名+元信息"的 json,先看结构再攻算法
- 签名入口常挂在 window 全局,优先尝试直接调用
- 算法迭代快,整段抠模块到 Node 跑比手搓可持续
- a1 cookie、计数器状态、x-s-common 是签名之外的三道暗桩