Install
openclaw skills install xhs-comment小红书评论爬虫。当用户在聊天中发送一个小红书博主主页链接时,自动抓取该博主所有笔记下的评论区数据,保存为本地JSON文件,并生成分析可视化报告。触发条件:用户发送的链接包含 xiaohongshu.com/user/profile 或类似博主主页链接。
openclaw skills install xhs-comment⚠️ 使用前必读:本文档包含大量踩坑经验,执行前务必通读一遍"关键经验"章节。
必须使用 profile="openclaw",不要用 profile="chrome"!
原因:Browser Relay 依赖 Gateway token,Gateway 重启后 token 失效,扩展无法重连,需要用户手动重新配对,体验差且不稳定。
正确方式:
browser(action=start, profile="openclaw") # 启动独立Chrome
browser(action=navigate, profile="openclaw", ...) # 操作小红书
用户需要在这个独立的 Chrome 窗口里扫码登录小红书一次,之后全程自动。
每篇笔记输出一个 JSON 文件,文件名格式:xhs_comments_{博主名}_{note_id}_{timestamp}.json
JSON 结构:
{
"blogger": { "name": "博主昵称", "profile_url": "主页URL" },
"note": {
"id": "笔记ID(URL末尾那段)",
"title": "笔记标题",
"url": "笔记详情页URL"
},
"comments": [
{
"author": "评论者昵称",
"content": "评论内容原文(原文保留,不做截断)",
"time": "评论时间字符串,如'3天前'或'2025-10-23'",
"likes": 42
}
],
"scraped_at": "2026-03-24T15:00:00",
"total_comments": 100
}
保存路径:C:\Users\Downloads\xhs_comments\
小红书是 Vue 动态渲染,JS 的 querySelector / getElementById 等 DOM API 全部无效。
唯一可靠的方式:innerText
// ✅ 正确:用 innerText 获取渲染后的纯文本
browser(action=act, kind=evaluate, fn="document.body.innerText")
// ❌ 错误:DOM 选择器全部返回空
document.querySelector('.comment-item') // 无效
获取文本后,按固定格式解析评论——用户名、评论内容、时间、点赞数都在文本流中按固定顺序排列。
小红书评论有折叠,只有展开才会加载完整嵌套评论链。
// 点击所有"展开N条回复"按钮
var btns = Array.from(document.querySelectorAll('*')).filter(
e => e.textContent.match(/^展开 \d+ 条回复$/)
);
btns.forEach(b => { try { b.click(); } catch(e) {} });
/explore/{noteId} 可直接访问navigate 后 JS 点击a[href*="/user/profile/{userId}/"] 中的前几条高频访问小红书必然触发风控验证码。此时:
/website-login/captcha?PowerShell 不支持 && 语法,会报 Unexpected token 错误。
所有命令行全部用 ; 分隔,或直接用 Python 执行:
# ❌ 错误
python script.py && echo done
# ✅ 正确
python script.py; echo done
# 最佳:直接用 Python
python script.py
PowerShell 重定向和 subprocess 调用默认 GBK,会导致中文文件名乱码。
所有文件操作必须显式指定 UTF-8:
with open(path, 'w', encoding='utf-8') as f:
f.write(content)
读取时加 errors='replace' 容忍乱码:
with open(path, 'r', encoding='utf-8', errors='replace') as f:
data = json.load(f)
STSong / SimHei 等中文字体均不含 emoji 字形,图表中会出现字形缺失警告(Glyph missing)。
解决:matplotlib 图用纯文字标签,emoji 只用于 HTML 页面。
browser(action=start, target=host, profile=openclaw)
browser(action=navigate, target=host, profile=openclaw,
url=<用户发送的主页链接>)
告知用户:需要在打开的 Chrome 窗口中用小红书 App 扫码登录。
页面加载完成后,用 JS 提取所有笔记链接:
var noteLinks = document.querySelectorAll(
'a[href*="/user/profile/{userId}/"]'
);
// 遍历,提取 href 和 noteId
noteLinks.forEach(link => {
var href = link.getAttribute('href');
// href 格式:/user/profile/59757acd.../69bf6c43000000002800807a
var parts = href.split('/');
var noteId = parts[parts.length - 1].split('?')[0];
// 获取标题(附近元素的文本)
var parent = link.closest('[class*="note"], div');
});
注意:置顶笔记在列表最前面,index=0 对应第一条。
对每篇笔记:
进入笔记详情
browser(action=act, kind=evaluate, fn="noteLinks[i].click()")browser(action=act, kind=wait, timeMs=5000)验证是否正确进入
/explore/{noteId} 或笔记详情页展开回复 + 滚动
// 展开所有回复
var btns = Array.from(document.querySelectorAll('*')).filter(
e => e.textContent.match(/^展开 \d+ 条回复$/)
);
btns.forEach(b => { try { b.click(); } catch(e) {} });
// 滚动加载
window.scrollBy(0, 800);
提取评论(核心!)
var bodyText = document.body.innerText;
// bodyText 格式示例:
// 评论者昵称
// 评论内容...
// 3天前
// 赞 42
// ---
// 评论者2
// 评论内容2...
用换行符 \n 分割,按固定模式解析(用户名 → 内容 → 时间 → 点赞 → 分隔线)。
返回笔记列表
history.back(); // 或重新 navigate 到主页
import json, os
from datetime import datetime
data = {
"blogger": {"name": "...", "profile_url": "..."},
"note": {"id": "...", "title": "...", "url": "..."},
"comments": [...],
"scraped_at": datetime.now().isoformat(),
"total_comments": len(comments)
}
out_dir = os.path.join(os.path.expanduser("~"), "Downloads", "xhs_comments")
os.makedirs(out_dir, exist_ok=True)
ts = datetime.now().strftime("%Y%m%d_%H%M%S")
filename = f"xhs_comments_{博主名}_{note_id}_{ts}.json"
with open(os.path.join(out_dir, filename), "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
print(f"Saved: {filepath}")
用户可能会要求生成可视化分析。在 Downloads\xhs_comments_analysis\ 目录生成:
分析脚本(写到临时路径,执行后清理):
# -*- coding: utf-8 -*-
import os, json, re
from collections import Counter
import jieba
import matplotlib; matplotlib.use('Agg')
import matplotlib.pyplot as plt
from wordcloud import WordCloud
# 中文字体
FONT = r"C:\Windows\Fonts\STSONG.TTF"
plt.rcParams['font.sans-serif'] = ['STSong', 'SimHei']
plt.rcParams['axes.unicode_minus'] = False
HTML 报告(纯 JS,无 Python 模板引擎):
ALL_NOTES JSON 变量<select> 下拉框data-content 属性存储原文,JS 高亮匹配生成顺序:
Start-Process 打开| 场景 | 处理方式 |
|---|---|
| 页面加载失败 | 重试 2 次,每次等待 5 秒 |
| 评论数量为 0 | 记录,跳过,继续下一篇 |
| 验证码拦截 | 提示用户在浏览器窗口中完成,任务暂停等待 |
| 笔记已删除(404) | 跳过,继续下一篇 |
| 滚动后内容不变 | 判定为已加载完毕,停止滚动 |
| URL 格式不对 | JS 点击进入,不用直接 navigate |
| innerText 提取为空 | 等待更长时间,或截图排查 |
| Python 脚本报错 | 用 Python 重写逻辑,避免 shell 转义 |
profile="openclaw",不是 "chrome")C:\Windows\Fonts\STSONG.TTFprofile="openclaw" 而非 profile="chrome"querySelectorAll + href 解析element.click(),不用直接 navigatedocument.body.innerText,不用 DOM 选择器encoding='utf-8' 显式指定; 而非 &&