三
三股水
三
三股水

Obsidian 小红书同步插件(Xiaohongshu Sync)二次开发完整教程

本文从 Obsidian 撰写发布

Obsidian 小红书同步插件(Xiaohongshu Sync)二次开发完整教程

基于开源插件 bnchiang96/xiaohongshu-importer 二次开发,打造一个功能完整、全中文化、支持手机短链、自动排版、属性丰富的小红书笔记导入插件。

目录

  1. 项目背景与目标
  2. 准备工作
  3. 获取源码
  4. 项目结构说明
  5. 核心改造点
  6. 完整开发流程
  7. 构建与安装
  8. 功能清单
  9. 进阶扩展方向
  10. 常见问题

一、项目背景与目标

原版插件的问题

原版 Xiaohongshu Importer 虽然能用,但存在以下不足:

问题影响
只支持 http://xhslink.com 短链手机 App 分享的 https://xhslink.cn 链接无法识别
界面部分英文中文用户体验不友好
文件存储路径固定无法自定义文件夹结构
无作者、发布时间等属性不利于后续数据检索
排版粗糙下载后是一堆文字,标签、图片堆在一起
标签在正文末尾无法用 Dataview 按标签检索
图片文件名带时间戳冗长且无意义

二次开发目标

  1. 改名:插件 ID 改为 xiaohongshu-sync,名称改为 Xiaohongshu Sync
  2. 全中文化:设置面板、弹窗、通知、报错全部中文
  3. 适配手机短链:兼容 xhslink.com 和 xhslink.cn
  4. 智能存储:有附件建同名文件夹,无附件直接建文件
  5. 日期前缀:文件夹和文件名加下载日期
  6. 丰富属性:新增作者、作者主页、发布时间
  7. 标签入属性:标签作为 frontmatter 最后一项,YAML 列表格式
  8. 基础排版:段落整理、图片独立区块、去掉冗余空行
  9. 快速新增分类:导入弹窗内即可新增分类,持久化到设置

二、准备工作

环境要求

工具版本说明
Node.js18.x 或更高用于安装依赖和构建
npm随 Node.js 安装包管理器
Obsidian0.15.0 或更高测试插件
Git(可选)最新版克隆仓库
代码编辑器VS Code 推荐修改源码

检查环境

node -v
npm -v

清理原项目文件

克隆或下载原仓库后,保留以下 6 个文件即可,其余可删除:

xiaohongshu-sync/
├── main.ts              ← 核心源码
├── manifest.json        ← 插件身份
├── package.json         ← 依赖和构建脚本
├── esbuild.config.mjs   ← 构建配置
├── tsconfig.json        ← TS 编译配置
└── styles.css           ← 样式

可删除的文件:

  • .editorconfig、.eslintignore、.eslintrc、.gitignore
  • .npmrc、LICENSE(自己用可删)
  • README.md、version-bump.mjs、versions.json

三、获取源码

原版仓库地址

注意:Plus 版只提供编译后的 main.js,没有 .ts 源码,属于源码不公开。二次开发请使用普通版。

克隆到本地

git clone https://github.com/bnchiang96/xiaohongshu-importer.git xiaohongshu-sync
cd xiaohongshu-sync

四、项目结构说明

核心文件职责

文件职责
main.ts插件全部逻辑:导入、解析、存储、设置面板、弹窗
manifest.json插件元信息:ID、名称、版本、最低 Obsidian 版本
package.json依赖列表、构建脚本、插件元信息
esbuild.config.mjs构建配置:入口、输出、外部依赖、banner
tsconfig.jsonTypeScript 编译选项
styles.css弹窗和分类 chip 的样式

关键类结构(改造后)

