小红书笔记完整采集(xhs-note-collector)
这个 skill 解决什么
一篇小红书图文笔记的信息分三层,必须一次取全:
| 层 | 内容 | 取法 |
|---|---|---|
| 文本层 | 标题 / 正文 / 标签 / 互动数 / 发布时间 / 笔记发布地 / 作者昵称 | page.evaluate 读 __INITIAL_STATE__ |
| 媒体层 | 图片列表(实测 1800×2400 webp) | 同上,取 imageList 后 HTTP 下载 |
| 互动层 | 首屏评论 + 子回复 | 双通路:__INITIAL_STATE__ 首屏 + 网络拦截 comment 接口 |
| 作者层 | 小红书号 redId / 作者 IP 属地 | 二跳 user/profile/<userId> 主页 + 源码正则兜底 |
本 skill 取代两个旧 skill(2026-09-21 合并):
- 市场版
xhs-note-scraper—— 走 Chrome + CDP(9222),本机没有 Chrome,100% 跑不通;其脚本逻辑也无法复用到 Tabbit 通路。 - 自建
xhs-note-image-reader—— 只覆盖图片,已并入本 skill。
边界:内容提炼方法论仍归 amz-market-research,别耦合进来。
⚠️ 前置条件(本机专属,必读)
本机没有 Chrome → 只有 Tabbit 一条通路
C:\Program Files\Google\Chrome\Application\chrome.exe不存在;9222 端口无监听。- 浏览器:Edge + Tabbit Browser。小红书登录态在 Tabbit。
- Tabbit CLI:
%LOCALAPPDATA%\Tabbit\LocalAgent\bin\tabbit-cli.exe - 要求:Tabbit 里至少有一个小红书页面(脚本会把它导航到目标笔记)。没有就先去开一个。
Tabbit 独占纪律(最容易踩的坑)
同一时刻只有一个 task 能持有某个 tab。 上一个 task 没 finish,下一个 task claim 同一个 tab 会直接报
A requested tab is owned by another session —— 而 collect.mjs 会在 finally 里自动 finish 自己的 task,
所以正常使用不会踩到;一旦踩到,先释放:
tabbit-cli.exe finish --task <那个任务名> # 默认任务名固定为 xhs-collect
finish 用 plain 形式(不加 --discard)—— --discard 会关掉用户自己开着的标签页。
一键用法
SKILL="$HOME/.workbuddy/skills/xhs-note-collector"
# 单篇
node "$SKILL/scripts/collect.mjs" \
--url "https://www.xiaohongshu.com/explore/<24hex>?xsec_token=..." \
--out ./xhs_note
# 只要文本+评论、不下图(快)
node "$SKILL/scripts/collect.mjs" --note-id <24hex> --no-images --out ./xhs_note
# 批量(links.txt 每行一个链接,# 开头跳过)
node "$SKILL/scripts/batch.mjs" links.txt ./raw_output
常用开关:--no-images --no-author --max-comment-pages N --tab <tabId> --task NAME
输出结构
<out>/
├── raw.json # 全量:note + comments + commentMeta + authorProfile + diagnostics
├── download_report.json # 每张图的落盘情况
└── images/01.webp … # 原图(HTTP 下载,带 Referer)
raw.json 关键字段:
note.title / noteId / type / interactInfo{likedCount,collectedCount,commentCount,shareCount}
note.time / lastUpdateTime(毫秒)/ ipLocation(笔记发布地)/ tagList / tags(正文里解析出的 #标签#)
note.user{nickname,userId,avatar,xiaohongshuId,ipLocation} ← 后两个来自作者主页二跳
note.desc(正文)/ imageCount / imgs[{i,url,w,h}]
comments[{id,nickname,userId,avatar,content,likeCount,ipLocation,createTime,subCommentCount,subComments[]}]
commentMeta{source,hasMore,endpoints,capturedResponses,topLevel,subTotal}
批量时每篇落在 <out>/<noteId>/,失败清单写 <out>/failed.txt(可直接当 links.txt 重跑)。
内部工作流(一次 Tabbit 调用完成 1–4 步)
- 导航 到笔记 URL(
waitUntil: domcontentloaded),先注册page.on("response")监听 comment 接口。 - 取元数据:
page.evaluate读window.__INITIAL_STATE__.note.noteDetailMap[noteId].note(含imageList); 首屏评论在entry.comments.list。 - 触发评论接口:滚动
.note-scroller—— 先回退一屏(scrollTop -= 300)再推到底(原地赋同值不触发 scroll 事件),等 2.5s,收网络响应。 - 双通路合并:state 首屏 + 网络拦截,按评论
id去重。 - 二跳作者主页:
page.goto(/user/profile/<userId>)取basicInfo.redId / ipLocation,读不到就正则兜底 ("redId"\s*:\s*"(\d+)"/"ipLocation"\s*:\s*"([^"]*)")。 - Node 侧下载图片(带
Referer: https://www.xiaohongshu.com/),落盘。 - finally 里 finish task,释放 tab 独占。
关键坑(都在本机实测踩过)
-
代码跑在 Tabbit 的 Node 上下文,不是页面上下文。 直接写
window报window is not defined。必须await page.evaluate(() => {...})。 -
CLI 没有
--read-only参数。 带上 →REQUEST_FAILED,且报错只在 stderr、stdout 为空, 会被误判成"未拿到结果"。合法参数仅--task--request-id--tab|--group--timeout-ms。 -
绝不对
__INITIAL_STATE__里的对象做JSON.parse(JSON.stringify(x))深拷贝。 Vue 响应式会撞circular structure或Object reference chain is too long。 一律手工挑叶子字段、现场拼普通对象(browser-code.js的pickUser/pickComment就是范例)。 -
互动数字段类型不稳定:同一字段 SSR 时是字符串
"301"、hydration 后是数字301。 一律Number()归一,别直接比较。 -
评论要走双通路。 市场版文档说"评论不进
__INITIAL_STATE__"已经过时——当前版本首屏评论 就在entry.comments.list里,与网络拦截结果同源。两条都取、按 id 去重最稳。 -
子回复默认只展开一部分。 实测:页显评论数 16 = 8 主 + 8 子,但实际只抓到 5 条子回复; 用
subCommentCount(每条主评论声明的子回复数)可以判断差多少。要全量得点"展开更多"(有被限流风险)。 -
作者主页二跳后 tab 会停在作者页,导致"找打开着该笔记的 tab"失效 ——
collect.mjs已放宽为 "优先匹配 noteId,否则复用任意小红书标签页"。 -
图片 URL 有时效。 形如
.../202609210954/...,中间是下发时间戳;当场下,隔天可能失效。 下载失败先试把尾部!nd_...后缀摘掉。 -
✅ Read 工具能直接读
.webp,不必装 Pillow 转格式(本机 python 3.13.12 没有 PIL)。 -
Node 脚本里别用
new URL(import.meta.url).pathname拼路径——中文目录会被 URL 编码成%E9%BB%98...导致 ENOENT。用fileURLToPath或process.cwd()。 -
本机 bash 的 PATH 为空(
ls: command not found),先export PATH="/usr/bin:/bin:/c/Windows/System32";本会话 PowerShell 工具 stdout 恒空,别用来取数。
已知限制
- 评论只取首屏 + 已触发的加载,不做"展开更多"全量翻页;
commentMeta.hasMore会告诉你还有没有。 - 子回复可能不全(见坑 6)。
- 不做视频笔记:
note.type === "video"时imageList是封面,视频流在note.video.media.stream.h264[0].masterUrl,本 skill 未覆盖。 - 依赖
__INITIAL_STATE__结构,小红书改版可能失效 → 排查顺序见references/field-notes.md。 - 高频重复采集同一篇会触发限流(实测评论接口返空、页面加载从 4s 变 11s),批量模式已内置 10–15s 随机间隔。
命令速查
TB="$LOCALAPPDATA/Tabbit/LocalAgent/bin/tabbit-cli.exe" # Git Bash;PowerShell 用 & "$env:LOCALAPPDATA\Tabbit\LocalAgent\bin\tabbit-cli.exe"
"$TB" tabs --task xhs-collect # 列标签页(看 state/groupId)
"$TB" finish --task xhs-collect # 释放 tab 独占(保留标签页)
# 脚本内部用:nodejs --task <name> --request-id <id> --tab <tabId> --timeout-ms 180000
结果 >16KB 时 tabbit 会自动走 resource 分片,collect.mjs 已封装自动取回。
微信扫一扫