ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Typora+博客园图床:实现Markdown图片自动上传的完整方案

Typora+博客园图床:实现Markdown图片自动上传的完整方案 1. 先把痛点说清楚为什么你在博客园写的Markdown总是“图挂了”如果你长期用博客园写技术文章肯定遇到过这个折磨本地用Typora写得好好的各种截图、架构图、流程图、UML图插了一堆文章发布到博客园之后前面几段文字都在图片却整整齐齐全部裂开浏览器里只剩一串破图标和alt文本。什么“图片不能外链”不是的博客园对图片外链其实挺宽容的真正的原因是你本地Markdown里的图片是本地文件路径。举个最简单的例子你在Typora里粘贴了一张截图Typora默认配置下会把图片存到本地某个目录Markdown源码里记录的是这样一行![](C:\Users\你的用户名\AppData\Roaming\Typora\typora-user-images\image-20250115102438152.png)这行代码在你自己电脑上打开Typora渲染完全正常因为Typora读的是本地文件。但你把整个Markdown源码复制粘贴到博客园的编辑器里博客园服务器去哪个盘找这个文件它根本找不到所以图片就挂了。这个问题困扰了我很久。早期我的解决方案是先把文章发布到博客园再一张一张地手动把图片传上去然后把图片URL替换到源码里。文章一长图片一多这活儿就是纯粹的体力活而且极其容易漏。后来我换了思路让图片在粘贴进Typora的同时就自动传到博客园本地Markdown里写的直接就是博客园的图片URL这样复制到博客园编辑器就必然是好的。整个方案落地之后我写一篇带20张截图的教程从截图到发布全程不会超过15分钟图片一张都不用手动处理。这篇文章就完整记录我的做法包括工具选择、配置步骤和我在使用过程中踩过的坑如果你想在博客园稳定输出带图文章这套方案可以直接抄。2. 方案整体拆解Typora 上传脚本 博客园图床2.1 核心思路把“本地图片”变成“网络图片”整个方案的核心逻辑其实很简单在Typora里每插入一张图片就触发一个外部脚本这个脚本负责把图片文件上传到博客园服务器上传成功后返回一个以https://img2023.cnblogs.com/...开头的图片URLTypora拿到这个URL之后自动替换掉本地路径。这里的关键人物有两个Typora本身的“图片上传”机制Typora在偏好设置里内置了图片上传功能支持自定义命令。你把图片粘贴进去它会自动把图片交给这个命令去处理命令的输出会被Typora解析为Markdown图片语法。图片上传脚本这个脚本负责拿到图片文件后通过接口或API把图片传到某个可以公网访问的地方然后把可访问的URL返回给Typora。整套链路可以这样理解你截图、粘贴、Typora调用脚本、脚本上传图片、返回URL、Typora自动替换路径整个过程通常在2~3秒内完成。你在Typora里看到的图片和你最终发布到博客园文章里的图片是同一个URL完全不需要任何二次处理。2.2 图床选择为什么直接传博客园而不是用第三方图床很多人可能会想既然Typora支持自定义上传命令那直接把图片传到某个免费图床不就行了比如一些公开的图床服务或者用GitHub仓库当图床再或者用腾讯云、阿里云的OSS为什么非要传到博客园我的答案很简单为了“一劳永逸”和“来源可控”。第三方图床最大的问题是不稳定国内很多免费图床说关就关图片传上去过半年全变成防盗链提示。用GitHub仓库当图床国内访问速度不理想而且GitHub仓库一旦被滥用还有封号风险。OSS确实稳定但需要花钱还要自己配置跨域和防盗链门槛偏高。而博客园本身就给每个用户提供了图片上传接口传上去的图片就存储在img2023.cnblogs.com这类域名下稳定性和访问速度都有保障。更重要的是博客园用户上传的图片本身就允许外链你的图片传上去之后不仅博客园自己的文章能显示任何网页里嵌入这个URL都能正常加载。这个“免费、稳定、完全够用”的图床其实就藏在你自己每天发文章的平台上只是很多人没有注意到。有人可能会问博客园的图片接口是不是需要通过登录Cookie鉴权是的方案的关键就在这里你看完下一节就明白了。2.3 工具选型你需要准备什么搞这套方案你需要准备三样东西已经安装好的Typora一个可以运行脚本的环境Windows下推荐PowerShellmacOS/Linux下推荐Python3下面我会给两个版本你的博客园账号Cookie中用于图片上传的凭证服务器端你什么都不用买博客园本身就是你的服务器。整个过程不涉及服务器部署、不改博客园后台代码就是纯本地脚本和配置安全风险很低行为上和你手动在博客园编辑器里上传图片是一回事。3. Typora端的配置自定义上传命令3.1 打开偏好设置里的图片上传入口先打开Typora的“文件 - 偏好设置”在左侧菜单里找到“图像”这一栏。这里就是整个方案的开关所在。在这个界面里你要关注三个地方“插入图片时”的行为建议选择“复制图片到 ./${filename}.assets 文件夹”这是为了让你的本地图片在Typora工作目录里有一个固定的存放位置便于后面脚本遍历处理。当然如果你不介意图片散落在Typora的user-images目录里也可以不修改这一项脚本同样能处理但统一目录会让你的文章目录更干净。“上传图片”的行为这里选择“上传图片”并且下面的“上传图片应用”选择“自定义命令”。只有选自定义命令才能把控制权交给你自己写的脚本。“自定义命令”这一栏留空或者填入你要运行的命令行具体写法我稍后给。需要说明的是Typora的图片上传动作是这样触发的当你在文档里粘贴图片时Typora会先按照“插入图片时”的规则保存图片文件然后判断当前文档是否自动启动了上传以及你的偏好设置是否勾选了“上传图片应用”如果都满足就会执行你设置的自定义命令。3.2 自定义命令的语法规则Typora的自定义命令语法比较简单它支持用占位符把当前图片路径传给你的脚本python D:/tools/upload_image.py ${imagePath}在Windows下如果脚本路径或者图片路径包含空格务必用双引号包裹。因为Typora会把这个命令拆分成参数后传给系统去执行路径一有空格就可能会出现参数被截断的问题我一开始就吃过这个亏。${imagePath}这个变量名是Typora内置的代表当前要上传的那张图片的绝对路径注意它是单数一次只传一张。你可能想让它一次传多张图比如一次粘贴了3张图片抱歉Typora的自定义命令是逐张调用的每次只传一个路径。所以你的脚本不需要写循环处理多张图片的逻辑专注于处理单张就行。在macOS/Linux环境下如果你用的是Python命令长得差不多区别只是Python解释器的路径和脚本路径要写对python3 /Users/你的用户名/tools/upload_image.py ${imagePath}3.3 验证Typora是否正确调用了脚本配好之后怎么确保Typora真的调用了你的脚本有个很实用的调试技巧先在命令行里直接运行一遍你的脚本传一张图片路径看看返回结果。确认命令本身没有问题之后再回到Typora里触发上传。在Typora里触发上传有三种方式新粘贴一张图片到文档里右键点击图片 - “上传图片”使用菜单栏的“格式 - 图像 - 上传图片”每次触发之后打开Typora的“帮助 - 开发者工具”切到Console标签页查看日志。如果命令执行成功你会看到Typora打印出的图片上传成功的日志里面包含了最终插入文档的URL。如果执行失败日志里通常也会给出错误输出这是排查问题的第一现场。4. 图片上传实现写一个真正能用的上传脚本4.1 博客园图片上传接口是怎么工作的要写上传脚本首先得搞清楚博客园图片上传接口的请求格式。博客园的编辑页面里图片上传走的是一个标准的HTTP接口它的核心逻辑是请求地址https://upload.cnblogs.com/imageupload/图片目录名这个目录名是上传时动态指定的请求方式POST请求头部需要带上你登录博客园后的Cookie服务端通过Cookie识别你的用户身份请求体multipart/form-data格式里面有一个file字段值就是图片的二进制内容因为博客园的图片上传接口要求带Cookie所以你脚本里需要写死一个有效的Cookie字符串。这个Cookie从哪里来很简单你用自己的账号登录博客园打开浏览器开发者工具找到上传图片时网络请求里的Cookie头复制出来填进脚本即可。这里有个细节需要注意Cookie是有时效性的尤其是博客园的登录Cookie过一段时间可能会失效。失效之后你的脚本会上传失败这时重新登录一次再取一次Cookie更新到脚本里就行。我一般是每1~2个月更新一次因为我自己登录后台的频率本身就不低。4.2 用Python写一个上传脚本跨平台推荐脚本的核心逻辑不复杂我直接给出一版我带注释的完整代码这个版本在Windows和macOS都能跑import os import sys import time import requests def upload_image(image_path, cookie_str): # 判断文件是否存在 if not os.path.exists(image_path): return 文件不存在: image_path # 博客园上传接口目录名可以随意指定一般用当天日期方便后台管理 folder_name time.strftime(%Y%m%d) upload_url https://upload.cnblogs.com/imageupload/ folder_name headers { Cookie: cookie_str, User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 } # 打开图片文件绑定到 multipart 请求的 file 字段 with open(image_path, rb) as f: files {file: (os.path.basename(image_path), f)} try: resp requests.post(upload_url, headersheaders, filesfiles, timeout30) except requests.exceptions.RequestException as e: return 上传请求失败: str(e) # 解析响应响应一般是简单的JSON里面包含图片URL try: data resp.json() if data.get(code) 0 and data.get(url): return data[url] else: return 上传失败: data.get(msg, resp.text) except Exception: return 响应解析失败: resp.text[:200] if __name__ __main__: # 从命令行接收图片路径 image_path sys.argv[1] # 这里填你自己的博客园Cookie my_cookie 这里换成你自己的Cookie result upload_image(image_path, my_cookie) print(result)把这段代码保存成upload_image.py文件然后在命令行测试python upload_image.py D:\screenshot\test.png如果一切正常脚本会打印出一行以https://img2023.cnblogs.com/开头的URL这就说明接口调用成功了。4.3 用PowerShell写一个轻量版本Windows专用如果你不想装PythonWindows下用PowerShell也可以实现。PowerShell脚本的好处是系统自带、零依赖坏处是代码稍微啰嗦点。下面是我另一台电脑上用的版本param( [string]$imagePath ) $cookie 这里换成你自己的Cookie $folderName Get-Date -Format yyyyMMdd $uploadUrl https://upload.cnblogs.com/imageupload/$folderName # 读取文件并转成byte数组 $fileBytes [System.IO.File]::ReadAllBytes($imagePath) $fileName Split-Path $imagePath -Leaf # 构建multipart/form-data请求体 $boundary [System.Guid]::NewGuid().ToString() $fileHeader --$boundaryrnContent-Disposition: form-data; namefile; filename$fileNamernContent-Type: application/octet-streamrnrn $fileFooter rn--$boundary--rn $fileHeaderBytes [System.Text.Encoding]::UTF8.GetBytes($fileHeader) $fileFooterBytes [System.Text.Encoding]::UTF8.GetBytes($fileFooter) $requestStream New-Object System.IO.MemoryStream $requestStream.Write($fileHeaderBytes, 0, $fileHeaderBytes.Length) $requestStream.Write($fileBytes, 0, $fileBytes.Length) $requestStream.Write($fileFooterBytes, 0, $fileFooterBytes.Length) $body $requestStream.ToArray() $requestStream.Dispose() $headers { Cookie $cookie User-Agent Mozilla/5.0 } try { $resp Invoke-RestMethod -Uri $uploadUrl -Method Post -Headers $headers -ContentType multipart/form-data; boundary$boundary -Body $body -TimeoutSec 30 if ($resp.code -eq 0 -and $resp.url) { Write-Output $resp.url } else { Write-Output 上传失败: $($resp.msg) } } catch { Write-Output 上传请求失败: $($_.Exception.Message) }保存为upload_image.ps1后在PowerShell里执行策略可能需要调整。如果你运行时报“在此系统上禁止运行脚本”的错误可以用管理员身份运行一次Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser或者干脆用powershell -ExecutionPolicy Bypass -File的方式调用这样不用改系统策略。4.4 两个脚本各自的适用场景Python版本适合已经装了Python环境的开发者requests库安装一下就行代码可读性和可维护性都更好。PowerShell版本适合完全不想装第三方环境的人Windows 10/11自带PowerShell脚本写完就能用不需要额外安装任何东西。我自己现在的主力机是WindowsTypora里配置的是PowerShell版本因为我觉得能少装一个依赖就少装一个。但我的笔记本是macOS那上面只能跑Python版本。所以我在不同机器上用的是不同的脚本但只要输入输出格式一致Typora这边的配置逻辑完全一样换机器无缝衔接。5. 把脚本挂到Typora上完整配置示范5.1 一个可直接抄的配置样例假设你的Python脚本放在D:\tools\upload_image.py那么Typora“自定义命令”那一栏里填的内容就是python D:\tools\upload_image.py ${imagePath}假设你用的是PowerShell脚本路径是D:\tools\upload_image.ps1那么填写的是powershell -NoProfile -ExecutionPolicy Bypass -File D:\tools\upload_image.ps1 ${imagePath}注意这里的差异Python脚本是用python加脚本路径PowerShell脚本因为涉及执行策略所以加了一串参数。-NoProfile表示不加载用户配置启动更快-ExecutionPolicy Bypass表示绕过执行策略限制确保脚本能跑起来-File后面跟脚本绝对路径。5.2 配置完成后的完整上传链路演示配置好之后我在Typora里做一次完整的图片上传演示带你走一遍整个流程这样你对这套机制有更直观的理解。我在本地新建一篇Markdown文档标题叫“测试图片自动上传”然后往里面粘贴了一张截图。Typora检测到图片插入先按“复制图片到./${filename}.assets文件夹”的规则把这张截图保存到了当前目录下的测试图片自动上传.assets文件夹里然后立即触发了自定义命令。我在命令行里看到的实际执行过程是这样的powershell -NoProfile -ExecutionPolicy Bypass -File D:\tools\upload_image.ps1 D:\blog\测试图片自动上传.assets\image-20250115102438152.png脚本执行大概2秒之后Typora文档里的图片路径由![](测试图片自动上传.assets/image-20250115102438152.png)自动变成了![](https://img2023.cnblogs.com/blog/123456/202501/123456-20250115102438152.png)我把光标悬停到图片上确认图片正常显示且URL以img2023.cnblogs.com开头。到这里本地Markdown里的图片就已经是公网可访问的了。然后我把这段源码复制到博客园编辑器点击发布图片全部正常渲染。5.3 插入多张图片时的行为观察一篇文章通常不止一张图片那么连续粘贴多张图片时脚本会被触发多次吗我特意测试过。我在一秒钟内连续CtrlV粘贴了5张截图Typora的处理方式是逐张调用脚本命令也就是说短时间内脚本被调用了5次。从结果看5张图片全部成功上传而且图片URL按顺序插入到了文档对应的位置没有任何错位。不过这里也暴露了一个潜在问题如果你的网络较慢或者博客园接口响应较慢连续上传时几张图片的URL返回顺序和粘贴顺序可能不一致。Typora在插入URL时是按脚本执行完成的先后顺序来的不是按图片路径顺序。一个经验是如果一篇文章图片特别多粘贴完一批之后等两三秒再继续粘贴给脚本留出执行时间这样插入的URL顺序更稳。6. 实战中遇到的坑和排查技巧6.1 上传失败脚本返回“code非0”或“msg异常”这种情况90%以上是Cookie的问题。博客园的Cookie里真正影响图片上传鉴权的主要是登录后的用户凭证字段如果你是从浏览器复制的完整Cookie一般没问题。但要注意Cookie字符串里的引号和空格建议你复制的时候把整个Cookie头原样粘到脚本里不要手动改动。还有一个容易被忽略的点博客园不同子域名之间的Cookie是共用的比如www.cnblogs.com和upload.cnblogs.com它们的会话是互通的你只需要登录一次就行。但是如果你开了无痕窗口、或者过了较长时间登录态失效就需要重新登录获取新Cookie。6.2 脚本单测正常Typora里却不生效这个问题的排查思路是两步第一步去Typora的开发者工具里看Console日志第二步看脚本有没有在系统里留下可查的执行痕迹。脚本单测正常但Typora里不生效通常有三种情况自定义命令的路径写错了Typora压根没找到脚本文件。检查路径里的反斜杠和双引号。Typora的“上传图片应用”没有选“自定义命令”或者选了但命令框是空的。Typora的安全或者环境变量问题导致python或powershell不可识别。这种情况下建议在命令里写全解释器的绝对路径比如C:\Users\你的用户名\AppData\Local\Programs\Python\Python312\python.exe D:\tools\upload_image.py ${imagePath}。6.3 图片URL正常但Typora里渲染显示成“图片加载失败”如果Typora文档里的图片URL已经变成了https://img2023.cnblogs.com/...但Typora里显示加载失败这大部分情况下不是你脚本的问题而是网络环境的缘故。有些本地方网络访问博客园图片域名会超时或者Typora的缓存里没有这个图片。点击图片右键选择“重新载入图像”或者把文档关闭重新打开一般就能恢复。我之前遇到过一种更隐蔽的坑我上传的是GIF动图接口返回URL之后Typora里显示正常发布到博客园之后也能动但是动图在Typora里第一次打开的时候要加载好几秒。这是因为动图文件体积比较大博客园服务器首次访问时要做同步处理后续访问就快多了。6.4 上传成功但博客园文章里图片不显示这种情况我遇到过一次不是上传的问题而是文章本身的问题。如果你在博客园的编辑器里用的是“Markdown”编辑模式图片是以URL形式嵌在源码里的应该没问题。但如果你切换到“可视化”编辑模式再切回来Typora插入的那种标准Markdown图片语法有可能被编辑器改写成HTML标签处理不当就可能显示异常。我的建议是在博客园发布Markdown文章时始终用Markdown编辑模式不要频繁切换编辑模式。如果需要预览效果用文章预览功能而不是切模式。7. 进阶玩法让这套方案更顺手7.1 图片自动压缩再上传博客园对单张图片大小有限制超过一定大小会拒绝上传我印象中大约是几MB的级别。如果你经常贴程序运行截图截图文件通常不大但如果你截的是高分屏的图一张就可能到5MB以上。解决思路是在脚本里加上压缩逻辑上传前先把图片处理到合适的尺寸和质量。Python里用Pillow库可以轻松实现代码逻辑是打开图片 - 判断宽高是否超过阈值比如1600px - 超过就等比缩放 - 保存到临时目录 - 用压缩后的文件去上传。from PIL import Image def compress_image(src_path, max_width1600, quality85): img Image.open(src_path) if img.width max_width: ratio max_width / img.width new_size (int(img.width * ratio), int(img.height * ratio)) img img.resize(new_size, Image.LANCZOS) temp_path src_path .compressed.jpg img.convert(RGB).save(temp_path, JPEG, qualityquality) return temp_path这样处理之后一张5MB的截图通常能压到200KB左右上传快、加载也快。7.2 上传失败时自动降级为本地路径Typora的自定义命令有个特性如果脚本的输出不是有效的URLTypora会提示上传失败但不会回退到本地路径。也就是说一旦上传失败你文档里的图片仍然指向./assets/xxx.png这个相对路径这时候如果你没注意就复制去发布图片照样挂。一个稳妥的做法是让脚本在失败时直接输出一个空字符串这样Typora会保留原路径并弹出上传失败提示。同时你要养成习惯每次发布前先看一眼文档里的图片是不是全是https://开头的。我有时候会写一个简单的脚本扫描当前Markdown文件里所有图片路径只要发现有本地路径就高亮提示不过这个属于额外自动化了做不做看个人需求。7.3 批量回传历史文章里的本地图片如果你之前已经用Typora写了很多篇文章图片全是本地相对路径这些历史文章的图片要怎么批量处理我提供一个思路用Python脚本遍历一个目录下所有Markdown文件用正则表达式提取所有![](...)里的图片路径匹配到本地文件就上传然后把Markdown里的路径替换成新的URL。这样就实现了一次性批量搬图。批量处理时要注意两点一是上传接口的调用频率不能太高适当加上延时比如每张图片上传后sleep 0.5秒避免被服务端限流二是替换前备份原文件万一有异常还能恢复。8. 方案评估这套东西到底值不值得折腾最后说点掏心窝的话。这套方案本身不复杂核心就是“Typora的自定义命令 一个几十行的上传脚本 博客园的免费图床”。它解决的是最让人头疼的Markdown图片托管问题付出的成本是偶尔更新一次Cookie以及几十分钟的配置时间。我个人用了小一年最大的感受是写文章这件事终于可以“所见即所得”了。截图、粘贴、继续写图片自动进入博客园整个流程行云流水文章写完直接复制发布没有任何中间环节。对比以前那种先写完再补图的痛苦流程体验提升不是一点半点。如果你只是偶尔发一篇文章手动传图也可以接受那可以不折腾。但如果你像我一样每周都要在博客园更新几篇文章每篇动辄十几二十张截图这套方案省下的时间和精力绝对值得投入。最后再提醒一句Cookie属于账号敏感信息你的脚本文件注意不要传到公开仓库里建议把Cookie单独放在一个配置文件里并且把这个文件加入Git的忽略列表。细节做到位方案才能真正长久稳定地跑下去。
返回列表