Joplin教程
🤖 摘要:本文详解如何利用Joplin本地REST API实现笔记自动化管理。涵盖接口配置,提供Python与Node.js代码,演示批量标签更新、数据注入及资源挂载。附带避坑指南、工程规范与集成方案,助力构建高效稳定的笔记处理工作流。
📌 本篇目标:打通 Joplin Local REST API 的自动化链路,掌握使用 Python/Node.js 进行笔记批量操作、标签管理、资源挂载及外部数据(CSV/JSON/Webhook)注入的核心方法,提供可落地的工程实践方案。
🛠 一、环境准备与 API 准入
1. 开启 Joplin Local API
- 桌面端:<code>偏好设置</code> → <code>高级</code> → ✅ <code>启用本地 API</code>(默认端口 <code>41184</code>)
- 验证连通性:
curl http://localhost:41184/api/ping # 返回 {"status":"ok"} 即表示服务正常
2. API Token 说明
Joplin Local API 不强制校验真实 Token,本地调用可使用任意字符串:
Authorization: Bearer dummy_token
⚠️ 若需跨设备或插件授权,可通过 <code>joplin api –list</code> 生成正式 Token。
3. 核心端点映射
| 操作 | HTTP Method | Endpoint | Payload 类型 |
|---|---|---|---|
| 查询笔记列表 | <code>GET</code> | <code>/api/notes</code> | query params |
| 创建笔记 | <code>POST</code> | <code>/api/notes</code> | JSON |
| 更新笔记 | <code>PUT</code> | <code>/api/notes/:id</code> | JSON |
| 删除笔记 | <code>DELETE</code> | <code>/api/notes/:id</code> | – |
| 查询文件夹 | <code>GET</code> | <code>/api/folders</code> | query params |
| 挂载资源 | <code>POST</code> | <code>/api/notes/:id/resources</code> | Multipart/Form-Data |
🐍 二、Python 实战:批量处理与外部注入
1. 依赖安装
pip install requests pandas python-dotenv
2. 批量更新标签与移动目录
import requests
import os
from dotenv import load_dotenv
load_dotenv()
BASE_URL = "http://localhost:41184/api"
TOKEN = os.getenv("JOPLIN_TOKEN", "dummy")
HEADERS = {"Authorization": f"Bearer {TOKEN}", "Content-Type": "application/json"}
def get_all_notes(folder_id=None):
params = {"fields": "id,title,folder_id,labels"}
if folder_id: params["folder"] = folder_id
res = requests.get(f"{BASE_URL}/notes", headers=HEADERS, params=params)
return res.json().get("data", [])
def batch_update_tags(note_ids, target_tag):
for nid in note_ids:
note = requests.get(f"{BASE_URL}/notes/{nid}", headers=HEADERS).json()["data"]
tags = note["labels"] if isinstance(note["labels"], list) else []
if target_tag not in tags:
tags.append(target_tag)
requests.put(f"{BASE_URL}/notes/{nid}", headers=HEADERS, json={"labels": tags})
# 使用示例:将某文件夹下所有笔记追加标签 #auto_sync
notes = get_all_notes(folder_id="your_folder_id")
batch_update_tags([n["id"] for n in notes], "auto_sync")
3. 外部 CSV/JSON 注入笔记
import pandas as pd
def inject_notes_from_csv(csv_path, target_folder_id):
df = pd.read_csv(csv_path)
for _, row in df.iterrows():
payload = {
"title": row["title"],
"body": row["content"], # 支持 Markdown
"folder_id": target_folder_id,
"tags": ["imported", "csv"]
}
res = requests.post(f"{BASE_URL}/notes", headers=HEADERS, json=payload)
if res.status_code != 200:
print(f"❌ 注入失败: {row['title']} -> {res.text}")
inject_notes_from_csv("data/input/notes.csv", "target_folder_id")
🟩 三、Node.js 实战:异步流式处理与自动化
1. 依赖安装
npm init -y
npm install axios p-limit fs-extra
2. 并发控制批量处理(防 API 限流)
const axios = require('axios');
const pLimit = require('p-limit');
const fs = require('fs-extra');
const BASE_URL = 'http://localhost:41184/api';
const TOKEN = process.env.JOPLIN_TOKEN || 'dummy';
const headers = { Authorization: `Bearer ${TOKEN}`, 'Content-Type': 'application/json' };
const limit = pLimit(5); // 并发 5
async function fetchNotes(folderId) {
const { data } = await axios.get(`${BASE_URL}/notes`, {
headers, params: { fields: 'id,title,folder_id', folder: folderId }
});
return data.data;
}
async function updateTags(notes, newTag) {
const tasks = notes.map(note => limit(async () => {
const res = await axios.get(`${BASE_URL}/notes/${note.id}`, { headers });
const tags = Array.isArray(res.data.data.labels) ? res.data.data.labels : [];
if (!tags.includes(newTag)) {
tags.push(newTag);
await axios.put(`${BASE_URL}/notes/${note.id}`, { labels: tags }, { headers });
}
}));
await Promise.all(tasks);
}
// 使用示例
(async () => {
const notes = await fetchNotes('your_folder_id');
await updateTags(notes, 'auto_sync_node');
})();
3. JSON 数据注入 + 资源挂载
async function injectWithResource(jsonPath, folderId) {
const rawData = await fs.readJson(jsonPath);
for (const item of rawData) {
// 1. 创建笔记
const noteRes = await axios.post(`${BASE_URL}/notes`, {
title: item.title, body: item.body, folder_id: folderId
}, { headers });
const noteId = noteRes.data.data.id;
// 2. 挂载图片资源(示例)
if (item.imagePath) {
const fileStream = fs.createReadStream(item.imagePath);
const resourceRes = await axios.post(
`${BASE_URL}/notes/${noteId}/resources`,
fileStream, { headers: { ...headers, 'Content-Type': 'application/octet-stream' } }
);
// 3. 在笔记 body 中插入图片引用(Markdown)
const imgRef = ``;
await axios.put(`${BASE_URL}/notes/${noteId}`, { body: item.body + '\n' + imgRef }, { headers });
}
}
}
injectWithResource('data/input/inject.json', 'target_folder_id');
⚡ 四、进阶技巧与避坑指南
| 场景 | 推荐方案 |
|---|---|
| 大数据量注入 | 改用 Joplin <code>export/import</code> API(<code>/api/exports</code>)或 <code>.jex</code> 格式批量导入 |
| 冲突处理 | 操作前读取 <code>updated_time</code>,PUT 时携带 <code>if-match: <etag></code> 防止覆盖同步冲突 |
| 加密笔记处理 | Local API 无法解密加密笔记内容,需先导出为 <code>.jex</code> 或在客户端内解密后调用 |
| 速率限制 | 本地 API 无严格限流,但建议单批 ≤50 条,加 <code>sleep(0.1)</code> 避免 UI 卡顿 |
| Token 安全 | 生产环境使用环境变量或 <code>.env</code>,勿硬编码;跨网络调用需配合 Joplin Web Clipper |
📦 五、推荐工程结构
joplin-automation/
├── src/
│ ├── python/ # Python 脚本目录
│ └── nodejs/ # Node.js 脚本目录
├── data/
│ ├── input/ # 待注入的 CSV/JSON/PDF
│ └── logs/ # 运行日志
├── config/
│ └── .env # API_TOKEN, TARGET_FOLDER_ID, SYNC_MODE
├── requirements.txt / package.json
└── README.md
🔮 六、后续演进方向
- 插件化封装:将脚本打包为 Joplin Desktop Plugin,实现一键触发
- 工作流集成:通过 Zapier/Make 监听 Notion/Trello/飞书,自动同步至 Joplin
- AI 辅助注入:结合 LLM 生成结构化笔记草稿,经审核后批量写入
- 双向同步守卫:增加 <code>last_synced_at</code> 字段,避免重复注入与循环覆盖