用 Python 批量创建思源笔记的 DailyNote

起因

经过一个多月的使用,已逐渐适应并喜欢上写思源的 dailynote,除了待办、摘录和总结外,当天剪藏的文章会直接用 Alt + [ 保存为子文档,书写格式和流程基本固化。

因我的某象已经沦为“垃圾场”,不适合批量导出导入,如想把之前年度的笔记进行手工“清洁”迁移,需要新建大量 dailynote 文档。

过程

研究思源笔记本地文件

思源笔记的本地文件结构

打开本地目录发现,主要结构为笔记本文件夹——文档,和文档文件夹——子文档两种。

data
├─.siyuan
├─yyyymmddhhmmss-随机7位字母和数字组合  # 笔记本文件夹,如对应daily note笔记本
│  │
│  ├─.siyuan
│  ├─yyyymmddhhmmss-随机7位字母和数字组合  # 一级文档文件夹,如对应2020子文档文件夹
│  └─yyyymmddhhmmss-随机7位字母和数字组合.sy  # 一级文档,如对应2020文档
│    │
│    ├─yyyymmddhhmmss-随机7位字母和数字组合  # 二级文档文件夹,如对应01月子文档文件夹
│    └─yyyymmddhhmmss-随机7位字母和数字组合.sy  # 二级文档,如对应01月文档
│      │
│      └─yyyymmddhhmmss-随机7位字母和数字组合.sy  # 三级文档,如对应2020-01-01文档
├─assets
├─emojis
├─icon
├─templates
└─widgets

思源笔记的本地文件

打开几个对应仅录入了文档名的本地文件,发现不同的主要为文档的 ID、文档名、段落的 ID 和更新时间戳。其中删改文档内容时,Children 对象的也会变化,考虑是否可先省略。

{
	"ID": "yyyymmddhhmmss-随机7位字母和数字组合",  # 文档的ID
	"Type": "NodeDocument",
	"Properties": {
		"id": "yyyymmddhhmmss-随机7位字母和数字组合",  # 文档的ID
		"title": "文档名",
		"updated": "yyyymmddhhmmss"  # 更新时间戳
	},
	"Children": [
		{
			"ID": "yyyymmddhhmmss-随机7位字母和数字组合",  # 段落的ID
			"Type": "NodeParagraph",
			"Properties": {
				"id": "yyyymmddhhmmss-随机7位字母和数字组合"  # 段落的ID
				"updated": "yyyymmddhhmmss"  # 更新时间戳
			}
		}
	]
}

ID 的差异化

当创建 2020-01-01 文档时,实际需要分别先创建 2020 和 01 文档和文件夹,3 个文档 ID 的前缀 yyyymmdd 都是 20200101。为了有所区分,对秒数进行了递进,同时方便后续按 ID 排序时不乱序。

文档名 ID 前缀
2020 20200101010101
01 20200101010102
2020-01-01 20200101010103

研究 Python 代码

随机字符串相关

string.ascii_lowercase  # 小写字母
string.digits  # 数字
random.sample(population, k)  # 无重复的随机抽样
str.join(sequence)  # 将序列中的元素以指定的字符连接生成一个新的字符串

日期时间相关

datetime.date(year, month, day)  # 一个理想化的简单型日期
datetime.datetime.now()  # 返回当前时间
strftime(format)   # 根据给定的格式将对象转换为字符串
    %Y  # yyyy,年份
    %m  # mm,补零月份
    %d  # dd,补零日
    %H  # hh,补零24小时制
    %M  # mm,补零分钟
    %S  # ss,补零秒
dateutil.relativedelta.relativedelta()  # 计算时间间隔,支持年月日等参数

文件相关

f = open(file, mode)
    "w"  # 写入,不存在则新建
    "x"  # 创建,已存在则报错
f.write()  # 文件写入
f.close()  # 文件关闭
os.mkdir(path)  # 创建文件夹

JSON 相关

json.dumps(obj, indent=4)  # 输出 JSON 格式,并缩进四个空格

消息框相关

tkinter.messagebox.showinfo(title, message)  # 弹出消息提示框

输出代码

import string
import random
import os
import json
import tkinter
import tkinter.messagebox
import sys

from datetime import *
from dateutil.relativedelta import *

start_date = date(2020, 1, 1)  # 需修改起始日期,不能跨年
end_date = date(2020, 12, 31)  # 需修改结束日期,不能跨年

# 确定随机数长度为 7
r_num = 7
# 随机数组合为小写字母和数字
token = string.ascii_lowercase + string.digits
# 生成年度文档随机数
token_y = ''.join(random.sample(token,r_num))

# 生成年度文档 ID,时间默认为当年 1 月 1 日 01 点 01 分 01 秒
id_y = start_date.strftime('%Y') + "0101010101-" + token_y

# 生成年度文档内容
data_y = {
    "ID": id_y,
    "Type": "NodeDocument",
    "Properties": {
        "id": id_y,
        # 标题格式 yyyy
        "title": start_date.strftime('%Y'),
        # 更新时间为当下时间,格式 yyyymmddhhmmss
        "updated": datetime.now().strftime('%Y%m%d%H%M%S')
    }
}
path_k = "results"
if not os.path.exists(path_k):
    os.mkdir(path_k)
else:
    tkinter.messagebox.showinfo("提示", "请先删除 " + path_k + " 文件夹后再重新运行")
    sys.exit()

# 创建年度文档
filename_y = path_k + "/" + id_y + ".sy"
f = open(filename_y, "x")
# 输出 JSON 格式,并缩进四个空格
f.write(json.dumps(data_y, indent=4))
# 关闭文件
f.close()
# 创建年度文档文件夹
os.mkdir(path_k + "/" + id_y)

# 计算起始和结束日期月份差
delta_m = end_date.month - start_date.month + 1

# 月份循环 i 次
for i in range(delta_m):
    day_m = start_date + relativedelta(months=+i)  
    token_m = ''.join(random.sample(token,r_num))
    # 生成月度文档 ID,时间默认为当月 1 日 01 点 01 分 02 秒
    id_m = day_m.strftime('%Y%m%d') + "010102-" + token_m
    data_m = {
        "ID": id_m,
        "Type": "NodeDocument",
        "Properties": {
            "id": id_m,
            # 标题格式 mm
            "title": day_m.strftime('%m'),
            "updated": datetime.now().strftime('%Y%m%d%H%M%S')
        }
    }
    # 在年度文件夹下,创建月度文档
    filename_m = path_k + "/" + id_y + "/" + id_m + ".sy"
    f = open(filename_m, "x")
    f.write(json.dumps(data_m, indent=4))
    f.close()
    # 在年度文件夹下,创建月度文件夹
    os.mkdir(path_k + "/" + id_y + "/" + id_m)

    # 计算当月总天数
    delta_d = day_m + relativedelta(months=+1) + relativedelta(days=-1)

    # 日循环 j 次
    for j in range(delta_d.day):
        day_d = day_m + relativedelta(days=+j)
        token_d = ''.join(random.sample(token,r_num))
        # 生成当日文档 ID,时间默认为当日 01 点 01 分 03 秒
        id_d = day_d.strftime('%Y%m%d') + "010103-" + token_d
        data_d = {
            "ID": id_d,
            "Type": "NodeDocument",
            "Properties": {
                "id": id_d,
                # 标题格式 yyyy-mm-dd
                "title": day_d.strftime('%Y-%m-%d'),
                "updated": datetime.now().strftime('%Y%m%d%H%M%S')
            }
        }
        # 在年度文件夹 / 月度文件夹下,创建当日文档
        filename_d = path_k + "/" + id_y +"/" + id_m + "/" + id_d + ".sy"
        f = open(filename_d, "x")
        f.write(json.dumps(data_d, indent=4))
        f.close()
  
tkinter.messagebox.showinfo("提示", "感谢等待,创建已完成")

操作

环境

  • win10
  • 思源笔记 1.8.4

步骤

  1. 选择 daily note 笔记本——...更多——打开文件位置;
  2. 关闭思源笔记;
  3. 将上方代码保存为 py 文件,修改第 11 和 12 行的起始结束日期,不跨年
  4. 文件运行完成后会出现成功的提示弹窗;
  5. 将生成的 result 文件夹下所有文件及文件夹复制到 daily note 笔记本对应的本地文件夹下。不含 result 文件夹
  6. 打开思源笔记,可能需要等待几分钟,再点击文档树——...更多——重建索引,完成;

风险提示

本人几乎无 Python 基础,代码为搜索 + 摸索产出,此贴主要是分享探讨,不排除使用时引发其他问题,建议新建工作目录测试成功后再用于正式目录。

有更好的优化方案也欢迎大家指教,谢谢!​

2 操作
Syngo 在 2022-02-27 18:45:36 更新了该帖
Syngo 在 2022-02-27 18:25:10 更新了该帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...
  • abumn

    没有 python 基础还能做出这么厉害的工作!太强了

    1 回复
  • ilovesiyuan

    请问楼主这个是干嘛的呀,有啥作用呀

    1 回复
  • thelamb 1
    订阅者

    lz 是为了把旧资料通过 daily note 的形式补充进来

  • WeiCJ
    订阅者

    直接新建 sy 文件也可以实现需求

    最近思源的 api 基本稳定了, 如果使用 api 的话会更加简单一些,后续如果有其他功能需求,实现起来也会方便许多

    有兴趣可以尝试一下哈 😄

  • buzzingbee
    订阅者

    楼主虽然可能没有 python 基础,但别的基础好啊,很专业了