沒錯,你的邏輯非常清晰且安全。這正是處理大量資料遷移的標準 SOP:
內容清洗 (Content Refactoring): 先把筆記內部所有的
[[December 15th, 2025]]連結改成[[2025-12-15]]。這時候檔案還沒改名,所以連結會暫時失效(沒關係)。檔案重命名 (File Renaming): 把檔名改成
2025-12-15.md。這時候剛才改好的連結就會自動生效。衝突防護 (Collision Guard): 如果
2025-12-15.md已經存在,則跳過該檔案的改名,留給你之後用 Obsidian 的 Note Composer 手動合併。
我為你寫了一個 Python Script,具備了 Dry Run (模擬執行) 功能。預設是「只顯示不執行」,確認沒問題後你可以把開關打開。
Python 自動遷移腳本
請將以下程式碼存成 migrate_roam_dates.py,並放在你的 Logseq pages 或 journals 資料夾同一層目錄中(或者你需要修改 TARGET_DIR 路徑)。
import os
import re
import sys
# ================= 設定區 =================
# 指定你的筆記資料夾路徑 (例如: r"C:\Users\Name\Logseq\pages")
# 如果腳本放在筆記資料夾內,使用 "." 即可
TARGET_DIR = r"."
# 安全開關:True = 只顯示會發生什麼事,不實際修改檔案
# 確認無誤後,請改成 False
DRY_RUN = True
# =========================================
# 月份對照表
MONTH_MAP = {
"January": "01", "February": "02", "March": "03", "April": "04",
"May": "05", "June": "06", "July": "07", "August": "08",
"September": "09", "October": "10", "November": "11", "December": "12"
}
# Regex 模式:匹配 Roam 格式日期 (例如 December 1st, 2025)
# 捕捉組: Group 1=Month, Group 2=Day, Group 3=Year
DATE_PATTERN = re.compile(
r'(January|February|March|April|May|June|July|August|September|October|November|December)\s+(\d{1,2})(?:st|nd|rd|th),\s+(\d{4})',
re.IGNORECASE
)
def to_iso_format(match):
"""將匹配到的 Roam 日期轉換為 YYYY-MM-DD"""
month_name = match.group(1).title() # 確保首字大寫以匹配字典
day = int(match.group(2))
year = match.group(3)
month_num = MONTH_MAP.get(month_name)
if not month_num:
return match.group(0) # 找不到月份則不改
return f"{year}-{month_num}-{day:02d}"
def process_files():
print(f"🚀 開始執行... (Dry Run 模式: {DRY_RUN})")
print(f"📂 目標資料夾: {os.path.abspath(TARGET_DIR)}\n")
files = [f for f in os.listdir(TARGET_DIR) if f.endswith(".md")]
# ---------------------------------------------------------
# 第一階段:修改檔案內容 (Content Replacement)
# ---------------------------------------------------------
print("--- 階段 1: 修改檔案內容中的連結 ---")
modified_count = 0
for filename in files:
file_path = os.path.join(TARGET_DIR, filename)
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
# 尋找 [[December 15th, 2025]] 這種格式
# 我們使用回調函數 to_iso_format 來動態替換
new_content = DATE_PATTERN.sub(to_iso_format, content)
if content != new_content:
print(f"[修改內容] {filename}")
modified_count += 1
if not DRY_RUN:
with open(file_path, 'w', encoding='utf-8') as f:
f.write(new_content)
except Exception as e:
print(f"❌ 讀取錯誤 {filename}: {e}")
print(f"第一階段完成:共計畫修改 {modified_count} 個檔案的內容。\n")
# ---------------------------------------------------------
# 第二階段:修改檔名 (Rename Files)
# ---------------------------------------------------------
print("--- 階段 2: 修改實體檔名 ---")
rename_count = 0
skip_count = 0
# 重新掃描一次檔案列表,確保處理的是當前狀態
files = [f for f in os.listdir(TARGET_DIR) if f.endswith(".md")]
for filename in files:
# 檢查檔名是否符合 Roam 格式 (去掉 .md 後檢查)
name_without_ext = filename[:-3]
match = DATE_PATTERN.fullmatch(name_without_ext)
if match:
new_name_body = to_iso_format(match)
new_filename = f"{new_name_body}.md"
old_path = os.path.join(TARGET_DIR, filename)
new_path = os.path.join(TARGET_DIR, new_filename)
# 檢查新檔名是否已經存在 (衝突檢查)
if os.path.exists(new_path) and filename != new_filename:
print(f"⚠️ [跳過 - 檔名衝突] {filename} -> {new_filename} (目標檔案已存在)")
skip_count += 1
else:
print(f"✅ [改名] {filename} -> {new_filename}")
rename_count += 1
if not DRY_RUN:
try:
os.rename(old_path, new_path)
except Exception as e:
print(f"❌ 改名失敗: {e}")
print(f"\n第二階段完成:")
print(f"預計改名: {rename_count}")
print(f"預計跳過 (衝突): {skip_count}")
if DRY_RUN:
print("\n✨ 這是模擬執行,沒有檔案被修改。")
print("請將程式碼中的 DRY_RUN = False 改為 True 以執行實際操作。")
else:
print("\n🎉 作業完成!請記得使用 Obsidian Note Composer 處理那些「跳過」的重複檔案。")
if __name__ == "__main__":
# 簡單防呆:確認路徑存在
if os.path.exists(TARGET_DIR):
process_files()
else:
print("❌ 找不到目標資料夾,請修改 TARGET_DIR 設定。")
如何使用此腳本
備份!備份!備份!
在執行任何腳本前,請將你的整個 Logseq/Obsidian 資料夾複製一份到其他地方。
設定路徑:
打開腳本,修改 TARGET_DIR。
如果你把腳本直接丟進
pages資料夾執行,保持TARGET_DIR = "."即可。否則請填入完整路徑,例如:
TARGET_DIR = r"D:\MyNotes\pages"(注意前面的r)。
執行 Dry Run (模擬):
直接執行腳本:python migrate_roam_dates.py。
它會列出所有它「打算」修改的內容和檔名。
檢查
[修改內容]是否正確抓到了日期。檢查
[改名]轉換的 ISO 格式是否正確。檢查
[跳過 - 檔名衝突]是否有正確偵測到重複檔案。
正式執行:
將程式碼第 13 行改成 DRY_RUN = False。
再次執行腳本。
善後處理:
執行完畢後,打開 Obsidian,你會發現大部分檔案都已經變成標準的 2025-12-15.md。
剩下的就是那些在 Console 中顯示 [跳過] 的檔案(即舊格式檔名),請使用 Obsidian 的 Note Composer 手動將它們合併到新的日期檔案中。
沒有留言:
張貼留言