查找相似markdown笔记

需求

在整理笔记的过程中,许多人由于使用过多个笔记软件,常常会面临笔记重复或内容相似的问题。这些重复的笔记不仅难以查找,而且取舍起来也相当棘手。为了更高效地管理这些笔记,我计划定期抽出固定时间,对相似的笔记进行统一处理,剔除过时的内容,补充最新的信息。

然而,找出相似笔记本身就是一个繁琐的任务。为了解决这一问题,我开发了一个脚本,旨在自动识别并标记出内容相似的笔记,从而简化整理过程。通过这个工具,我希望能够更轻松地整合和优化我的笔记库,确保其始终保持整洁和更新。

步骤

在使用之前,你需要安装一些特定库:

pip install nltk scikit-learn

具体的代码如下:

import os
import re
import nltk
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity

# 下载nltk数据包
nltk.download('punkt')

def read_md_files(folder_path, exclude_folders=None):
    """
    递归读取文件夹及子文件夹中的所有Markdown文件内容(返回绝对路径)
    :param folder_path: 根目录路径
    :param exclude_folders: 需要排除的文件夹列表(相对路径或绝对路径)
    :return: 字典,键为文件绝对路径,值为文件内容
    """
    if exclude_folders is None:
        exclude_folders = []
    
    # 将排除文件夹转换为绝对路径
    exclude_folders = [os.path.abspath(os.path.join(folder_path, f)) for f in exclude_folders]
    
    md_contents = {}
    for root, _, files in os.walk(folder_path):
        # 检查当前目录是否在排除列表中
        if any(exclude_folder in root for exclude_folder in exclude_folders):
            print(f"跳过排除目录: {root}")
            continue
        
        for filename in files:
            if filename.lower().endswith(".md"):
                file_path = os.path.abspath(os.path.join(root, filename))
                with open(file_path, 'r', encoding='utf-8') as file:
                    try:
                        content = preprocess(file.read())
                        md_contents[file_path] = content
                    except UnicodeDecodeError:
                        print(f"解码失败跳过文件: {file_path}")
    return md_contents

def preprocess(text):
    """文本预处理函数(带Markdown清理)"""
    # 移除Markdown语法元素
    text = re.sub(r'[#*\-_`\[\]!]', '', text)
    # 移除代码块
    text = re.sub(r'```.*?```', '', text, flags=re.DOTALL)
    # 移除HTML标签
    text = re.sub(r'<.*?>', '', text)
    # 转小写并去除多余空白
    return text.lower().strip()

def batch_calculate_similarity(md_contents, threshold=0.8):
    """批量计算相似度并返回结果列表"""
    file_paths = list(md_contents.keys())
    contents = list(md_contents.values())
    
    vectorizer = TfidfVectorizer()
    tfidf_matrix = vectorizer.fit_transform(contents)
    
    cos_sim = cosine_similarity(tfidf_matrix)
    
    similar_pairs = []
    for i in range(len(cos_sim)):
        for j in range(i+1, len(cos_sim)):
            if cos_sim[i][j] >= threshold:
                similar_pairs.append((
                    file_paths[i], 
                    file_paths[j], 
                    round(cos_sim[i][j], 4)
                ))
    
    return similar_pairs

def save_results(similar_pairs, output_file="similar_results.txt"):
    """保存结果到文本文件"""
    with open(output_file, 'w', encoding='utf-8') as f:
        f.write("file1,file2,similarity\n")
        for pair in sorted(similar_pairs, key=lambda x: x[2], reverse=True):
            f.write(f'"{pair[0]}","{pair[1]}",{pair[2]}\n')
    print(f"\n结果已保存到 {os.path.abspath(output_file)}")

def main():
    folder_path = "D:/库/笔记备份/"  # 修改为你的实际路径
    output_filename = "similar_notes.csv"  # 输出文件名
    
    # 需要排除的文件夹列表(相对路径或绝对路径)
    exclude_folders = [
        "archive",  # 相对路径
        "D:/库/笔记备份/旧笔记",  # 绝对路径
    ]
    
    print("正在扫描Markdown文件...")
    md_contents = read_md_files(folder_path, exclude_folders)
    
    if not md_contents:
        print("未找到任何Markdown文件")
        return
    
    print(f"找到 {len(md_contents)} 个文件,开始计算相似度...")
    similar_pairs = batch_calculate_similarity(md_contents)
    
    if similar_pairs:
        print(f"\n发现 {len(similar_pairs)} 对相似文档:")
        for pair in sorted(similar_pairs, key=lambda x: x[2], reverse=True):
            print(f"{pair[2]:.2%} | {os.path.basename(pair[0])} <-> {os.path.basename(pair[1])}")
        save_results(similar_pairs, output_filename)
    else:
        print("没有找到相似度超过80%的文件对")

if __name__ == "__main__":
    main()

使用说明

  1. folder_path变量用于需要查找的文件夹(子文件夹中的笔记也会进行遍历);
  2. 为了避免一些特殊的文件和二进制文件,所以只遍历了md后缀的markdown文件(二进制文件对比起来非常耗时,其他文件可能包括了配置文件,于我个人没有实际意义;
  3. 当前设置的相似度为80%,如果需要修改,可以修改def batch_calculate_similarity(md_contents, threshold=0.8)中的0.8;
  4. 结果输出在similar_notes.csv文件中,代码中变量为output_filename。

输出效果:

最后使用quicker动作 ever智识 快速打开对应笔记进行对照修改即可。


此处,可以按我圈出来的,加一个排除 Exclude_Path = [“.trash”, " "] # 排除的文件夹 ,因为有的人,没有清理 这个 .trash的习惯,然后就会把这个回收站的也给比对了,不过整体来,非常好,谢谢分享;

感谢反馈,已更新
250203-185749