XHSSyncPlugin              ← 主插件类
├── onload()               ← 注册 ribbon、命令、设置面板
├── extractURL()           ← 提取小红书链接
├── importXHSNote()        ← 主导入流程
├── extractTitle()         ← 提取标题
├── extractImages()        ← 提取图片列表
├── extractVideoUrl()      ← 提取视频地址
├── extractContent()       ← 提取正文
├── isVideoNote()          ← 判断是否为视频笔记
├── extractAuthor()        ← 提取作者信息(新增)
├── extractPublishTime()   ← 提取发布时间(新增)
├── extractTags()          ← 提取标签
├── formatContent()        ← 正文排版(新增)
└── formatDateTime()       ← 时间格式化(新增)

XHSSyncSettingTab          ← 设置面板
XHSInputModal              ← 导入弹窗

五、核心改造点

1. 插件改名

manifest.json:

{
    "id": "xiaohongshu-sync",
    "name": "Xiaohongshu Sync",
    "version": "1.0.0",
    "minAppVersion": "0.15.0",
    "description": "导入小红书笔记,支持媒体下载、分类、作者信息和自动排版。",
    "author": "你的名字",
    "authorUrl": "https://github.com/你的用户名",
    "isDesktopOnly": false
}

package.json:

{
    "name": "xiaohongshu-sync",
    "version": "1.0.0",
    "description": "Import Xiaohongshu notes into Obsidian.",
    ...
}

esbuild.config.mjs banner:

const banner =
    `/*
 * Xiaohongshu Sync plugin
 * Based on Xiaohongshu Importer by bnchiang96 (MIT)
 * Version: 1.0.0
 */
`;

main.ts 类名和引用:

  • XHSImporterPlugin 改为 XHSSyncPlugin
  • XHSImporterSettings 改为 XHSSyncSettings
  • XHSImporterSettingTab 改为 XHSSyncSettingTab

2. 适配手机短链

原版正则只匹配 http://xhslink.com/a?o?/,改为:

extractURL(shareText: string): string | null {
    const mobileUrlMatch = shareText.match(/https?:\/\/xhslink\.(?:com|cn)\/[a-zA-Z0-9/?=&._-]+/);
    if (mobileUrlMatch) {
        return mobileUrlMatch[0];
    }

    const webUrlMatch = shareText.match(/https?:\/\/www\.xiaohongshu\.com\/(?:discovery\/item|explore)\/[a-zA-Z0-9]+(?:\?[^\s,,]*)?/);
    if (webUrlMatch) {
        return webUrlMatch[0].replace('/explore/', '/discovery/item/');
    }

    return null;
}

兼容的链接格式:

3. 智能存储逻辑

规则:

情况存储路径
有图片/视频默认文件夹/分类/日期_标题/日期_标题.md + 图片同文件夹
无图片/视频默认文件夹/分类/日期_标题.md

核心代码:

const hasMedia = (images.length > 0 || (isVideo && videoUrl)) && downloadMedia;

if (hasMedia) {
    const noteFolder = `${folderPath}/${fileBaseName}`;
    if (!await this.app.vault.adapter.exists(noteFolder)) {
        await this.app.vault.createFolder(noteFolder);
    }
    notePath = `${noteFolder}/${fileBaseName}.md`;
    mediaFolder = noteFolder;
} else {
    if (!await this.app.vault.adapter.exists(folderPath)) {
        await this.app.vault.createFolder(folderPath);
    }
    notePath = `${folderPath}/${fileBaseName}.md`;
    mediaFolder = folderPath;
}

4. 日期前缀

文件夹和文件名统一用 日期_标题 格式:

const datePrefix = noteDate;  // 2026-09-16
const fileBaseName = `${datePrefix}_${safeTitle}`;

5. 图片和视频文件名简化

改前:

标题-0-1758000000000.jpg
标题-1-1758000000001.jpg
标题-1758000000000.mp4

改后:

1.jpg
2.jpg
video.mp4

改动点:

// 图文笔记
const imageFilename = `${i + 1}.jpg`;

// 视频笔记封面
const imageFilename = `1.jpg`;

// 视频
const videoFilename = `video.mp4`;

6. 新增作者和发布时间属性

新增两个方法:

extractAuthor(html: string): { nickname: string; profileUrl: string } {
    const stateMatch = html.match(/window\.__INITIAL_STATE__=(.*?)<\/script>/s);
    if (!stateMatch) return { nickname: "", profileUrl: "" };

    try {
        const jsonStr = stateMatch[1].trim();
        const cleanedJson = jsonStr.replace(/undefined/g, "null");
        const state = JSON.parse(cleanedJson);
        const noteId = Object.keys(state.note.noteDetailMap)[0];
        const note = state.note.noteDetailMap[noteId].note;
        const user = note.user || {};

        const nickname = user.nickname || user.nickName || "";
        const userId = user.userId || user.user_id || "";
        const profileUrl = userId ? `https://www.xiaohongshu.com/user/profile/${userId}` : "";

        return { nickname, profileUrl };
    } catch (e) {
        console.log(`解析作者信息失败:${e.message}`);
        return { nickname: "", profileUrl: "" };
    }
}

extractPublishTime(html: string): string {
    const stateMatch = html.match(/window\.__INITIAL_STATE__=(.*?)<\/script>/s);
    if (!stateMatch) return "";

    try {
        const jsonStr = stateMatch[1].trim();
        const cleanedJson = jsonStr.replace(/undefined/g, "null");
        const state = JSON.parse(cleanedJson);
        const noteId = Object.keys(state.note.noteDetailMap)[0];
        const note = state.note.noteDetailMap[noteId].note;
        const rawTime = note.time || note.publishTime || "";

        if (!rawTime) return "";

        const date = new Date(Number(rawTime));
        if (isNaN(date.getTime())) return "";

        const y = date.getFullYear();
        const m = String(date.getMonth() + 1).padStart(2, "0");
        const d = String(date.getDate()).padStart(2, "0");
        return `${y}-${m}-${d}`;
    } catch (e) {
        console.log(`解析发布时间失败:${e.message}`);
        return "";
    }
}

frontmatter 增加:

作者: 林间自然
作者主页: https://www.xiaohongshu.com/user/profile/xxxxx
发布时间: 2026-09-10

7. 标签移入属性

改前:标签在正文末尾,代码块形式

改后:标签作为 frontmatter 最后一项,YAML 列表

let frontmatterTags = "tags: []\n";
if (tags.length > 0) {
    frontmatterTags = "tags:\n" + tags.map((tag) => `  - ${tag}`).join("\n") + "\n";
}

标签提取正则修正(避免把标点符号也抓进来):

