一个纯浏览器端、不依赖任何本地程序的小红书作品下载脚本。 基于开源项目 XHS-Downloader 的源码与原生 userscript 逆向复刻。
…/explore/xxxx)一键下载当前作品的全部图片或视频。…/explore/{noteId}?xsec_token=…)复制到剪贴板。XHS-Downloader-Userscript.user.js 的全部内容粘贴进去,保存(Ctrl+S)。.user.js 文件拖入 Tampermonkey 窗口完成安装。支持的站点:
www.xiaohongshu.com与www.rednote.com(红笔记国际版)。
面板各控件说明:
| 控件 | 作用 |
|---|---|
| 下载本篇 | 下载当前详情页作品(图文下全部图片 / 视频下视频文件)。 |
| 下载本页全部 | 下载本页所有可见作品。勾选「自动滚动加载」后会先向下滚动把内容加载出来再下载。 |
| 图片格式 | JPEG / WEBP / PNG / HEIC / AVIF,决定图片文件的保存格式。 |
| 视频偏好 | 分辨率优先 / 码率优先 / 体积优先,决定视频取哪一档清晰度。 |
| 自动滚动加载 | 下载本页全部前,自动滚动把懒加载的内容触发出来。 |
| 滚动次数 | 自动滚动的轮数(页面越长填越大,默认 30)。 |
| 复制本页链接 | 复制本页所有作品的分享链接,方便转发或二次处理。 |
小红书把所有作品数据都挂在页面的全局变量 window.__INITIAL_STATE__ 上。脚本直接读取它,而不是去爬 DOM 或请求接口,因此不需要 Cookie、不需要额外请求。
不同页面的数据路径:
| 页面类型 | 数据位置 |
|---|---|
| 作品详情页 | state.note.noteDetailMap[id].note 或 state.noteData.data.noteData |
| 首页 / 频道 | state.feed.feeds._rawValue[*].noteCard |
| 搜索结果 | state.search.feeds._rawValue[*].noteCard |
| 用户主页 | state.user.notes._rawValue[*].noteCard |
| 合集 / 专辑 | state.board.boardFeedsMap._rawValue[...].notes[*] |
每个笔记对象自带 noteId、type(normal / video)、imageList、video、title、user 等字段。
关键设计:取「当前作品」时,脚本先用 URL 里的
noteId去note.noteDetailMap[id].note精确匹配(见下方「已修复问题」),避免误取信息流卡片。
从 note.imageList[*] 里取每张图的地址(优先 urlDefault,其次 url,再其次 urlPre),抠出其中的资源 token,拼成可直连下载的地址。
为最大化成功率,每张图会生成三个候选地址,按顺序尝试:
https://ci.xiaohongshu.com/{token}?imageView2/format/{fmt} —— 格式可控的原图(与官方 XHS-Downloader 的 image.py 逻辑一致)。https://sns-img-bd.xhscdn.com/{token} —— 原项目 image.py 中的 "auto" 直链。urlDefault 去掉 ! 变换后缀得到的未处理原图(同域,最稳,作为兜底)。note.video.consumer.originVideoKey → https://sns-video-bd.xhscdn.com/{key}。note.video.media.stream 各清晰度档位中,按你选择的偏好(分辨率 / 码率 / 体积)取最优档的 backupUrls[0] 或 masterUrl。小红书的 CDN(xhscdn、ci.xiaohongshu.com)允许跨域 fetch,所以脚本:
fetch(直链) → 读取为 Blob → URL.createObjectURL → 生成 <a download> 触发浏览器保存
全程在浏览器内闭环,文件直接落盘到浏览器默认下载目录。
{标题}_{作者}_{作品ID}_{图片序号}.{格式} # 图文,如 春日穿搭_小红薯abc_65a1b2_1.jpeg
{标题}_{作者}_{作品ID}.mp4 # 视频
标题 / 作者会做安全化处理(去掉 \ / : * ? " < > | 等非法字符,并截断到 60 字以内)。
早期版本存在「明明好几张图却只下到一张」的问题,根因与修复如下:
根因 1 —— 取错了笔记对象
旧逻辑直接取页面全部笔记的「最后一个」,在详情页数据尚未加载完(__INITIAL_STATE__ 水合未完成)时,容易拿到信息流卡片,而卡片的 imageList 往往只有 1 张封面图。
修复:currentNote() 改为
noteId 精确匹配 note.noteDetailMap[id].note;noteData.data.noteData 或 noteDetailMap 里的笔记。根因 2 —— 直链只有单一来源
旧版图片直链只靠 ci.xiaohongshu.com 一条路,某张图 token 解析异常就整张失败。
修复:imageCandidates() 为每张图生成 3 个候选地址依次尝试;downloadFile() 支持候选数组、加了图片 accept 请求头、并校验空文件;downloadNote() 逐张显示进度与成功数(如「图片下载完成:5/5 张」)。
设置通过油猴的 GM_setValue / GM_getValue 持久化,键名与默认值:
| 键名 | 含义 | 默认 |
|---|---|---|
xhs_image_fmt |
图片保存格式 | jpeg |
xhs_video_pref |
视频清晰度偏好 | resolution |
xhs_auto_scroll |
下载本页全部前是否自动滚动 | false |
xhs_scroll_times |
自动滚动轮数 | 30 |
脚本为单一 IIFE,无外部依赖,主要分区:
$ / $$(选择器)、sleep、getState(读 __INITIAL_STATE__)、safeGet / safeArr(安全取值,避免路径中间为 null 报错)。collectNotes()(本页全部笔记,用于「下载本页全部 / 复制链接」)、extractCurrentNoteId() + getDetailNote() + currentNote()(精确且带重试地取当前详情页作品)。imageCandidates()(图片多候选)、videoUrl()(视频最优档)。downloadFile()(fetch + Blob + 候选兜底 + 重试)、triggerDownload()(<a download> 落盘)、downloadNote()(按类型分发、逐张进度)。autoScroll()(触发懒加载)。buildUI()(面板与控件)、toast()(提示)、makeDraggable()(拖拽)。init() 在 document-idle 后注入面板。imageList / video 字段可能缺失,导致下载不全。__INITIAL_STATE__,需勾选「自动滚动加载」先把内容滚出来。| 现象 | 可能原因 / 处理 |
|---|---|
| 面板不出现 | 确认已登录小红书;确认 Tampermonkey 已启用该脚本;刷新页面。 |
| 提示「未识别到当前作品」 | 详情页数据未加载完,刷新页面后重试;或确认当前确为 /explore/ 详情页。 |
| 只下到部分图片(如 3/5 张) | 个别 CDN 限流,稍等后重下;或切换图片格式重试。 |
| 视频下载失败 | 该作品视频字段结构特殊,可重试;确认登录态有效。 |
| 大量文件被浏览器拦截 | 在浏览器下载确认框点「允许本站点多次下载」。 |
| 文件名乱码 | 标题含非常用字符,已做安全化处理;不影响文件内容。 |
文档对应脚本版本:v2.0.0