把 Obsidian 和 Markdown 里的图片迁移到新图床

2026-09-01 | 作者:Molunerfinn

把 Obsidian 和 Markdown 里的图片迁移到新图床

你用的图床关停了,于是你写过的每一篇文章里的每一张图,都成了失效链接。又或者,你有一个 Obsidian 库,所有截图都躺在 attachments/ 里,除了你自己的电脑,放到哪儿都显示不出来。

这两件事本质上是同一个问题:几百个 Markdown 文件,里面的图片链接指向了不该指向的地方,而且根本没法靠手工一个个改。

picgo-plugin-pic-migrater 可以替你搞定。它会读取你的 Markdown 文件,把每一张图片重新上传到 PicGo 当前配置的图床,然后改写链接。一个文件、一个目录、一整个库都可以。从 v1.4.0 开始,它还支持 Obsidian 的 ![[wikilink]] 嵌入。

简单版

假设你已经装好了 PicGo-Core,也配置好了新图床,只需要三条命令:

picgo install pic-migrater
picgo set plugin pic-migrater
picgo migrate ./posts/

最后一条命令会递归遍历 ./posts/,迁移其中所有 .md 文件。默认情况下原文件不会被改动——改写后的内容会写到旁边的 post_new.md 里,你可以先 diff 一下两个文件,再决定留哪个。

在 PicGo 桌面端里也能用。行为完全一样,只不过入口换成了插件上的 选择文件 / 选择文件夹 菜单。

它到底做了什么

这不是对文本做一次查找替换。对每个 Markdown 文件,插件会:

  1. 提取图片链接。 既支持 Markdown 语法(![](url)),也支持原生 HTML(<img src="url" />,v1.3.0 起支持)。
  2. 获取每一张图片。 远程 URL 会下载到内存里;本地路径则直接从磁盘读取——绝对路径或相对于 Markdown 文件的相对路径都行,像 ./my%20image.png 这样经过 URL 编码的路径也会先解码再重试。
  3. 通过 PicGo 上传。 插件自己没有上传器。它把图片交给 PicGo,由 PicGo 发往你已经配置好的地方——PicGo Cloud、GitHub、Amazon S3、腾讯云 COS、七牛、又拍云、SM.MS,或者你安装的任何第三方上传插件。
  4. 改写链接,换成新的 URL,然后写出文件。

正因为第 3 步,你不需要去查什么「支持的目标图床列表」。只要 PicGo 能传过去,就能迁移过去。

这也意味着本地图片和远程图片的处理方式完全一样。在两个图床之间搬家是最常见的场景,但把本地附件传到图床上用的也是同一条命令——这正是它适合用来处理 Obsidian 库的原因。

先配置

第一次迁移之前,先运行 picgo set plugin pic-migrater,哪怕你全部使用默认值。没有配置的话,插件会拒绝运行:

You should configure this plugin first!
picgo set plugin pic-migrater

在桌面端里对应的是插件的 配置 按钮,提示会以通知的形式弹出,而不是一行日志。

一共有四个配置项。其中两个决定了这次迁移是顺顺利利,还是搞得一团糟。

配置项作用默认值
newFileSuffix生成文件的后缀_new
include只迁移匹配它的链接空
exclude跳过匹配它的链接空
oldContentWriteToNewFile反转被覆盖的是哪个文件false

用默认后缀的话,迁移 2019.md 会生成 2019_new.md。include 为空表示全部迁移;exclude 为空表示什么都不跳过。

include / exclude:只迁移挂掉的那个图床

这是大多数人最需要、却最找不到的配置。

真实的 Markdown 存档从来不会只用一个图床。有的图片在刚挂掉的图床上,有的在一个还好好的 CDN 上,还有 shields.io 的徽章,以及几张本地截图。你想要的是把挂掉的那部分搬走,其余的一概不动。

所以把 include 设成你要逃离的那个图床:

sinaimg.cn

这样只有包含 sinaimg.cn 的 URL 会被迁移。你的 shields.io 徽章原样保留,也不用把几百张本来就没坏的图片再传一遍。

exclude 则反过来,适合「列出要跳过的更方便」的情况。两个都设置的话,一条链接必须匹配 include 并且不匹配 exclude,才会被迁移。

有一点文档里说得不够清楚:这两个值都会被编译成正则表达式,而不是 glob 模式或者普通子串。这很好用——sinaimg\.cn|imgur\.com 一次就能迁移两个图床。但这也意味着 . 和 ? 带有正则含义,所以匹配普通域名时记得把点转义。

安全网:到底覆盖哪个文件

默认情况下,原文件保持不动,迁移后的内容写到一个新文件里:

  • post.md — 不变,仍然指向旧图床
  • post_new.md — 新文件,新链接

