← Back to skills
extension
Category: Data & AnalyticsNo API key required

小红书笔记采集器

小红书(RedNote)笔记完整采集器:一次拿到标题正文标签与互动数、图片原图、评论区明细和作者信息,支持单篇与批量。当用户需要采集小红书笔记、提取笔记图片里的内容、分析评论区、批量采集链接、做竞品笔记监控时使用。触发词:小红书采集、小红书数据、小红书笔记、小红书评论、小红书图片、读小红书长图、笔记互动数、批量小红书。

personAuthor: user_d3687acdhubcommunity

小红书笔记完整采集(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 步)

  1. 导航 到笔记 URL(waitUntil: domcontentloaded),先注册 page.on("response") 监听 comment 接口。
  2. 取元数据:page.evaluate 读 window.__INITIAL_STATE__.note.noteDetailMap[noteId].note(含 imageList); 首屏评论在 entry.comments.list。
  3. 触发评论接口:滚动 .note-scroller —— 先回退一屏(scrollTop -= 300)再推到底(原地赋同值不触发 scroll 事件),等 2.5s,收网络响应。
  4. 双通路合并:state 首屏 + 网络拦截,按评论 id 去重。
  5. 二跳作者主页:page.goto(/user/profile/<userId>) 取 basicInfo.redId / ipLocation,读不到就正则兜底 ("redId"\s*:\s*"(\d+)" / "ipLocation"\s*:\s*"([^"]*)")。
  6. Node 侧下载图片(带 Referer: https://www.xiaohongshu.com/),落盘。
  7. finally 里 finish task,释放 tab 独占。

关键坑(都在本机实测踩过)

  1. 代码跑在 Tabbit 的 Node 上下文,不是页面上下文。 直接写 window 报 window is not defined。必须 await page.evaluate(() => {...})。

  2. CLI 没有 --read-only 参数。 带上 → REQUEST_FAILED,且报错只在 stderr、stdout 为空, 会被误判成"未拿到结果"。合法参数仅 --task --request-id --tab|--group --timeout-ms。

  3. 绝不对 __INITIAL_STATE__ 里的对象做 JSON.parse(JSON.stringify(x)) 深拷贝。 Vue 响应式会撞 circular structure 或 Object reference chain is too long。 一律手工挑叶子字段、现场拼普通对象(browser-code.js 的 pickUser/pickComment 就是范例)。

  4. 互动数字段类型不稳定:同一字段 SSR 时是字符串 "301"、hydration 后是数字 301。 一律 Number() 归一,别直接比较。

  5. 评论要走双通路。 市场版文档说"评论不进 __INITIAL_STATE__"已经过时——当前版本首屏评论 就在 entry.comments.list 里,与网络拦截结果同源。两条都取、按 id 去重最稳。

  6. 子回复默认只展开一部分。 实测:页显评论数 16 = 8 主 + 8 子,但实际只抓到 5 条子回复; 用 subCommentCount(每条主评论声明的子回复数)可以判断差多少。要全量得点"展开更多"(有被限流风险)。

  7. 作者主页二跳后 tab 会停在作者页,导致"找打开着该笔记的 tab"失效 —— collect.mjs 已放宽为 "优先匹配 noteId,否则复用任意小红书标签页"。

  8. 图片 URL 有时效。 形如 .../202609210954/...,中间是下发时间戳;当场下,隔天可能失效。 下载失败先试把尾部 !nd_... 后缀摘掉。

  9. ✅ Read 工具能直接读 .webp,不必装 Pillow 转格式(本机 python 3.13.12 没有 PIL)。

  10. Node 脚本里别用 new URL(import.meta.url).pathname 拼路径——中文目录会被 URL 编码成 %E9%BB%98... 导致 ENOENT。用 fileURLToPath 或 process.cwd()。

  11. 本机 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 已封装自动取回。