Skip to content

fix(xiaohongshu/user): 登录墙报 AUTH_REQUIRED + 修 hydration 竞态#1945

Merged
jackwener merged 2 commits into
jackwener:mainfrom
huanghe:pr/xhs-user-login-wall
Jun 15, 2026
Merged

fix(xiaohongshu/user): 登录墙报 AUTH_REQUIRED + 修 hydration 竞态#1945
jackwener merged 2 commits into
jackwener:mainfrom
huanghe:pr/xhs-user-login-wall

Conversation

@huanghe

@huanghe huanghe commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

xiaohongshu user 在两种场景下都误报,下游(以及人工排查)无法区分到底是「登录失效」「解析失败」还是「真空号」。本 PR 把两类误报修成语义正确的结构化错误。改动集中在 clis/xiaohongshu/user.js(+ 新增测试),USER_SNAPSHOT_JS 被 rednote 复用,rednote 一并受益。

场景 1:登录态失效被误报成 "Malformed user store" / EMPTY_RESULT

小红书 profile 页比 search 更吃登录态。会话失效 / 被风控降级时,访问 /user/profile/<id> 会 302 重定向到 /login(__INITIAL_STATE__.user.loggedIn=false,页面挂登录二维码)。旧逻辑读到登录页的空 user store → 抛 Malformed Xiaohongshu user snapshot: user store was not found,或等到 store 出现却 notes 全空 → EMPTY_RESULT

下游据此把登录失效误判成解析失败 / 空号:白等 rate-limit cooldown、误锁平台。实测 2026-06-09:风控把 profile 浏览态降级 → 整批 seed 创作者全 302 到 /login,被报成 Malformed,排查很久才定位是登录墙。

修复:USER_SNAPSHOT_JS 增加 loginWall 检测(pathname 落在 /login,或 loggedIn===false),命中即抛 AuthRequiredError(code AUTH_REQUIRED,exit NOPERM)。语义正确,下游一眼可辨,并提示用户重登 xiaohongshu.com。

场景 2:hydration 竞态(间歇性 "user store was not found")

__INITIAL_STATE__.user 由 SSR / client bootstrap 异步注入;page.goto立刻 page.evaluate 会撞 hydration 窗口 → store / notes 尚未就绪。兄弟命令 note.js(page.wait({time:2+rand*3}))与 download.js(page.wait({time:1+rand*2}))早已用 goto 后等待规避,唯独 user.js 漏了 → 慢加载必现、快加载侥幸过的间歇性失败。

修复:新增 readUserSnapshotHydrated —— 先快读一次(已就绪零额外延迟,保住快加载路径),未拿到笔记且非登录墙才 wait 后重试至多 maxRetries 次。笔记是 [tab[], ...] 形态、首屏可能晚于 store 填充,故用 countFlatNotes(展平后真实条数)作就绪判据,而非 store 在即停。登录墙命中立即停(再等无用);真·空号走满重试后由 EmptyResultError 正确收尾。

测试

  • 新增 clis/xiaohongshu/user.test.js(14 例):countFlatNotes / isLoginWallSnapshot / readUserSnapshotHydrated(快路径不 wait、慢加载重试、登录墙即停、空号走满重试)/ command.func(登录墙→AUTH_REQUIRED、笔记晚到→重试成功、空号→EMPTY_RESULT)
  • 端到端:对真实登录失效会话,opencli xiaohongshu user <id> 现报 AUTH_REQUIRED(exit 77),替代旧的误导性 Malformed / EMPTY
  • xiaohongshu + rednote 全套测试通过(USER_SNAPSHOT_JS 被 rednote 复用,loginWall 为 additive 字段、无回归);tsc --noEmit 干净

兼容性

  • 登录墙现抛 AuthRequiredError(exit 77 / code AUTH_REQUIRED),是新的、更准确的错误分类 —— 之前这种情况错误地表现为 Malformed / EMPTY_RESULT
  • 快加载路径零额外延迟(先快读、就绪即返回),只有未就绪才进入重试等待
  • loginWall 是 snapshot 的 additive 字段,不改既有输出结构

ml-scout and others added 2 commits June 14, 2026 06:39
`xiaohongshu user` 在两种场景下都误报,下游无法区分:

## 场景 1:登录态失效被误报成 "Malformed user store" / EMPTY_RESULT
小红书 profile 页比 search 更吃登录态。会话失效 / 被风控降级时,访问
`/user/profile/<id>` 会 302 重定向到 `/login`(`__INITIAL_STATE__.user.loggedIn=false`,
页面挂登录二维码)。老逻辑读到登录页的空 user store → 抛 "Malformed Xiaohongshu user
snapshot: user store was not found",或等到 store 出现却 notes 全空 → EMPTY_RESULT。

下游(如 ml-scout)据此把**登录失效**误判成**解析失败 / 空号**:白等 rate-limit
cooldown、误锁平台。实测 2026-06-09:风控把 profile 浏览态降级 → 整批 seed 创作者全
302 到 /login,被报成 Malformed,排查了很久才定位是登录墙。

修复:USER_SNAPSHOT_JS 增加 `loginWall` 检测(pathname 落在 /login,或 loggedIn===false),
命中即抛 `AuthRequiredError`(code AUTH_REQUIRED,exit NOPERM)。语义正确,下游一眼可辨,
提示用户重登 xiaohongshu.com。

## 场景 2:hydration 竞态(间歇性 "user store was not found")
`__INITIAL_STATE__.user` 由 SSR / client bootstrap 异步注入;`page.goto` 后**立刻**
`page.evaluate` 会撞 hydration 窗口 → store/notes 尚未就绪。note.js(`page.wait({time:2+rand*3})`)
与 download.js(`page.wait({time:1+rand*2})`)早已用 goto 后等待规避,唯独 user.js 漏了
→ 慢加载必现、快加载侥幸过的间歇性失败(2026-06-09 复现、2026-05-20 亦有记录)。

修复:新增 `readUserSnapshotHydrated`——先快读一次(已就绪零额外延迟,保住快加载路径),
未拿到笔记**且非登录墙**就 wait 后重试至多 maxRetries 次。笔记是 `[tab[], ...]` 形态、首屏
可能晚于 store 填充,故用 `countFlatNotes`(展平后真实条数)作为就绪判据,而非 store 在即停。
登录墙命中立即停(再等无用);真·空号走满重试后由 EmptyResultError 正确收尾。

## 测试
- 新增 clis/xiaohongshu/user.test.js(14 例):countFlatNotes / isLoginWallSnapshot /
  readUserSnapshotHydrated(快路径不 wait、慢加载重试、登录墙即停、空号走满重试)/
  command.func(登录墙→AUTH_REQUIRED、笔记晚到→重试成功、空号→EMPTY_RESULT)
- 端到端:对真实登录失效会话,`opencli xiaohongshu user <id>` 现报 AUTH_REQUIRED(exit 77),
  替代旧的误导性 Malformed/EMPTY
- xiaohongshu + rednote 全套 231 例通过(USER_SNAPSHOT_JS 被 rednote 复用,loginWall 为
  additive 字段、无回归)
@jackwener jackwener merged commit 1ff4de3 into jackwener:main Jun 15, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants