小红书是内容驱动的平台,用户真实评价的价值远高于商品详情页的官方介绍。 很多做电商的人都在爬小红书笔记做竞品分析、口碑调研。
小红书的采集有两个特殊难点:无限滚动加载和动态class(类名随机变化)。处理不好,翻页到一半就卡死。今天用一套完整的采集方案把这两个问题一并解决。
先说这个流程能做什么
输入: 一个关键词列表(如“蓝牙耳机”“充电宝”) 输出:
- 笔记清单(标题、点赞数、收藏数、作者、发布时间)
- 每条笔记的评论(评论内容、评论者、点赞数、时间)
适用场景: 竞品口碑分析、用户需求挖掘、选品灵感收集。
核心难点与应对策略
| 难点 | 原因 | 应对方案 |
|---|---|---|
| 无限滚动 | 下拉到底部自动加载更多,没有传统翻页按钮 | 条件循环 + 连续无新增检测 |
| class动态变化 | 小红书前端用随机生成的class名,每次刷新都变 | 避开class,用属性+结构定位 |
| 登录态限制 | 未登录只能看少量内容 | 用手机号登录,Cookie保存在Profile里 |
| 反爬限流 | 请求频率过高会弹出验证 | 随机延迟 + 关键词切换间隔 |
第一步:环境与登录
选型建议: 小红书PC端限制较多,移动端页面(手机或模拟器)更友好。
影刀RPA实操路径: 新建流程 → 浏览器类型选择“移动端(安卓)”。
# ============================================
# 打开小红书移动端并登录

# ============================================
# 1. 打开小红书移动端首页
打开网页("https://www.xiaohongshu.com/", 打开方式="新建标签页")
等待(3秒)
# 2. 检测登录态(用“关注”按钮是否存在来判断)
# 注意:不能用class,用属性或文本
登录标志 = "//*[contains(text(),'关注')]"
如果 判断元素是否存在(登录标志) == False:
输出日志("未登录,开始登录流程")
# 点击"我的"(用文本定位,不用class)
点击元素("//*[contains(text(),'我的')]")
等待(2秒)
# 点击登录按钮
点击元素("//*[contains(text(),'登录')]")
等待(2秒)
# 选择手机号登录
点击元素("//*[contains(text(),'手机号')]")
等待(1秒)
# 输入手机号(用placeholder属性定位)
输入文本("//input[@placeholder='手机号']", "13800138000")
# 点击获取验证码

点击元素("//*[contains(text(),'获取验证码')]")
# 等待验证码短信,手动输入
输出日志("请查看手机验证码,在弹窗中输入")
等待(30秒) # 预留输入时间
# 点击登录确认
点击元素("//*[contains(text(),'登录')]")
等待(3秒)
输出日志("登录完成")
否则:
输出日志("已登录,跳过登录步骤")
结束如果
第二步:搜索关键词与处理无限滚动
核心思路: 搜索后通过“连续滚动的次数”来判断是否已经加载完毕。
# ============================================
# 搜索关键词并滚动加载笔记列表
# ============================================
# 读取关键词列表

读取Excel文件("C:/data/keywords.xlsx", 输出变量=keywords_data)
关键词列表 = keywords_data[1:] # 从第二行开始
设置变量(全部笔记列表 = [])
按列表循环(关键词列表, 循环项=keyword_row):
keyword = keyword_row[0] # 第一列是关键词
输出日志("===== 开始采集关键词:" + keyword + " =====")
# ----- 1. 执行搜索 -----
# 点击搜索框(用placeholder或文本定位)
点击元素("//input[@placeholder='搜索']")
等待(1秒)
# 输入关键词
输入文本("//input[@placeholder='搜索']", keyword)
等待(0.5秒)
# 点击搜索按钮(用文本定位)
点击元素("//*[contains(text(),'搜索')]")
等待(3秒) # 等待搜索结果加载
# ----- 2. 滚动加载更多笔记 -----
# 小红书是无限滚动,没有“下一页”按钮
# 策略:持续滚动直到连续3次滚动都没有新增内容
设置变量(连续无新增 = 0)
设置变量(上一次数量 = 0)
设置变量(当前页笔记数 = 0)
# 用于存储当前关键词的笔记
设置变量(本关键词笔记列表 = [])
条件循环(连续无新增 < 3):
# 获取当前页面的所有笔记卡片
# 注意:用属性定位,不用动态class
获取相似元素列表("//section[contains(@class,'note-item') or contains(@class,'feed-item')]", 存入列表=当前笔记卡片)
当前数量 = 获取列表长度(当前笔记卡片)
输出日志("当前已加载 " + 当前数量 + " 条笔记")
如果 当前数量 > 上一次数量:
# 有新增内容:采集新增的部分
输出日志("检测到新增 " + (当前数量 - 上一次数量) + " 条笔记")
设置变量(连续无新增 = 0)
设置变量(上一次数量 = 当前数量)
# 采集笔记数据(增量采集,只采新增的)
# 用“获取相似元素列表”配合索引范围只取新增部分
获取相似元素列表("//section[contains(@class,'note-item')]", 存入列表=全部卡片)
# 从上次数量开始循环
设置变量(起始索引 = 上次采集数量)
按次数循环(当前数量 - 起始索引):
当前索引 = 起始索引 + 循环索引
Try:
单条笔记 = 采集单条笔记(全部卡片[当前索引])
将 单条笔记 加入列表(本关键词笔记列表)
Catch:
输出日志("第 " + 当前索引 + " 条笔记采集失败,跳过")
继续循环
EndTry
结束循环
设置变量(上次采集数量 = 当前数量)
否则:
# 无新增
设置变量(连续无新增 = 连续无新增 + 1)
输出日志("无新增内容,连续第 " + 连续无新增 + " 次")
结束如果
# 滚动到页面底部(触发加载)
Try:
# 滚动到页面底部最后可见的元素
滚动到元素("//footer") # 或者滚动到页面最后
Catch:
输出日志("滚动到底部失败,尝试用JS滚动")
执行JavaScript("window.scrollTo(0, document.body.scrollHeight);")
EndTry
等待(随机数(2, 4)) # 等待加载
结束条件循环
输出日志("关键词 " + keyword + " 滚动结束,共采集 " + 获取列表长度(本关键词笔记列表) + " 条")
# 将当前关键词的数据合并到总列表
将 本关键词笔记列表 合并到 全部笔记列表
# 切换关键词前重置搜索
点击元素("//*[contains(text(),'取消') or contains(text(),'×')]")
等待(2秒)
# 关键词间休息
等待(随机数(3, 6))
结束循环
输出日志("全部采集完成,共 " + 获取列表长度(全部笔记列表) + " 条")
第三步:采集单条笔记(笔记列表页获取基本信息)
在列表页直接获取笔记的基本信息(标题、作者、互动数据),不需要点进详情页。
# ============================================
# 子流程:采集单条笔记(列表页)
# ============================================
# 输入参数:note_card(笔记卡片元素)
# 输出参数:note_data(笔记数据字典)
Try:
# ----- 1. 采集标题 -----
# 标题通常在一个a标签或span里,用文本定位
Try:
# 方法1:通过链接文本
标题 = 获取元素文本(note_card + "//a[contains(@class,'title')]")
Catch:
# 方法2:通过包含文本的通用定位
标题 = 获取元素文本(note_card + "//*[contains(@class,'title')]")
EndTry
如果 标题 == "" 或 标题 == None:
标题 = "未获取到标题"
EndIf
# ----- 2. 采集作者名 -----
Try:
作者名 = 获取元素文本(note_card + "//span[contains(@class,'author')]")
Catch:
作者名 = "未知作者"
EndTry
# ----- 3. 采集点赞数 -----
Try:
# 点赞数通常在带❤️图标的span里
点赞数文本 = 获取元素文本(note_card + "//span[contains(@class,'like')]")
点赞数 = 提取数字(点赞数文本) # "1.2万" → 12000
Catch:
点赞数 = 0
EndTry
# ----- 4. 采集收藏数 -----
Try:
收藏数文本 = 获取元素文本(note_card + "//span[contains(@class,'collect')]")
收藏数 = 提取数字(收藏数文本)
Catch:
收藏数 = 0
EndTry
# ----- 5. 采集笔记链接(用于后续评论采集)-----
Try:
笔记链接 = 获取元素属性(note_card + "//a[contains(@class,'title')]", "href")
# 拼接完整链接
如果 笔记链接.startswith("/"):
笔记链接 = "https://www.xiaohongshu.com" + 笔记链接
结束如果
Catch:
笔记链接 = ""
EndTry
# ----- 6. 组装数据 -----
笔记数据 = {
"标题": 标题,
"作者": 作者名,
"点赞数": 点赞数,
"收藏数": 收藏数,
"链接": 笔记链接,
"采集时间": 获取当前时间()
}
返回结果(note_data=笔记数据)
Catch:
输出日志("单条笔记采集失败")
返回结果(note_data={})
EndTry
第四步:采集评论(进入笔记详情页)
只采集互动量高的笔记的评论(点赞数>100),避免无效数据。
# ============================================
# 子流程:采集笔记评论
# ============================================
# 输入参数:note_url(笔记链接), max_comments(最大评论数)
# 输出参数:comments_list(评论列表)
# 1. 打开笔记详情页
打开网页(note_url, 打开方式="新建标签页")
等待(3秒)
# 2. 滚动加载评论(小红书评论也是无限滚动)
设置变量(评论列表 = [])
设置变量(连续无新增 = 0)
设置变量(上次评论数 = 0)
条件循环(连续无新增 < 3 且 获取列表长度(评论列表) < max_comments):
# 获取当前所有评论
获取相似元素列表("//div[contains(@class,'comment-item')]", 存入列表=当前评论列表)
当前数量 = 获取列表长度(当前评论列表)
如果 当前数量 > 上次评论数:
设置变量(连续无新增 = 0)
设置变量(上次评论数 = 当前数量)
# 只采集新增的评论(从上次采集的位置开始)
设置变量(起始索引 = 获取列表长度(评论列表))
按次数循环(当前数量 - 起始索引):
当前索引 = 起始索引 + 循环索引
Try:
item = 当前评论列表[当前索引]
# 采集评论内容
评论内容 = 获取元素文本(item + "//span[contains(@class,'content')]")
# 采集评论者
评论者 = 获取元素文本(item + "//span[contains(@class,'name')]")
# 采集评论点赞数
Try:
评论点赞 = 提取数字(获取元素文本(item + "//span[contains(@class,'like')]"))
Catch:
评论点赞 = 0
EndTry
一行评论 = [评论内容, 评论者, 评论点赞, 获取当前时间()]
将 一行评论 加入列表(评论列表)
Catch:
输出日志("单条评论采集失败")
继续循环
EndTry
结束循环
否则:
设置变量(连续无新增 = 连续无新增 + 1)
结束如果
# 滚动加载更多评论
滚动到元素("//div[contains(@class,'comment-list')]//div[last()]")
等待(随机数(1.5, 3))
结束条件循环
输出日志("采集到 " + 获取列表长度(评论列表) + " 条评论")
关闭标签页()
返回结果(comments_list=评论列表)
第五步:数据处理与清洗
# Python清洗:去重、字数统计、情感标记
import pandas as pd
import re
# 输入:raw_notes(笔记列表), raw_comments(评论列表)
# 1. 笔记去重(按标题)
notes_df = pd.DataFrame(raw_notes)
notes_df = notes_df.drop_duplicates(subset=['标题'], keep='first')
# 2. 评论去重
comments_df = pd.DataFrame(raw_comments)
comments_df = comments_df.drop_duplicates(subset=['评论内容'], keep='first')
# 3. 评论长度分析
comments_df['评论长度'] = comments_df['评论内容'].str.len()
# 4. 简单情感标记(包含特定关键词)
好评词 = ['好用', '推荐', '满意', '不错', '值得']
差评词 = ['不好', '差', '失望', '垃圾', '后悔']
def 情感分析(text):
if pd.isna(text):
return '中性'
text = str(text)
if any(word in text for word in 好评词):
return '正面'
if any(word in text for word in 差评词):
return '负面'
return '中性'
comments_df['情感'] = comments_df['评论内容'].apply(情感分析)
# 5. 输出
cleaned_notes = notes_df.to_dict('records')
cleaned_comments = comments_df.to_dict('records')
限流防护策略
| 策略 | 具体做法 |
|---|---|
| 随机延迟 | 每次操作后等待0.5-3秒随机时间 |
| 关键词切换间隔 | 每个关键词采集完等待5-10秒 |
| 评论采集限制 | 每篇笔记最多采50条评论 |
| 总采集量控制 | 单次流程不超过500篇笔记 |
| 登录态保持 | Cookie保存在Profile中,减少重复登录 |
常见问题速查
| 问题 | 原因 | 解决方法 |
|---|---|---|
| 滚动加载不到新内容 | 已加载全部或网络慢 | 增加单次等待时间到3-5秒 |
| class定位不到元素 | class动态生成 | 改用文本/属性定位 |
| 验证码频繁弹出 | 请求频率过高 | 增加延迟,切换关键词间隔拉长 |
| 登录态失效 | Cookie过期 | 重新登录,或提前用手机扫码 |
| 笔记链接采集不到 | 链接是相对路径 | 拼完整域名 |
推荐资源
- 小红书移动端页面——用Chrome开发者工具的移动端模拟模式调试
- 影刀官方《小红书采集模板》——流程市场可以下载参考
- 我的小红书XPath库:维护了一份常用定位表达式(全部用文本和属性,不用class)
#影刀RPA #RPA自动化 #小红书 #内容采集 #数据采集
作者:林焱
本文为《影刀RPA学习手册》系列文章之一,内容源于实操经验的整理与分享。
