你每天刷到几十条 AI 资讯、技术文章、行业分析,随手转发到微信群或收藏夹,然后呢?三天后你连标题都想不起来。如果每一条你分享的链接,能自动被抓取正文、AI 生成摘要和标签、归档到你的 Obsidian 知识库 — 而且整个过程跑在你自己的电脑上,不花一分钱,你会不会想试试?这篇教程把我实际在用的这套管道从头拆给你看。

为什么要本地 AI 管道

在开始动手之前,先说清楚为什么我选了这条路,而不是直接用 Notion Web Clipper 或 Readwise 之类的现成工具。

给新手的一句话解释:n8n 相当于免费版的 Zapier — 一个可视化的自动化工具,把不同服务串成流水线。Ollama 相当于本地版的 ChatGPT — 在你自己的电脑上跑大语言模型。两者结合,就是一条完全属于你的 AI 流水线。

架构总览

先看全景图,搞清楚数据怎么从你的手机跑到 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/

每个节点的职责用一句话说清楚:

  1. Webhook — 入口,接收外部发来的 URL 和备注
  2. Normalize Input — 清洗输入,提取 URL、备注、作者、来源
  3. Fetch Content — 抓取网页正文,处理各种平台的页面结构差异
  4. Prepare Prompt — 把正文裁剪到 15000 字以内,拼装 LLM prompt
  5. Ollama — 本地大模型生成 JSON 格式的摘要、标签和分类
  6. Build Markdown — 把所有信息拼成 Obsidian 友好的 Markdown 文件
  7. Write to Inbox — 写入 Obsidian Vault 的 Inbox 目录
不要一口气搭完。正确的顺序是:先把 Webhook → Fetch → Write 跑通(不加 LLM),确认能拿到正文并写成文件,然后再加 Ollama 节点。我第一次搭的时候一步到位,结果 debug 了一整晚都不知道是 Fetch 的问题还是 Ollama 的问题。

Step 1 — 安装 Docker + n8n

n8n 推荐用 Docker 跑,隔离干净,升级方便。

安装 Docker

如果你的 Mac 还没装 Docker,去 Docker 官网 下载 Docker Desktop 安装即可。Windows 和 Linux 同理。

给新手:Docker 是一个「容器」工具,你可以把它理解成一台虚拟电脑。n8n 跑在这个虚拟电脑里,不会弄脏你的系统环境。

启动 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

解释几个关键参数:

启动后访问 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:8b?三个原因:(1) 中文能力在同尺寸开源模型中数一数二;(2) 8B 参数在 M1 Pro 16GB 上跑得很流畅,3-8 秒出结果;(3) 对「只输出 JSON」这类结构化指令的遵从度很好,减少后续解析出错。如果你内存只有 8GB,换 qwen3:4b

Step 3 — n8n Webhook 节点

回到 n8n 界面(http://localhost:5680),创建一个新 workflow。

添加 Webhook 节点

  1. 点击 + 添加节点,搜索 Webhook
  2. HTTP Method 选 POST
  3. Path 填 knowledge-inbox
  4. 保存并激活 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

我的做法是两层尝试:

  1. 先走 Jina Reader — 免费 API,能处理 JavaScript 渲染的页面,直接返回干净的 Markdown。URL 格式:https://r.jina.ai/<原始URL>
  2. 如果 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 中提取正文,而不是拿到一堆导航栏、广告、页脚。我的做法是按优先级匹配:

  1. <article> 标签
  2. <main> 标签
  3. 找 class 包含 articlepostcontent<div>
  4. 都没有就用整个 <body>,去掉 <header><footer><nav><script><style>
Jina Reader 的免费额度足够个人使用。如果你每天处理的文章不超过几十篇,基本不会碰到限制。而且它对付那些重度 JavaScript 渲染的单页应用特别有效 — 直接 HTTP 抓这类页面只能拿到一个空壳。

小红书特殊处理

小红书的页面结构比较特殊 — 正文内容不在 HTML 里,而是藏在 window.__INITIAL_STATE__ 这个 JavaScript 变量中。所以我在代码里加了一个判断:如果 URL 是 xhslink.comxiaohongshu.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 节点,配置如下:

为什么是 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
}

几个设计要点:

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 犯错时也能继续运行,只是输出质量稍低而已。

「不要 Markdown」这句话真的很重要。qwen3:8b 有时候会把 JSON 包在 ```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)

几个设计决策:

写入文件

最后用 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 篇文章。绝大部分时间它安安静静地工作,但也遇到过几个坑:

Ollama 没启动,管道静默失败。n8n 调用 Ollama 的 HTTP Request 节点超时后,默认行为是报错但不重试。如果你的 Mac 重启后忘了打开 Ollama,所有文章都会卡在这个节点。解决方案:(1) 把 Ollama 设成开机启动(brew services start ollama);(2) 在 n8n 的 HTTP Request 节点开启 Retry On Fail,设 3 次重试,间隔 30 秒。
微信文章需要 Cookie 才能访问。有些微信公众号文章对非微信浏览器返回空白页或验证码页。Jina Reader 能处理大部分情况,但偶尔也会失败。目前的 workaround 是在 fetch 失败时检查返回内容是否包含「登录」「验证码」等关键词,如果是,标记为 fetch_failed,手动处理。这是目前管道里最不完美的环节。

调试技巧

出问题时的排查路径:

  1. 打开 n8n 的 Executions 页面(左侧栏),找到失败的执行记录
  2. 点击失败的节点,看 InputOutput 数据
  3. 常见问题:Fetch 节点拿到的 data 为空(抓取失败)、Ollama 返回非 JSON(prompt 没约束好)、Write 节点权限不足(N8N_RESTRICT_FILE_ACCESS_TO 没配对)

总结

如果你也在用 Obsidian 做知识管理,但苦于手动整理太慢,这套管道值得花一个下午搭起来。一旦跑通,你会发现「阅读 → 归档 → 检索」的闭环变得无比丝滑。