你检查完之后再自己把两者对调。这样很安全,但对 Obsidian 库或 Hexo 站点来说就很别扭了:文件名就是 URL,一整个目录的 _new 文件显然不是你想要的。

oldContentWriteToNewFile(v1.3.0+)就是为此准备的。把它设为 true,两者的角色就对调了:

  • post.md — 被新链接覆盖,文件名不变
  • post_new.md — 原文件的备份

无论哪种方式,最后都是这两个文件。唯一的区别是哪个文件保留原来的文件名。对于路径很重要的站点或笔记库,第二种布局才是你要的。

不管用哪种方式,后缀都不要留空。newFileSuffix 为空的话,生成的文件名就和源文件一模一样,最后你只剩一个文件:被覆盖掉的原文件,没有任何备份。

迁移 Obsidian 库

如果是往笔记里粘贴新截图,请看用 PicGo 在 Obsidian 里自动上传图片。下面的步骤针对的是库里已经存在的图片的迁移。

这是大家用这个插件最常见的原因,而且有一个前提条件,最好在开始之前就弄对。

笔记库和一个博客文章目录有两点不同。从 v1.4.0 开始,这两点都已经处理好了。

Obsidian 默认把图片嵌入写成 ![[Pasted image 20240101.png]]。这种语法没有圆括号,所以 1.3.3 及之前的版本会完全跳过它——迁移照常运行,报告的图片数比你库里实际的少,每个嵌入依旧指向本地文件。唯一的变通办法是先在 Obsidian 里关掉 使用 [[Wiki 链接]],但这对你已经写好的笔记毫无作用。

从 v1.4.0 开始,它们可以直接迁移。嵌入语法没法放远程 URL,所以会被改写成标准的 Markdown:

<!-- 迁移前 -->
![[Pasted image 20240101.png]]
![[diagram.png|300]]

