由于我日常习惯用 Typora 来写笔记和博客,文章中的图片保存在本地,在发布文章到 cnblog 之前,希望能有一个自动化工具可以自动上传文章引用的图片到 cnblog,获取图片链接,替换掉文章内容中的图片本地链接。
调研了一番之后,发现主要有以下两类方法:
- 基于 MetaWeblog api 的方法,可参考使用MetaWeblog API实现通用博客发布 之 API测试
- 基于 http 请求的方法,模拟请求发送给 cnblog,可参考如何利用Python实现图片自动上传博客园/博客园图床API外链
第一种方法优势在于 cnblog 官方支持(也就是说 api 不会变化),除了 cnblog,其他大部分博客站都支持这种 api,方便未来博客迁移同步。另外可以参考的文章很多,缺点是这些文章年代久远,有很多坑。ORZ
第二种方法优势在于只需要简单模拟 http 请求,缺点在于万一哪天 cnblog 把请求字段改了,这工具就歇菜了。
最终,我决定采用第一种方法。在 python 官方文档稍微了解了下 xmlrpc 的用法之后(client文档,server文档),写了一段代码,想测试下blogger.getUsersBlogs
接口:
import xmlrpc.client as xrc
if __name__ == '__main__':
svr = xrc.ServerProxy('https://rpc.cnblogs.com/MetaWeblog/jy5380')
usrInfo = svr.blogger.getUserBlogs('jy5380',
'jy5380',
'这里填的是MetaWeblog令牌')
print(usrInfo[0])
但是运行之后报如下错误:
Traceback (most recent call last):
File "C:\Users\JiangYuan\Tools\cn_blog_auto\main.py", line 17, in <module>
usrInfo = svr.blogger.getUserBlogs('jy5380',
File "C:\Users\JiangYuan\AppData\Local\Programs\Python\Python39\lib\xmlrpc\client.py", line 1122, in __call__
return self.__send(self.__name, args)
File "C:\Users\JiangYuan\AppData\Local\Programs\Python\Python39\lib\xmlrpc\client.py", line 1464, in __request
response = self.__transport.request(
File "C:\Users\JiangYuan\AppData\Local\Programs\Python\Python39\lib\xmlrpc\client.py", line 1166, in request
return self.single_request(host, handler, request_body, verbose)
File "C:\Users\JiangYuan\AppData\Local\Programs\Python\Python39\lib\xmlrpc\client.py", line 1182, in single_request
return self.parse_response(resp)
File "C:\Users\JiangYuan\AppData\Local\Programs\Python\Python39\lib\xmlrpc\client.py", line 1354, in parse_response
return u.close()
File "C:\Users\JiangYuan\AppData\Local\Programs\Python\Python39\lib\xmlrpc\client.py", line 668, in close
raise Fault(**self._stack[0])
xmlrpc.client.Fault: <Fault 1: 'Failed to handle XmlRpcService call'>
Process finished with exit code 1
百思不得其解,苦苦查询无果,我暂时放弃这个测试。
既然 MetaWeblog api 实际上是把请求内容封装成 http 请求发送给 cnblog 后台,那么我可以在 Postman 工具中模拟这个请求,这次依然是从简单的blogger.getUsersBlogs
接口入手,成功拿到了回包:
这时候我转回去研究 python xmlrpc 请求为何失败,断点看报错是出现在 cnblog 后台的回包里面,也就是说请求本身是成功的,然后我仔细检查了下代码,发现接口处少打了一个字幕s
, 给自己跪了ORZ。
getUserBlogs -> getUsersBlogs
修改后,能正确请求到信息了。
C:\Users\JiangYuan\Tools\cn_blog_auto\venv\Scripts\python.exe C:/Users/JiangYuan/Tools/cn_blog_auto/main.py
{'blogid': '*****', 'url': 'https://www.cnblogs.com/jy5380/', 'blogName': '可能会下雨'}
简单接口跑通之后,自然是试验下图片上传接口,在这里可以查到 cnblog 提供给我的所有接口,图片上传是用下边这个metaWeblog.newMediaObject
:
请求参数里的file
字段是一个结构体:
依然先是用 Postman 来模拟一下请求:(需要先把图片转成 base64 字符串)
返回了一个地址,就是下边这张照片,去年在大梅沙奥特莱斯拍的。
现在把这个过程写成 python:
# 输入markdown文件路径/path/to/test.md,拷贝其内容,在同一目录下生成/path/to/test_cnblog.md
# 读取/path/to/test_cnblog.md文件中的图片,上传至cnblog获得图片url,并替换图片的本地相对路径为此url
import sys
import xmlrpc.client as xrc
import os
import re
imgExt2Type = {
'.bmp': 'image/bmp',
'.cmx': 'image/x-cmx',
'.cod': 'image/cis-cod',
'.dib': 'image/bmp',
'.gif': 'image/gif',
'.ief': 'image/ief',
'.ico': 'image/x-icon',
'.jpg': 'image/jpeg',
'.jfif': 'image/pjpeg',
'.jpe': 'image/jpeg',
'.jpeg': 'image/jpeg',
'.pbm': 'image/x-portable-bitmap',
'.ppm': 'image/x-portable-pixmap',
'.pnm': 'image/x-portable-anymap',
'.png': 'image/png',
'.pgm': 'image/x-portable-graymap',
'.rgb': 'image/x-rgb',
'.ras': 'image/x-cmu-raster',
'.tif': 'image/tiff',
'.tiff': 'image/tiff',
'.xbm': 'image/x-xbitmap',
'.xpm': 'image/x-xpixmap',
'.xwd': 'image/x-xwindowdump'
}
def uploadImg(imgAbsPath):
svr = xrc.ServerProxy('https://rpc.cnblogs.com/metaweblog/jy5380')
name = os.path.basename(imgAbsPath)
_, ext = os.path.splitext(imgAbsPath)
with open(imgAbsPath, 'rb') as f:
file = {
'bits': f.read(),
'name': name,
'type': imgExt2Type[ext]
}
resp = svr.metaWeblog.newMediaObject('jy5380',
'jy5380',
'这里填的是MetaWeblog令牌',
file)
return resp['url']
if __name__ == '__main__':
argLen = len(sys.argv)
if argLen < 2:
print('[ERROR] no parameters found')
exit(1)
targetMDPath = sys.argv[1]
if not os.path.exists(targetMDPath):
print('[ERROR] target markdown file not exist')
exit(1)
with open(targetMDPath, 'r', encoding='utf-8') as md:
article = md.read()
images = re.findall('\\!\\[.*?\\]\\((.*?)\\)', article)
localImg2NetImg = dict()
# 排除掉网络图片
images = [i for i in images if not re.match('((http(s?))|(ftp))://.*', i)]
print(f'{len(images)} local images founded')
print(*images, sep='\n')
# 上传图片
for img in images:
imgAbsPath = os.path.join(os.path.dirname(targetMDPath), img)
print(f'uploading img {imgAbsPath}')
url = uploadImg(imgAbsPath)
localImg2NetImg[img] = url
print(f'uploading img finished. Got {url}')
# 替换md中的图片链接
for localImg, netImg in localImg2NetImg.items():
article = article.replace(localImg, netImg)
outputFile = os.path.join(os.path.dirname(targetMDPath), os.path.basename(targetMDPath) + '.cnblog')
with open(outputFile, 'w', encoding='utf-8') as outputMD:
outputMD.write(article)
成功替换:
参考文章列表:
- MetaWebLog API — 一个多平台文章同步的思路
- XML-RPC 简单理解与博客园的MetaWeblog协议
- 博客园markdown上传文件及图片
- MetaWeblog API中文说明
- XML-RPC Specification
- 如何利用Python实现图片自动上传博客园/博客园图床API外链
- 使用metaweblog API实现通用博客发布 之 API测试