extractTags(content: string): string[] {
    const tagMatches = content.match(/#[\w\u4e00-\u9fa5-]+/g) || [];
    return tagMatches.map((tag) => tag.replace("#", "").trim());
}

8. 正文排版

新增 formatContent 方法:

formatContent(rawContent: string): string {
    let text = rawContent;
    text = text.replace(/#[^#\s]*(?:\s+#[^#\s]*)*\s*/g, "");
    const lines = text.split("\n").map((line) => line.trim());
    const paragraphs: string[] = [];
    let buffer: string[] = [];

    for (const line of lines) {
        if (line === "") {
            if (buffer.length > 0) {
                paragraphs.push(buffer.join(" "));
                buffer = [];
            }
        } else {
            buffer.push(line);
        }
    }
    if (buffer.length > 0) {
        paragraphs.push(buffer.join(" "));
    }

    return paragraphs.join("\n\n");
}

排版效果:

  • 去掉标签和话题标记
  • 段落之间空一行
  • 图片独立成 ## 图片 区块,每张图前加 图 N

9. 快速新增分类

在导入弹窗的分类 chip 下方加一行输入框和按钮:

const addCategoryRow = contentEl.createEl("div", { cls: "xhs-add-category-row" });
const newCategoryInput = addCategoryRow.createEl("input", {
    cls: "xhs-add-category-input",
    attr: { type: "text", placeholder: "新增分类名称" },
});
const addCategoryButton = addCategoryRow.createEl("button", {
    text: "新增分类",
    cls: "xhs-add-category-button",
});

const handleAddCategory = async () => {
    const name = newCategoryInput.value.trim();
    if (!name) {
        new Notice("请输入分类名称。");
        return;
    }
    if (name === "其他") {
        new Notice("“其他”是保留分类,请换一个名称。");
        return;
    }
    if (this.plugin.settings.categories.includes(name)) {
        new Notice("该分类已存在。");
        return;
    }

    this.plugin.settings.categories.push(name);
    await this.plugin.saveSettings();

    this.selectedCategory = name;
    newCategoryInput.value = "";
    renderChips();

    new Notice(`已新增分类:${name}`);
};

10. 全中文化

设置面板:

  • Default folder 改为 默认存储文件夹
  • Download media 改为 默认下载媒体
  • Categories 改为 分类管理
  • Add category 改为 添加分类
  • Remove 改为 删除
  • Move up 改为 上移
  • Move down 改为 下移

弹窗:

  • Import Xiaohongshu note 改为 导入小红书笔记
  • Paste the share text below: 改为 粘贴分享文本:
  • Select a category: 改为 选择分类:
  • Download media locally for this import 改为 本次导入下载媒体到本地
  • Import 改为 导入

通知和报错:

  • No valid Xiaohongshu URL found in the text. 改为 未在文本中找到有效的小红书链接。
  • Failed to download media: 改为 下载媒体失败:
  • Imported Xiaohongshu note as 改为 已导入小红书笔记:
  • Failed to import note: 改为 导入笔记失败:

11. 删除冗余字段

原版 frontmatter 里 date 和 导入时间 重复,删除 date,只保留 导入时间,格式统一为 YYYY-MM-DD HH:mm:ss。

formatDateTime(date: Date): string {
    const y = date.getFullYear();
    const m = String(date.getMonth() + 1).padStart(2, "0");
    const d = String(date.getDate()).padStart(2, "0");
    const hh = String(date.getHours()).padStart(2, "0");
    const mm = String(date.getMinutes()).padStart(2, "0");
    const ss = String(date.getSeconds()).padStart(2, "0");
    return `${y}-${m}-${d} ${hh}:${mm}:${ss}`;
}

六、完整开发流程

步骤 1:安装依赖

npm install

步骤 2:开发模式构建

npm run dev

开发模式会监听 main.ts 变化,自动重新编译。

步骤 3:生产模式构建

npm run build

生产模式会压缩代码,生成 main.js。

步骤 4:复制到插件目录

把以下三个文件复制到 Obsidian 库的插件目录:

main.js
manifest.json
styles.css

目标路径:

<你的库>/.obsidian/plugins/xiaohongshu-sync/

步骤 5:启用插件

  1. 重启 Obsidian
  2. 进入 设置 - 第三方插件
  3. 找到 Xiaohongshu Sync,打开开关

步骤 6:测试导入

  1. 点击左侧边栏的书本图标,或按 Ctrl/Cmd + P 搜索“导入小红书笔记”
  2. 粘贴小红书分享文本
  3. 选择分类(可现场新增)
  4. 勾选是否下载媒体
  5. 点击“导入”

七、构建与安装

完整命令序列

# 1. 进入项目目录
cd xiaohongshu-sync

# 2. 安装依赖
npm install

# 3. 生产构建
npm run build

# 4. 复制到插件目录(手动操作)
# 把 main.js、manifest.json、styles.css 复制到
# <你的库>/.obsidian/plugins/xiaohongshu-sync/

最终插件目录结构

<你的库>/.obsidian/plugins/xiaohongshu-sync/
├── main.js          ← 编译产物
├── manifest.json    ← 插件元信息
└── styles.css       ← 样式

导入后的文件结构

xhs/
└── 美食/
    └── 2026-09-16_某篇笔记/
        ├── 2026-09-16_某篇笔记.md
        ├── 1.jpg
        ├── 2.jpg
        └── 3.jpg

无附件的笔记:

xhs/
└── 知识/
    └── 2026-09-15_无图笔记.md

八、功能清单

已实现功能

功能状态说明
手机短链适配已完成兼容 xhslink.com 和 xhslink.cn
网页链接适配已完成兼容 discovery/item 和 explore
全中文化已完成设置、弹窗、通知、报错
智能存储已完成有附件建文件夹,无附件直接建文件
日期前缀已完成文件夹和文件名加 YYYY-MM-DD_
图片序号已完成1.jpg、2.jpg 从 1 开始
视频处理已完成下载为 video.mp4
作者昵称已完成frontmatter 作者 字段
作者主页已完成frontmatter 作者主页 字段
发布时间已完成frontmatter 发布时间 字段
标签入属性已完成YAML 列表格式,最后一项
正文排版已完成段落整理,图片独立区块
快速新增分类已完成弹窗内即时新增,持久化
分类管理已完成设置面板可增删改、排序

frontmatter 最终格式

---
title: 想做自然教育,第一场活动怎么落地?
作者: 林间自然
作者主页: https://www.xiaohongshu.com/user/profile/xxxxx
发布时间: 2026-09-10
source: https://xhslink.cn/o/6oa52Edo8CC
导入时间: 2026-09-16 14:30:00
分类: 知识
tags:
  - 自然教育创业
  - 自然教育
  - 林间自然
  - 自然体验活动
  - 自然探索之旅
  - 户外自然教育
---

Dataview 检索示例

TABLE 作者, 发布时间, 分类
FROM #自然教育
SORT 发布时间 DESC

TABLE 作者, 发布时间
FROM "xhs"
WHERE 分类 = "知识"
SORT 导入时间 DESC

九、进阶扩展方向

1. 互动数据入属性

小红书页面的 INITIAL_STATE 里包含点赞、收藏、评论数,可以提取后写入 frontmatter:

点赞: 1234
收藏: 567
评论: 89

配合 Dataview 可以按热度排序筛选。

2. URI Protocol Handler

注册 obsidian://xiaohongshu-sync 协议,手机端配合 iOS 快捷指令或 Android Tasker,在小红书 App 里分享后直接跳转导入。

this.registerObsidianProtocolHandler("xiaohongshu-sync", async (params) => {
    const url = params.url;
    // 触发导入
});

3. 图片横排显示

配合 Image Grid 插件,把图片区块输出改成:

![[1.jpg]]
![[2.jpg]]
![[3.jpg]]

4. 图片轮播切换

配合 Simple Image Slider 插件,输出:

![[1.jpg]]
![[2.jpg]]
![[3.jpg]]

5. 批量导入收藏夹

扩展插件,支持读取用户收藏夹列表,批量导入多篇笔记。

6. AI 自动分类

接入 AI 模型,根据笔记内容自动推荐分类,减少手动选择。

7. 发布到七牛云

配合七牛云插件,增加“上传当前笔记所有本地图片并替换链接”命令,方便发布到网站。


十、常见问题

Q1:构建时报错 Cannot find module 'obsidian'

原因:依赖未安装。

解决:

npm install

Q2:npm install 时出现 allow-scripts 警告

原因:npm 的安全提示,esbuild 的 postinstall 脚本未自动执行。

解决:不影响构建,忽略即可。如果构建报错找不到 esbuild,执行:

npm approve-scripts esbuild

Q3:插件启用后没反应

排查:

  1. 确认 main.js、manifest.json、styles.css 三个文件都在同一个文件夹
  2. 确认文件夹名和 manifest.json 里的 id 一致(xiaohongshu-sync)
  3. 重启 Obsidian
  4. 在 设置 - 第三方插件 里确认插件已启用

Q4:导入时提示“未在文本中找到有效的小红书链接”

排查:

  1. 确认链接格式是 xhslink.com、xhslink.cn 或 xiaohongshu.com
  2. 确认链接是公开笔记,不是私密或已删除
  3. 检查分享文本里是否包含完整 URL

Q5:图片下载失败

排查:

  1. 确认“下载媒体”选项已勾选
  2. 检查网络连接
  3. 确认图片 URL 可访问
  4. 部分图片可能有防盗链,需要检查 Referer

Q6:标签在 Obsidian 里显示被划线

原因:标签名包含 Obsidian 不支持的字符(如标点符号)。

解决:已修正 extractTags 正则,只匹配字母、数字、下划线、连字符、正斜杠和中文:

const tagMatches = content.match(/#[\w\u4e00-\u9fa5-]+/g) || [];

Q7:Windows 下看不到 .obsidian 文件夹

解决:在资源管理器里勾选 查看 - 显示 - 隐藏的项目。

Q8:构建产物 main.js 在哪里

答:在项目根目录,和 main.ts 同级。npm run build 后会生成或覆盖。

Q9:能否同时保留原版插件和二次开发版

答:可以,只要 manifest.json 里的 id 不同即可。原版是 xiaohongshu-importer,二次开发版是 xiaohongshu-sync,两者互不冲突。

Q10:MIT 许可证要求

答:原版采用 MIT 许可证,允许修改和分发。如果公开发布二次开发版,建议在 README 里注明:

Based on Xiaohongshu Importer by bnchiang96 (MIT License)
https://github.com/bnchiang96/xiaohongshu-importer

自己用则没有这个约束。


附录:关键代码文件

manifest.json 最终版

{
    "id": "xiaohongshu-sync",
    "name": "Xiaohongshu Sync",
    "version": "1.0.0",
    "minAppVersion": "0.15.0",
    "description": "导入小红书笔记,支持媒体下载、分类、作者信息和自动排版。",
    "author": "你的名字",
    "authorUrl": "https://github.com/你的用户名",
    "isDesktopOnly": false
}

styles.css 关键样式

/* 弹窗内容容器 */
.xhs-modal-content {
    display: flex;
    flex-direction: column;
    gap: 10px;
    padding: 10px;
}

/* 分类 chip */
.xhs-chip {
    padding: 4px 8px;
    border-radius: 12px;
    border: 1px solid #ccc;
    background-color: #f0f0f0;
    color: #000;
    cursor: pointer;
    transition: background-color 0.2s;
}

.xhs-chip.xhs-chip--selected {
    background-color: #FF2442;
    color: #fff;
}

/* 新增分类行 */
.xhs-add-category-row {
    display: flex;
    gap: 8px;
    align-items: center;
    margin-top: 4px;
}

/* 去掉设置页面分类管理标题的左边距 */
.xhs-setting-heading {
    margin-left: 0;
    padding-left: 0;
}

总结

通过这次二次开发,我们把一个基础的小红书导入插件,改造成了一个功能完整、体验友好、便于检索的知识管理工具。核心思路是:

  1. 兼容性优先:适配手机短链,覆盖更多用户场景
  2. 中文化:降低中文用户的使用门槛
  3. 结构化存储:日期前缀 + 智能文件夹,文件管理更清晰
  4. 元数据丰富:作者、发布时间、标签入属性,方便 Dataview 检索
  5. 排版优化:正文段落整理,图片独立区块,阅读体验更好
  6. 交互便捷:弹窗内快速新增分类,减少设置切换

这套流程不仅适用于小红书插件,也可以作为其他 Obsidian 插件二次开发的参考模板。

上一篇 Obsidian小红书内容下载、发布、优化插件合集 下一篇 Obsidian 发布插件开发教程:对接 Discuz X5 RESTful API

暂无评论

Ctrl + Enter 发送

还没有评论,来说点什么吧

© 2026 三股水