你每天刷到几十条 AI 资讯、技术文章、行业分析,随手转发到微信群或收藏夹,然后呢?三天后你连标题都想不起来。如果每一条你分享的链接,能自动被抓取正文、AI 生成摘要和标签、归档到你的 Obsidian 知识库 — 而且整个过程跑在你自己的电脑上,不花一分钱,你会不会想试试?这篇教程把我实际在用的这套管道从头拆给你看。
为什么要本地 AI 管道
在开始动手之前,先说清楚为什么我选了这条路,而不是直接用 Notion Web Clipper 或 Readwise 之类的现成工具。
- 隐私 — 你的阅读历史是你的画像。我不想让任何第三方知道我每天在看什么。本地管道意味着数据从头到尾不出你的电脑。
- 成本 — Ollama 免费,n8n 自托管免费,Obsidian 本地使用免费。每月运行成本:0 元。对比 Readwise 每月 $8.99 或 Notion AI 每月 $10。
- 控制 — 你拥有模型、数据、格式的完全控制权。想换模型?改一个字段。想改分类体系?改一行 prompt。想换存储格式?改一个模板。没有任何厂商锁定。
- 可扩展 — 今天是知识归档,明天可以接日报生成、周刊汇总、主题聚类。n8n 是积木式架构,加节点就能扩展功能。
架构总览
先看全景图,搞清楚数据怎么从你的手机跑到 Obsidian 里:
微信 / Telegram
|
v
n8n Webhook (POST /knowledge-inbox)
|
v
Normalize Input -- 提取 URL、备注、来源
|
v
Fetch Content -- Jina Reader 优先,fallback 到直接 HTTP
| (小红书走特殊解析)
v
Prepare Prompt -- 组装 Ollama prompt
|
v
Ollama qwen3:8b -- 本地 LLM 生成摘要 + 标签 + 分类
|
v
Build Markdown -- YAML frontmatter + 正文
|
v
Write to Obsidian Vault/Inbox/
每个节点的职责用一句话说清楚:
- Webhook — 入口,接收外部发来的 URL 和备注
- Normalize Input — 清洗输入,提取 URL、备注、作者、来源
- Fetch Content — 抓取网页正文,处理各种平台的页面结构差异
- Prepare Prompt — 把正文裁剪到 15000 字以内,拼装 LLM prompt
- Ollama — 本地大模型生成 JSON 格式的摘要、标签和分类
- Build Markdown — 把所有信息拼成 Obsidian 友好的 Markdown 文件
- Write to Inbox — 写入 Obsidian Vault 的 Inbox 目录
Step 1 — 安装 Docker + n8n
n8n 推荐用 Docker 跑,隔离干净,升级方便。
安装 Docker
如果你的 Mac 还没装 Docker,去 Docker 官网 下载 Docker Desktop 安装即可。Windows 和 Linux 同理。
启动 n8n
打开终端,一行命令启动 n8n:
docker run -d --name n8n \
-p 5680:5678 \
-v n8n_data:/home/node/.n8n \
-v /path/to/your/obsidian/vault:/know \
-e N8N_RESTRICT_FILE_ACCESS_TO="/know" \
--restart unless-stopped \
n8nio/n8n
解释几个关键参数:
-p 5680:5678— 把容器内的 5678 端口映射到本地 5680。为什么不用默认的 5678?因为可能被其他服务占了,5680 更安全。-v n8n_data:/home/node/.n8n— 数据持久化。Docker 重启后你的 workflow 还在。-v /path/to/your/obsidian/vault:/know— 把你的 Obsidian Vault 目录挂载到容器内的/know,这样 n8n 才能往里面写文件。换成你自己的实际路径。-e N8N_RESTRICT_FILE_ACCESS_TO="/know"— 安全措施,限制 n8n 只能访问/know目录。--restart unless-stopped— 系统重启后 n8n 自动恢复运行。
启动后访问 http://localhost:5680,看到 n8n 的界面就说明成功了。
Step 2 — 安装 Ollama
安装
Mac 用户直接去 ollama.com 下载安装包,或者用 Homebrew:
brew install ollama
拉取模型
我用的是 qwen3:8b — 通义千问的 8B 参数版本,中文能力强,大小适中:
ollama pull qwen3:8b
拉完大约 5GB。首次拉取需要几分钟,取决于你的网速。
快速测试
确认模型能用:
curl http://localhost:11434/api/chat -d '{
"model": "qwen3:8b",
"messages": [{"role": "user", "content": "用一句话介绍你自己"}],
"stream": false
}'
能返回 JSON 响应就说明 Ollama 已就绪。
qwen3:4b。
Step 3 — n8n Webhook 节点
回到 n8n 界面(http://localhost:5680),创建一个新 workflow。
添加 Webhook 节点
- 点击
+添加节点,搜索 Webhook - HTTP Method 选
POST - Path 填
knowledge-inbox - 保存并激活 workflow
你的 webhook URL 就是:http://localhost:5680/webhook/knowledge-inbox
测试 Webhook
打开终端,发一条测试请求:
curl -X POST http://localhost:5680/webhook/knowledge-inbox \
-H 'Content-Type: application/json' \
-d '{
"url": "https://example.com/article",
"note": "测试备注",
"author": "cyrus",
"source": "wechat"
}'
回到 n8n 界面,在 Webhook 节点的执行记录里应该能看到这条数据。如果看到了,说明入口已经通了。
Step 4 — 内容抓取
这是整条管道里最复杂的环节。网页千奇百怪,你需要一个鲁棒的抓取策略。
策略:Jina Reader 优先,fallback 到直接 HTTP
我的做法是两层尝试:
- 先走 Jina Reader — 免费 API,能处理 JavaScript 渲染的页面,直接返回干净的 Markdown。URL 格式:
https://r.jina.ai/<原始URL> - 如果 Jina 失败(返回太短或报错),fallback 到直接 HTTP — 用 n8n 的 HTTP Request 节点直接抓 HTML,然后自己解析正文
在 n8n 里,我用一个 Code 节点来实现这个逻辑。核心代码思路:
fetch-content 核心逻辑(简化版)// 第一步:尝试 Jina Reader
const jinaUrl = 'https://r.jina.ai/' + inputUrl;
const jinaResult = await fetch(jinaUrl, {
headers: {
'accept': 'text/markdown',
'x-return-format': 'markdown',
'x-with-images': 'true'
}
});
// 判断 Jina 结果是否可用
if (jinaResult.ok && jinaResult.text.length > 500) {
// 用 Jina 的结果
return parseJinaMarkdown(jinaResult.text);
}
// 第二步:Jina 失败,fallback 到直接 HTTP
const htmlResult = await fetch(inputUrl);
const article = extractArticleBody(htmlResult.body);
return stripHtml(article);
直接 HTTP 抓取的关键是从 HTML 中提取正文,而不是拿到一堆导航栏、广告、页脚。我的做法是按优先级匹配:
- 找
<article>标签 - 找
<main>标签 - 找 class 包含
article、post、content的<div> - 都没有就用整个
<body>,去掉<header>、<footer>、<nav>、<script>、<style>
小红书特殊处理
小红书的页面结构比较特殊 — 正文内容不在 HTML 里,而是藏在 window.__INITIAL_STATE__ 这个 JavaScript 变量中。所以我在代码里加了一个判断:如果 URL 是 xhslink.com 或 xiaohongshu.com,就走专门的解析逻辑,从那个 JSON 对象里提取标题、正文、图片和作者。
小红书解析(核心思路)// 从 HTML 中提取 __INITIAL_STATE__ JSON
const stateJson = extractBalancedObject(
html, 'window.__INITIAL_STATE__='
);
const state = JSON.parse(stateJson);
// 从 state 中拿到笔记数据
const note = state.note.noteDetailMap[noteId];
const title = note.title;
const description = note.desc;
const images = note.imageList;
xhslink.com/xxx 会 302 重定向到真实地址。确保你的 HTTP 请求开启了 followRedirect: true,否则拿到的是重定向页面而不是内容页面。
Step 5 — Ollama 摘要 + 标签
内容抓到了,接下来让本地 LLM 做三件事:生成一句话摘要、打 5-10 个标签、归入一个分类。
HTTP Request 节点调用 Ollama
在 n8n 里添加一个 HTTP Request 节点,配置如下:
- Method:
POST - URL:
http://host.docker.internal:11434/api/chat - Body Type: JSON
host.docker.internal 而不是 localhost?因为 n8n 跑在 Docker 容器里,容器内的 localhost 指的是容器自己,不是你的 Mac。host.docker.internal 是 Docker Desktop 提供的特殊域名,指向宿主机。
Prompt 设计
Prompt 的设计直接决定了输出质量。这是我实际在用的 prompt 结构:
HTTP Request Body(发送给 Ollama){
"model": "qwen3:8b",
"messages": [
{
"role": "system",
"content": "你是知识整理助手。请只输出严格 JSON,不要 Markdown,不要额外解释。"
},
{
"role": "user",
"content": "JSON 必须包含 summary, tags, category 三个字段。\nsummary: 一句话中文摘要。\ntags: 5-10 个标签,数组格式,兼顾主题/技术/领域。\ncategory: 只能是以下之一:AI技术、AI商业、运营方法、产品思维、技术实操、职业发展、生活洞察。\n\nsource=wechat\nurl=https://...\ntitle=文章标题\nnote=用户备注\ntext=\n(文章正文,最多 15000 字)"
}
],
"stream": false
}
几个设计要点:
- system 消息里强调「只输出 JSON」 — 这不是废话。LLM 默认会加解释文字,而我需要的是可以直接
JSON.parse()的输出。不加这条约束,后续解析会频繁出错。 - category 给定封闭选项 — 让 LLM 从 7 个选项里选一个,而不是自由发挥。这样你的 Obsidian 分类体系是可控的。
- 正文限制 15000 字 — 8B 模型的 context window 有限。超长文章裁到 15000 字,保证不超限的同时覆盖大部分内容。
JSON 解析 + fallback
LLM 输出的 JSON 不总是干净的。我的代码里做了三层防御:
JSON 解析逻辑let parsed;
try {
let candidate = rawOutput;
// 去掉可能的 markdown 代码块包装
if (candidate.startsWith('```')) {
candidate = candidate
.replace(/^```(?:json)?/i, '')
.replace(/```$/i, '')
.trim();
}
// 如果不是以 { 开头,尝试从中间提取 JSON
if (!candidate.startsWith('{')) {
const match = candidate.match(/\{[\s\S]*\}/);
if (match) candidate = match[0];
}
parsed = JSON.parse(candidate);
} catch {
// 解析失败:用 fallback 值
parsed = {
summary: articleText.slice(0, 100),
tags: ['知识管理', '内容沉淀', '自动化工作流'],
category: '未分类',
};
}
关键思想:永远不要假设 LLM 的输出格式是完美的。写好 fallback,让管道在 LLM 犯错时也能继续运行,只是输出质量稍低而已。
```json ... ``` 里面。如果你不处理这层包装,JSON.parse() 直接报错,整条管道就断了。上面那段代码的第一个 if 就是处理这个问题。
Step 6 — 生成 Markdown 写入 Obsidian
最后一步:把所有信息拼成一个 Obsidian 友好的 Markdown 文件。
YAML Frontmatter
Obsidian 支持 YAML frontmatter 作为文件的元数据。我生成的格式是这样的:
生成的 Markdown 文件示例---
title: "Claude 4 Opus 发布:多模态推理能力大幅提升"
date: 2026-05-08T10:30:00.000Z
type: article
category: AI技术
tags:
- Claude
- Anthropic
- 多模态
- LLM
- AI技术
source: "https://example.com/claude-4-opus"
author: "Cyrus"
status: inbox
---
> **摘要**: Anthropic 发布 Claude 4 Opus,在多模态推理、
> 长上下文处理和代码生成方面大幅超越前代模型。
## 原文内容
(完整正文...)
---
🔗 [原文链接](https://example.com/claude-4-opus)
几个设计决策:
status: inbox— 所有新文件默认进 inbox 状态。我会在 Obsidian 里定期 review,把有价值的改成processed,没价值的改成archived。- 文件名包含标题 + 时间戳 — 格式是
标题-20260508-103000.md,保证不重名。 - 图片用 Markdown 链接引用 — 不下载图片到本地,只保留 URL。节省磁盘空间,打开笔记时 Obsidian 会自动加载。
写入文件
最后用 n8n 的 Write Binary File 节点(或 Move Binary Data + Write File 组合)把 Markdown 写入 /know/Inbox/ 目录。因为我们在 Docker 启动时把 Obsidian Vault 挂载到了 /know,所以容器内写 /know/Inbox/xxx.md,实际就写到了你本地的 Obsidian Vault 里。
打开 Obsidian,你会看到 Inbox 文件夹里多了一个新笔记,带完整的 frontmatter、摘要、标签和原文。整个过程从发送链接到笔记出现,大约 10-15 秒。
实际效果 + 踩坑记录
这套管道我跑了两个月,日均处理 5-15 篇文章。绝大部分时间它安安静静地工作,但也遇到过几个坑:
brew services start ollama);(2) 在 n8n 的 HTTP Request 节点开启 Retry On Fail,设 3 次重试,间隔 30 秒。
fetch_failed,手动处理。这是目前管道里最不完美的环节。
调试技巧
出问题时的排查路径:
- 打开 n8n 的 Executions 页面(左侧栏),找到失败的执行记录
- 点击失败的节点,看 Input 和 Output 数据
- 常见问题:Fetch 节点拿到的 data 为空(抓取失败)、Ollama 返回非 JSON(prompt 没约束好)、Write 节点权限不足(
N8N_RESTRICT_FILE_ACCESS_TO没配对)
总结
- 整条管道 6 个节点:Webhook → Normalize → Fetch → Prompt → Ollama → Build + Write
- 核心技术栈:Docker + n8n(自动化)+ Ollama qwen3:8b(本地 LLM)+ Obsidian(知识库)
- 运行成本:0 元/月。数据全程不出本地。
- 从分享链接到笔记入库,10-15 秒。
- 关键设计原则:Jina Reader + HTTP fallback 双保险抓取;LLM 输出做三层 JSON 解析防御;永远写 fallback 让管道不因单点错误中断。
如果你也在用 Obsidian 做知识管理,但苦于手动整理太慢,这套管道值得花一个下午搭起来。一旦跑通,你会发现「阅读 → 归档 → 检索」的闭环变得无比丝滑。