<!-- 迁移后 -->
![](https://your-host.com/xxx.png)
![](https://your-host.com/yyy.png)

有三点值得了解:

  • 只动图片。 Obsidian 用 ![[...]] 嵌入任何文件,笔记也包括在内。像 ![[Some note#heading]] 这样的笔记嵌入,以及普通的 [[Some note]] 链接,都会原封不动。这点比听起来更重要——如果你的库里有讲 Obsidian 的笔记,这种语法会出现在正文和代码块里,把它改写掉就错了。
  • 显示参数会被处理。 ![[image.png|300]] 和 ![[image.png|alt|300]] 都会解析成 image.png。尺寸设置会随着嵌入语法一起去掉。
  • 短名称按 Obsidian 的方式解析。 ![[image.png]] 里没有路径——Obsidian 是通过搜索整个库来找到文件的。插件也一样:先尝试显式的相对路径,再搜索笔记所在的目录树。所以默认的 attachments/ 布局开箱即用。

用 picgo install pic-migrater 升级(或者在桌面端插件列表里点 更新)。迁移笔记库之前,确认你用的是 1.4.0 或更新的版本。

使用相对路径

在 设置 → 文件与链接 中,把 内部链接类型 设为 基于当前笔记的相对路径。插件会以笔记所在的目录为基准解析相对图片路径,所以基于库根目录的路径,对子目录里的笔记全都会失效。

然后迁移整个库

本地附件和远程 URL 的处理方式完全一样,所以一条命令就能把满是 attachments/ 的库整个搬到图床上:

picgo migrate ~/Documents/MyVault/

对于笔记库,请先把 oldContentWriteToNewFile 设为 true。Obsidian 是按文件名来定位笔记的,库里到处是 note_new.md 会让你的内部链接全部失效;你希望迁移后的内容保留原文件名,让备份带上后缀。

运行之前要知道的四件事

这些来自实际行为,而不是 README。

先 commit。 迁移会改写磁盘上的文件,开启 oldContentWriteToNewFile: true 时改写的就是你的原文件。事先 git commit 一下,或者干脆把目录复制一份,任何意外都能一行命令撤销。

旧图片必须还能访问。 远程图片的迁移方式是下载再重新上传。如果旧图床已经彻底下线,或者用防盗链拦截了普通请求,那就没东西可下载,这些链接会保持原样。所以要在图床挂掉之前迁移,而不是之后。一旦某个服务宣布了关停日期,那就是你的窗口期。

每张图片单独占一行。 链接提取按每行一张图片来处理。如果在同一行里并排放两张 Markdown 图片,只有最后一张会被识别。大多数 Markdown 本来就是一行一张图,所以很少遇到这个问题,但如果你用了并排图片,最好检查一下。引用式链接(![alt][ref])同样不会被识别——必须写成行内链接。

最后看一眼统计数字。 运行结束时会输出一份汇总:

Success: 128 pics, Fail: 3 pics

对于老存档来说,有失败很正常:源图床上已经 404 的图片、几年前就挪过位置的路径。这些链接会继续指向原来的地方,所以情况不会变得更糟。这个数字只是告诉你需要抽查多少。

在 Node.js 里调用

如果要写脚本,比如跨多个仓库迁移,或者作为构建中的一个步骤,可以直接调用 API(v1.2.3+):

const { PicGo } = require('picgo')
const PluginMigrater = require('picgo-plugin-pic-migrater')

const picgo = new PicGo()

picgo.setConfig({
  'picgo-plugin-pic-migrater': {
    newFileSuffix: '_new',
    include: '',
    exclude: ''
  }
})

const plugin = picgo.use(PluginMigrater)

const result = await plugin.migrateFiles(['/path/to/post.md'])
// { total: 12, success: 12 }

setConfig 也算作配置了插件,所以能通过「先配置」的检查。

即使你想让 include 和 exclude 为空,也要显式地写上。在 1.4.0 之前的版本里,如果完全不写这两个 key,过滤器会退回到一个什么都匹配不上的占位值,结果运行报告零张图片、一个文件都没改——看起来和「迁移了但什么都没找到」一模一样。这个问题已在 v1.4.0 修复,但显式传入仍然是更清晰的习惯。

版本要求

插件会调用 PicGo 的内部接口,所以版本需要对得上:

插件PicGo 桌面端PicGo-Core
> 1.2.22.3.0+1.5.0-alpha.1+
<= 1.2.22.0.2 ~ 2.2.01.4.0 ~ 1.5.0

当前版本是 1.4.0,所以只要是较新的 PicGo,都适用第一行。Obsidian 的 wikilink 嵌入则特别要求 1.4.0 或更新版本。

在较老的桌面端版本上,插件会一开始就告诉你,而不是迁移到一半才失败。

常见问题

问:我的图床能用吗?

答:只要 PicGo 能上传到它,就能用。插件自己不上传任何东西——它把图片交给 PicGo,由 PicGo 使用你配置好的图床,第三方上传插件也包括在内。

问:它会覆盖我原来的 Markdown 文件吗?

答:默认不会。它会用你配置的后缀写一个新文件(post_new.md),原文件保持不动。把 oldContentWriteToNewFile 设为 true 可以反过来——原文件名保存新链接,备份带上后缀。

问:能把本地图片迁移到图床吗?

答:能。相对路径和绝对路径的本地图片,处理方式和远程 URL 完全一样,所以一个满是本地附件的笔记目录,和一个满是失效外链的博客,用的是同一条命令。

问:Obsidian 能用吗?

答:能。请使用 v1.4.0 或更新版本,它可以直接迁移 Obsidian 默认的 ![[image.png]] 嵌入——更早的版本只识别标准的 ![](path) 链接,会悄无声息地跳过所有嵌入。在 设置 → 文件与链接 里把 内部链接类型 设为 基于当前笔记的相对路径,并开启 oldContentWriteToNewFile,让你的笔记保留原文件名。

问:它会改写我的 [[笔记链接]] 或笔记嵌入吗?

答:不会。只有带图片扩展名的目标才会被迁移,所以 ![[Some note#heading]] 和普通的 [[Some note]] 都会原封不动——包括这些语法出现在讲 Obsidian 本身的笔记正文或代码块里的情况。

问:能只迁移某一个图床上的图片吗?

答:这正是 include 的用途。把它设为那个图床的域名,文件里其他所有链接都不会被动。

问:它能处理 HTML 的 <img> 标签吗,还是只支持 Markdown 语法?

答:能,v1.3.0 起支持。![](url) 和 <img src="url" /> 都会被识别并改写。

问:迁移失败的图片会怎样?

答:它们的链接保持不变,所以不会比之前更糟。最后的汇总会报告成功和失败各多少张。

问:命令行和桌面端都能用吗?

答:都能。命令行里用 picgo migrate <files...>,桌面端里用插件上的 选择文件 / 选择文件夹 菜单。

PicList 用户可以在 npm 上找到这个插件的单独分支 picgo-plugin-pic-migrater-piclist。两个应用之间的插件兼容关系,可以看 PicGo 和 PicList 对比。

试试看

picgo install pic-migrater
picgo set plugin pic-migrater
picgo migrate ./posts/

插件基于 MIT 协议,在 GitHub 上开源。如果它漏掉了你文件里的某种链接写法,欢迎附上示例提个 issue。现在大部分提取规则就是这么来的,v1.4.0 的 wikilink 支持也是。

最初的想法来自 @Moyf 写的一个 Python 脚本。修复几百个失效图片链接的办法一直都是同一个——只是它不该再是每个人都要自己写一遍的脚本了。

迁移愉快!