关注这个项目的最新进展:Obsidian Releases Tools Development Logs-CSDN博客
0x01:工具简介
Obsidian Publish To CSDN,是我开发的一款将 Obsidian 内容发布到 CSDN 的辅助工具,暂时还没有完成全自动化,有点呆呆的,但是也给我省了不少事。
Obsidian 笔记发布的时候,最令人头大的就是里面的双链图片了,因为 CSDN 他不认(我也懒得做图床啥的,本地笔记,如果和云上牵扯太大,感觉还不如用 “语雀”)。所以经常会出现,文字复制上去了,但图片还得一张一张的去 Obsidian 存放图片的位置,根据图片名称查找图片,然后再一张张上传到 CSDN 的情况,十分的麻烦,且不优雅。
那么,我开发的这个脚本,就是为了这种情况而生。
它可以快速提取出,Obsidian 笔记中的图片,并且顺带生成一份更适合 CSDN 发布的 Markdown 笔记。
0x02:工具使用
0x0201:工具初始化配置
复制脚本代码后,需要修改的内容仅一项,就是 OBSIDIAN_IMAGE_PATH,即你在 Obsidian 中设置的图片存储路径,这里的路径建议直接上绝对路径(以免你移动脚本,导致找不到图片),设置好后的结果如下:
后面的路径就是我 Obsidian 存放图片的位置:
0x0202:工具使用流程
1. 运行脚本
脚本的使用方法也很简单,直接运行就行,它会让你输入待发布的 Obsidian Markdown 文档路径:
这里我以今天发布的一个笔记为例,打开存放笔记的文件夹,选中待发布的笔记,然后右击,选择“复制笔记地址” 或者 Ctrl+Shift+C
:
将复制好的地址黏贴进脚本的提示框中,点击 Enter 即可运行脚本:
脚本运行成功后会在脚本同文件夹下生成一个 CSDN_PUBLISH_FOLDER 的文件夹,里面就是更方便发布的格式:
2. 发布至 CSDN
打开 CSDN 的文章发布页面,我使用的是富文本编辑器,可不是 Markdown 编辑器哦(虽然笔记是 Markdown 格式的)。
打开 00 - HackerBar - README.md
文档(就是脚本处理好后的你想发布的那个文档),可以看到原本 Obsidian 中的图片都被以序号编号好了(我是使用本地的 Typora 打开的 Markdown 文件,使用 Typora 复制文章到 CSDN 可以保留格式):
这个时候Ctrl + A
选中文章所有内容,然后Ctrl + C
复制到 CSDN 发布页面上,其结果如下,可以看到格式都保留的很好(这个得归功于 Typora,不然也可以使用 CSDN 的 Markdown 编辑模式):
但是图片还没贴上,这个时候,我们打开 images 文件夹,将图片依次拖到页面上即可(虽然还是比较麻烦,但比自己一个个搜索好多了):
0x03:工具源码
最后,附上工具源码(脚本中用到了shutil
包,可能需要自己下载一下,我本地运行的环境是 python 3.9.6)。
"""
-------------------------------------------------
File Name: Obsidian Publish To CSDN
Description : 此脚本会提取出 Obsidian 笔记中的双链图片,生成一份可以直接复制进 CSDN 的笔记 + 笔记图片文件夹,
节省您搜索图片的时间
Author : Blue17
Date: 2024/08/18
-------------------------------------------------
Upgrading plan:
Change Activity:
2024/08/19: 修复了当笔记中的代码块内同时出现 ``` 与双链图片格式时,会识别出错误的图片的问题。
-------------------------------------------------
"""
import re
import os
import shutil
# ============================================== Config
# Obsidian 存放图片资源的文件夹路径(绝对地址)
OBSIDIAN_IMAGE_FILE_PATH = r"G:\06 - ObsidianNotes\04 - Resource\0401 - Obsidian Notes Resource\ObsidianNotesAttachments"
# 适合 CSDN 体质的 Markdown 文件存储路径(到文件夹),默认为脚本同级文件夹中
RESULT_FOLDER = "CSDN_PUBLISH_FOLDER"
# ==============================================
def load_md_file(file_path):
"""
导入 Obsidian Markdown 文件
@file_path:Obsidian Markdown 文件路径
"""
if os.path.exists(file_path):
with open(file_path, "r", encoding="utf-8") as f:
return f.readlines()
else:
print(f"[+] Error:{file_path} 文件不存在!")
def save_md_file(file_name, content):
"""
保存 md 文件
@param:file_name,保存的文件名
@param: content,保存的文件结果
"""
# 查看结果存放的文件夹是否存在,不存在就创建
if os.path.exists(RESULT_FOLDER) == False:
# 创建 RESULT_FOLDER 文件夹
os.mkdir(RESULT_FOLDER)
print(f"[+] 存放结果的文件夹 {RESULT_FOLDER} 创建成功!")
# 保存 md 文件
save_path = os.path.join(RESULT_FOLDER, file_name)
with open(save_path, "w", encoding="utf-8") as f:
f.write(content)
print(f"[+] CSDN Markdown 文件保存成功,保存路径:{save_path}")
def copy_obsidian_image(image_path, image_save_folder=os.path.join(RESULT_FOLDER, "images")):
"""
将 Obsidian 中的 IMAGE 复制到指定文件夹中
"""
# 检查保存图片的文件夹是否创建好,没有则创建
if os.path.exists(os.path.join(image_save_folder)) == False:
os.mkdir(image_save_folder)
print(f"[+] 存放图片的文件夹 {image_save_folder} 创建成功!")
# 将图片复制到图片文件夹中
for id, name in image_path.items():
src_path = os.path.join(OBSIDIAN_IMAGE_FILE_PATH, name)
dst_path = os.path.join(
image_save_folder, f"IMAGE_{id:03d}."+name.split(".")[-1])
try:
shutil.copy(src_path, dst_path)
print(f"[+] IMAGE_{id:03d} - 复制成功!")
except Exception as e:
print(e)
def main(obsidian_file_path):
# 读取指定的 Obsidian Markdown 笔记内容
file_content_lists = load_md_file(obsidian_file_path)
# 一行一行的处理文档内容
image_id = 1
image_path = {} # 存放图片路径
line_no = 0
while line_no < len(file_content_lists):
# 检查当前行是否是代码块的起始位置
code_block_sign = re.match(
r"((>{0,} {0,}){0,})( {0,}`{3})", file_content_lists[line_no]) # 检查代码块 ``` 起始标志
if code_block_sign != None:
# 当前位置是代码块的起始位置 - 找到代码块的结束位置
# 判断代码块是否在引用块中
if '>' in code_block_sign.group():
# 当前代码块处于引用块中,引用块中的代码块可以不设置结束标志,而直接以引用块的结尾作为结尾
while line_no < len(file_content_lists):
line_no += 1
if ">" != file_content_lists[line_no][0]: # 已经不在引用块内部了
break
new_sign = re.match(
r"((>{0,} {0,}){0,})( {0,}`{3})", file_content_lists[line_no]) # 检查代码块结束标志
if new_sign != None and new_sign.group() == code_block_sign.group(): # 如果匹配到了结束的符号
line_no += 1
break
else:
# 当前代码块不在引用块中
while line_no < len(file_content_lists):
line_no += 1
# 检查普通代码块 ``` 起始标志
new_sign = re.match(
r"((>{0,} {0,}){0,})( {0,}`{3})", file_content_lists[line_no])
if new_sign != None and new_sign.group() == code_block_sign.group(): # 匹配了代码块结尾标识
line_no += 1
break
else:
# 当前位置不是代码块的位置,还需要防范 `` 行内代码块
content = file_content_lists[line_no] # 当前行文本
content_split_result = re.split(
r"(?<!\\)`", content) # 负向后查找,匹配 ` 前不存在 \ 的
# 分割后的内容:[内容1,代码块,内容2,代码块,内容3]
for id, content in enumerate(content_split_result, start=1):
if (id % 2 == 1):
# 提取文章中的图片
image_file_lists = re.findall(r"!\[\[(.*?)\]\]", content)
# 将图片保存到 image_path 中,并修改图片格式为 ![[IMAGE_ID]]
for image_file in image_file_lists:
# 修改图片格式为 ![[IMAGE_ID]]
content_split_result[id - 1] = content_split_result[id - 1].replace(
image_file, f"IMAGE_{image_id:03d}")
# 将图片名称保存到 image_path 中,| 是 Obsidian 中用来调整图片大小的,# 号是扩展语法
image_path[image_id] = os.path.basename(image_file).split("|")[0].split("#")[0]
image_id += 1
# 将重构后的当前行文本拼接后重新赋值给当前行内容
content = "`".join(content_split_result)
file_content_lists[line_no] = content
line_no += 1
save_md_file(os.path.basename(obsidian_file_path),
"".join(file_content_lists))
copy_obsidian_image(image_path)
if __name__ == "__main__":
# 待发布的 Obsidian Markdown 文件路径
obsidian_file_path = input("请输入待发布的 Obsidian Markdown 文件路径:").strip("\"")
main(obsidian_file_path)
0x04:工具优化记录
0x0401:解决了 1.0 中的图片误识别问题
这里附上 Bug 触发样式(应该挺少见的):
这里附上 1.1 版本的识别结果(我把引用块中的代码块可以不设置结束符也考虑进去了):