ARTICLE DETAIL

资讯详情

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

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

用Typora+博客园实现Markdown图片自动上传的完整方案 用Typora写Markdown再把文章贴到博客园这是很多技术博主的工作流。可图片呢粘贴到Typora里的截图默认存在本地等你把Markdown正文复制到博客园编辑器图片路径还指向C盘某个临时目录结果就是博文发出去图全裂了。这个问题的常规解法是手动一张张传到博客园相册再贴链接或者挂第三方图床但前者烦后者不稳定。这篇就分享一套我用了很久的方案Typora Cnblog 实现 Markdown 图片自动上传写文章时正常粘贴、正常预览发布时图片已经自动躺在博客园自己的图床上了。如果你也在写CSDN、博客园或其他支持MetaWeblog协议的博客这套思路可以直接改改就复用。1. 核心思路Typora的图片上传服务和博客园的MetaWeblog接口1.1 Markdown图片的三条路先说清楚Markdown图片的三种归宿这决定了你后面会踩多少坑。第一种是本地相对路径。这是Typora默认行为图片和md文件放在同一个目录或某个子目录里。好处是纯本地、离线可用坏处是一旦把Markdown内容复制到网页编辑器这些路径就全废了。因为博客园服务器上根本没有你电脑里的D:\notes\img\xxx.png。第二种是第三方图床。你把图片传到某个云服务得到一个公网URL然后Markdown里就写这个URL。这类服务很多免费的有sm.ms、路过图床稳定但随时可能关停付费的有七牛、又拍云、阿里OSS需要配置Bucket、CDN、HTTPS证书对只想安静写博客的人来说有点重。而且免费图床的通病是用的时候好好的过半年你发现文章图全裂了那真是灾难。第三种就是用博客园自己提供的图片上传能力。博客园本身就缺一个在编辑器中直接插入图片的人性化路径但它底层暴露了一个MetaWeblog API专门用来做内容发布和媒体上传。通过这个接口把图片传上去拿到的URL就在博客园的域名下稳定性和寿命都有保障而且不占你个人资源。这才是这篇要讲的重点。1.2 Typora的图片上传是怎么工作的很多人天天用Typora写笔记却没注意过它有个上传图片的功能。它不只是编辑器还是一个客户端能在你把图片粘贴进文档时把本地图片转成远程URL。核心入口在偏好设置的图像面板里。Typora的上传服务支持几种方式PicGo.app、uPic、iPic、Custom Command等。其中Custom Command自定义命令是自由度最高的——你指定一个可执行程序Typora会把图片路径当作参数传给它然后读取这个程序的标准输出把输出的每一行当成一个图片URL回填进Markdown文档里。换句话说你只需要写一个能接收图片路径、调用博客园API上传、再输出URL的脚本就能把Typora和博客园串起来。这就是整套方案的核心逻辑。1.3 为什么用MetaWeblog API而不是现成图床博客园的MetaWeblog API是一个XML-RPC接口地址格式是固定的https://rpc.cnblogs.com/metaweblog/{你的博客应用名}这个接口提供了标准的metaWeblog.newMediaObject方法用来上传图片、文件等媒体对象。调用方法时传入博客ID、用户名、密码和一个包含文件内容的对象它会返回一个JSON结构里面带url字段就是图片在博客园服务器上的最终访问地址。选择这个方案有几个很现实的原因图片放在自己博客的名下相当于博客园帮你托管不需要额外注册任何图床服务。很多第三方图床在上传时要求携带Referer或TokenTypora的Custom Command里处理起来麻烦而MetaWeblog接口只需要用户名密码。博客园对所有文章图片都有比较稳定的带宽和防盗链策略直接用它的URL比你找个冷门图床可靠得多。唯一要接受的代价是你得写一个小脚本并且把它配置好。但这是一劳永逸的事。2. 准备工作账号、路径和工具一次到位2.1 从博客园后台找到你的BlogApp动手写脚本前先把博客园的配置信息找齐。登录博客园进入后台管理页面。很多人找不到博客应用名在哪其实它就在博客后台的设置页面里。功能说明博客应用名BlogApp是博客园分配给你的一个唯一标识一般是一串英文或数字也会出现在你博客的二级域名里。MetaWeblog接口要用的blogid就是这个值。这时候你需要确认三样东西博客应用名类似cnblogs_test这种。登录用户名就是你平时登录博客园的账号名不是显示昵称。登录密码就是博客园账号的登录密码。把这三样记下来后面写脚本时要填进去。注意密码里如果有#、、$这类特殊字符XML-RPC协议本身对字符串是安全传输的直接填就行不用担心Shell转义问题这一点比命令行方式省心。2.2 Typora的安装与图像偏好设置Typora官方版本安装完成之后打开偏好设置Windows下快捷键Ctrl逗号macOS是Command逗号找到图像分类。你会看到这些选项插入图片时建议选复制图片到当前目录或者上传图片这决定了粘贴图片时Typora的默认行为。上传服务设定这里有几种模式我们要选Custom Command。上传命令这里填入你的脚本调用命令。如果你是第一次用Custom Command建议先不要急着配置复杂参数先用系统自带的Python跑通一个最简单的脚本再逐步增加逻辑。这样排错会轻松很多。2.3 Python环境与依赖脚本只需要Python标准库不需要pip install任何第三方包。核心就三个模块xmlrpc.client用来调MetaWeblog APIbase64用来编码文件二进制其实xmlrpc.client.Binary已经封装了这一步但心里要有这个概念mimetypes用来判断图片类型。Windows用户建议直接去官网下载Python 3安装包安装时勾选Add Python to PATHmacOS用户一般系统自带Python3直接命令行里试一下python3 --version能输出版本号就行。Linux更不用说apt install python3或yum install python3都是一行命令的事。一个额外的建议脚本文件路径和存放图片的路径都别用中文更别带空格。不是脚本不支持而是Typora在调用Custom Command时对路径的处理在不同系统上有差异中文路径偶尔会出编码问题。我把脚本固定放在C:\Tools\cnblog_upload.py一次配置再也没动过。3. 真正的核心一个能跑的Python上传脚本3.1 脚本整体逻辑整个脚本的运行逻辑其实很清晰读取命令行参数这些参数就是Typora传进来的图片路径。逐个检查图片文件是否存在。为每张图片生成一个避免重名的文件名。读取图片二进制内容封装成XML-RPC的Binary对象。调用metaWeblog.newMediaObject上传。把返回的url逐行打印到标准输出。Typora读取这些输出自动替换文档中的图片路径。这里需要注意Typora可能一次传入多张图片比如一次性粘贴了5张截图它会把这5张图片的路径都作为命令行参数传给你的脚本。所以脚本里一定要用sys.argv[1:]来取全部路径不能只取第一个。3.2 完整代码与关键解释#!/usr/bin/env python3 # -*- coding: utf-8 -*- import sys import os import time import mimetypes import xmlrpc.client # 配置区改成你自己的信息 BLOG_APP your_blog_app # 博客园后台的应用名 USERNAME your_username # 博客园登录用户名 PASSWORD your_password # 博客园登录密码 API_URL fhttps://rpc.cnblogs.com/metaweblog/{BLOG_APP} def upload_one_image(file_path): if not os.path.isfile(file_path): print(f[error] file not found: {file_path}, filesys.stderr) return None # 生成文件名原名_时间戳.后缀防止同名覆盖 base_name os.path.basename(file_path) name_part, ext_part os.path.splitext(base_name) remote_name f{name_part}_{int(time.time())}{ext_part} # 判断MIME类型没识别到默认png mime_type mimetypes.guess_type(file_path)[0] or image/png # 读取图片二进制 with open(file_path, rb) as f: bits_data f.read() # 连接博客园接口 server xmlrpc.client.ServerProxy(API_URL) media_object { name: remote_name, type: mime_type, bits: xmlrpc.client.Binary(bits_data), } # 调用接口上传 result server.metaWeblog.newMediaObject( BLOG_APP, USERNAME, PASSWORD, media_object ) return result.get(url) if __name__ __main__: image_paths sys.argv[1:] for path in image_paths: url upload_one_image(path) if url: print(url) else: print(f[error] upload failed: {path}, filesys.stderr)几个容易被忽略的技术点xmlrpc.client.Binary(bits_data)是必须的。XML-RPC协议要求二进制内容放进base64这样的标签里Binary对象专门负责这件事不能直接放原始bytes。result.get(url)拿到的就是可访问的图片地址一般是https://images.cnblogs.com/xxxx/yyyy.png这种格式。日志信息全部写到sys.stderr不要混到标准输出里。Typora只认标准输出的URL一旦混入其他文字它就会把整行当成URL图片路径就乱了。文件重命名是个被我加了又减、减了又加的功能。一开始我不想动文件名后来发现如果不同时间上传两张同名截图博客园服务器上可能出现覆盖或缓存问题导致文章图片显示成旧图。加上时间戳之后文件名唯一性有了保障。3.3 配置Typora的Custom Command脚本写好后回到Typora的图像偏好设置。在上传服务设定下拉框里选择Custom Command然后在Command输入框里填命令。Windows下的标准写法是python C:\Tools\cnblog_upload.pymacOS或Linux下是python3 /Users/你的名字/tools/cnblog_upload.py注意如果你的Python不在PATH里要用完整路径。比如Windows装的是Microsoft Store版Python路径可能是python如果上面命令回车后提示不是内部或外部命令就用完整路径。可以先在命令行里手动执行一下确认命令能把图片传上去再回Typora配置。配置完成后Typora里会有一个验证图片上传之类的按钮点击它会弹窗显示测试结果。这个测试会找一张本地图片走一遍上传流程方便你确认脚本能正常返回URL。如果测试失败它会显示脚本的标准错误输出这是排错的第一手信息。3.4 一次粘贴多张图片的情况在实际写作中截图经常是一堆一堆地来。比如你要写一篇软件教程可能一次性粘贴了8张界面截图。Typora对这种情况的处理是当你粘贴或者拖拽多张图片到文档时只要触发了上传设置它会把所有图片路径一次性传给脚本。这时候脚本的循环处理逻辑就体现出作用了它会逐个上传逐个打印URL。Typora拿到返回的多行输出后按照顺序依次替换文档里的图片引用。只要你的脚本输出顺序和输入路径顺序一致多图替换就不会错位。我在脚本里额外加了一个小习惯如果某张图上传失败不要让整个脚本崩掉而是记录错误并继续处理下一张。这样即使某张图有问题其他图也能正常上传最后你只要回头检查报错的那张就行。4. 踩坑实录常见问题与排查技巧4.1 认证失败到底是谁的问题在博客园MetaWeblog接口使用初期认证失败真的是最高频的报错。我整理了一个排查清单按顺序检查一遍基本都能解决。排查项检查内容说明BlogApp是否正确后台设置里的应用名不是博客标题帖子里填错一个字符就会404用户名是否是登录名不能用昵称必须是可以登录的账号昵称往往不能通过API认证密码是否有误大小写、特殊字符是否原样填写直接在Python文件里硬编码别在Shell里传参是否触发了安全验证如果账号开了登录双重验证API可能被限制遇到这种情况可以在博客园后台关掉双验再试一次网络是否能访问rpc域名直接浏览器访问API地址看是否有响应被墙的步骤不在讨论范围但确认连通性很重要最常见的坑是BLOG_APP填成了博客标题。比如你的博客叫张三的技术杂谈应用名可能是zhangsan_blog这两者完全不一样。去博客后台设置页面找到那个写着博客应用名BlogApp的地方复制它。4.2 图片URL返回成功但浏览器打不开这个坑相对隐蔽。有次我测试脚本返回了一个URL控制台也打印出来了但放浏览器里访问却是403。后来发现是文件名里的空格和中文字符引起的URL编码问题。博客园的静态资源服务器对特殊字符处理非常严格file name这种带空格的原始文件名即使result.get(url)里看着正常实际访问时也会出问题。所以后来我在脚本里做了两件事一是重命名时把空格替换成下划线二是如果原始文件名是中文就用一个file_时间戳的英文文件名。上面代码里重命名的部分就是这么来的。4.3 Typora调用脚本没反应如果你在Typora里点上传半天没有反应大概率不是脚本逻辑问题而是命令没有真正被执行。这时先抛开Typora自己在命令行里手动模拟一下python C:\Tools\cnblog_upload.py D:\test.png如果命令行能正常输出URL那问题就出在Typora和命令的交互上。常见原因有命令路径含有空格又没有加引号包裹。Typora执行命令时只是简单拼接路径里有空格就会中断。Python不是系统PATH里的默认解释器。建议把Python的完整路径写进命令比如C:\Users\你的名字\AppData\Local\Programs\Python\Python312\python.exe C:\Tools\cnblog_upload.py。Typora版本较旧Custom Command功能有bug。去官网更新到最新版。4.4 输出干扰标准输出只能有URL这是脚本调优里最值得强调的一点。有些同学喜欢在Python里加print(正在上传...)来调试但这行字一旦出现在标准输出里Typora就会把它当成图片URL填进Markdown。文章里插入了莫名其妙的一行正在上传...那真是灾难。所以我的脚本里所有调试信息都写到sys.stderr。Typora对这种错误输出是有专门处理通道的它会把stderr的内容显示在错误对话框里但不影响URL解析。这是排查问题的最优解也建议你把日志写得更详细一些。4.5 图片质量与上传速度的平衡用这个脚本上传默认是原图上传。博客园服务器本身会对图片做一定的压缩处理但如果你截图动辄好几MB上传速度会明显变慢。我的一个实践是在上传前用Python的Pillow库判断图片大小如果超过1MB就压缩到合理尺寸再传。不过这个属于进阶优化有很多细节值得单独说明我在下一节展开。5. 进阶玩法压缩、监控与扩展5.1 上传前自动压缩大图截图文件大一方面上传慢另一方面博客园回源加载也慢。我用Pillow做了个压缩逻辑思路很简单from PIL import Image MAX_FILE_SIZE 1024 * 1024 # 1MB def compress_if_needed(file_path): if os.path.getsize(file_path) MAX_FILE_SIZE: return file_path img Image.open(file_path) if img.mode RGBA: img img.convert(RGB) # WebP格式体积小、兼容性不错 new_path file_path .compressed.jpg img.save(new_path, JPEG, quality80, optimizeTrue) return new_path这个逻辑接到上传函数前面如果图片大于1MB就先压缩再上传。压缩后图片可能从2MB降到300KB上传速度立竿见影。注意压缩后的临时文件用完要删不然本地磁盘会堆一堆垃圾文件。不过别迷信压缩码率对截图这类平铺内容影响不大反而保存成JPEG时文字边缘可能出现毛刺。如果你是技术教程、代码截图为主建议选择PNG或WebP格式保持文字清晰度追求的是体积和清晰度之间的平衡。5.2 用脚本做上传监控和失败重试API调用偶尔会超时尤其是网络波动的时候。我后来在脚本里加了简单的重试机制上传失败后等待2秒再试一次最多重试3次。也可以把失败次数和文件路径记录在一个日志文件里方便事后统计。import time MAX_RETRY 3 def upload_with_retry(file_path): for attempt in range(MAX_RETRY): try: url upload_one_image(file_path) if url: return url time.sleep(2) except Exception as exc: print(f[error] attempt {attempt 1} failed: {exc}, filesys.stderr) time.sleep(2) return None这个改动让脚本在偶发的网络抖动下不会直接中断很多次我在批量上传时都靠它在后台默默重试成功的。5.3 扩展到其他博客平台博客园的MetaWeblog接口是标准XML-RPC而很多博客系统也支持同样的协议。比如某些WordPress站点、一些开源博客系统它们都暴露了类似的接口区别只在于接口地址和认证方式。如果你用的其他平台刚好也支持MetaWeblog脚本里的API_URL和newMediaObject这两个地方改一改基本就能复用。WordPress的接口一般是/xmlrpc.php上传方法也是metaWeblog.newMediaObject返回结构略有差别但大同小异。这个方法也可以反过来用如果你同时维护多个平台比如博客园、WordPress、自建博客可以写一个脚本根据参数上传到指定平台。我在一次整理旧文章时就这么干过一篇文章的图片同时传到了三个系统效率很高。5.4 把脚本变成通用的CLI工具脚本打磨到一定程度后我觉得它不该只属于Typora。于是给它加了一个简单的命令行入口支持直接指定一个目录批量上传目录下所有图片。python cnblog_upload.py --dir D:\screenshots这个功能在处理历史文章时很有用。有些老文章里的图片就躺在某个文件夹里手动一张张粘贴到编辑器费时费力用这个命令一次性上传再把输出整理成Markdown图片引用格式十几分钟就能把一篇文章的图片全部复活。我的体会是这些小工具一旦写顺了越用越顺手。它解决的不只是粘贴图片这一个动作而是把整个写作发布链路串了起来。这套方案我用了一年多最踏实的一点是图片永远和我的博客账号绑定不用惦记哪个图片床会不会跑路。如果你一天只发一篇图少的文章手动传图可能也无所谓但像我这种每周两三篇、经常附十几张截图的让脚本处理这一块能省的时间真不是一点半点。最后提醒一句脚本里的密码是明文保存的注意别把带密码的版本传到公开仓库如果要分享脚本一定把配置区拆成单独的配置文件或者用环境变量代替。
返回列表