今天这篇学习日记day74,我想从一个很小但很真实的痛点说起:个人文档库的混乱。我坚持写了七十多天的技术笔记,到第50天左右的时候,积累的Markdown文件、临时记录、代码片段已经散落得到处都是,文件名千奇百怪,比如“新建文档123.md”、“未命名2222.txt”、“最终版最终版V3.md”。第74天,我决定不再逃避这个问题,用一上午的时间,写了一个Python脚本,把整堆散乱的笔记自动归类、重命名、去重,顺手还生成了一份目录索引。整个过程不算复杂,但踩了不少坑,也把很多平时没想明白的细节彻底搞清楚了。这篇日记就记录一下当天的完整思路、代码和问题排查过程,想给同样被文件整理折磨的朋友一个可以直接抄作业的参考。
如果你是刚开始接触自动化脚本的新手,或者你手里也有一堆命名混乱的文档不知道从何下手,这篇内容会比较适合你。我会尽量把每一步的原理讲明白,而不是只丢一段代码让你复制。
1. 今天的学习目标与选题拆解
1.1 第74天这个节点的特殊意义
连续写了74天学习日记,其实是一个挺微妙的状态。前30天是新鲜感驱动的,每天学什么都觉得有意思;到第60天左右,新鲜感消退,开始进入一种需要靠习惯和真实产出维持的阶段。这个时候如果继续零零散散地学新知识,学习效果反而会打折,因为积累的东西没有系统化,很多内容学了就忘了,需要的时候又找不到。
所以我在第74天给自己定了一个不一样的目标:不是学新东西,而是用已有的知识去解决一个具体问题。整理文档这件事,既需要文件操作能力,又需要正则表达式知识,还需要对数据结构有一点理解——正好把我前七十多天学的内容串起来了。我打算用python写一个脚本,把我那个乱糟糟的知识笔记目录彻底整理一遍。
1.2 先搞清楚要解决什么问题
动手前我没有急着写代码,先花了不少时间盘了盘自己的文档库里到底有什么。不查不知道,一查吓一跳,一个不到两GB的目录里,挤着400多个文件,分布在8层深浅不一的子文件夹里,有十几种文件格式。
我把问题归纳成四类:
- 命名完全没有规则:日期、编号、中文、英文、表情符号混在一起,看不出哪个文件是关于什么主题的
- 大量重复文件:同一个笔记被导出多次,内容相似,文件名带“副本”、“最终版”这类标记
- 文件类型混杂:markdown笔记、txt草稿、PDF资料、代码文件堆在同一个目录里,找东西全靠记忆
- 没有索引:想看自己写过哪些专题内容,只能一个个文件翻
这些问题其实很有代表性。很多人日常整理文件时,用的是“新建文件夹”和“手动移动”的办法,因为怕搞错所以不敢动,结果越积越多,最后干脆放弃整理。第74天,我把手动整理拟成的备份,只考虑“自动识别分类”和“批量重命名”这两个动作,让脚本干那些重复机械的活。
1.3 为什么用脚本而不是现成工具
可能有人会问,现在不是有很多文件整理工具、笔记软件自带的归档功能吗?为什么不直接拿来用。我的回答是:现成工具确实省事,但有两个问题没法绕开。
第一,工具只解决它自己定义好的场景。比如笔记软件可以给文件打标签,但如果你的文件有一半是txt、PDF,还有源码文件,大杂烩式的目录根本不是某个单一软件能覆盖的。第二,我想借这个机会练手。用脚本整理的最大价值,是你必须理清“文件的特征怎么识别”、“重命名的规则怎么定”、“哪些文件需要保留”,这个过程本身就是一次系统性的思考训练。
从性价比看,写一个百来行的Python脚本,一次投入时间,以后每次产生新笔记都可以跑一遍,长期节省的时间和精力远超手动整理。所以我确定方案:遍历目录、识别文件类型和主题关键词、按规则重命名、按分类移入对应文件夹、生成索引。这就是这次学习日记的完整目标。
2. 动手前必须理解的核心原理
2.1 文件名背后的信息结构
整理文件的第一个核心认知是:文件名不只是给人看的,它本质上是一条信息记录。一段好的文件名,应该一句话就能说清“这是什么内容、什么时间、属于哪个主题”。
我当时给自己定了一个命名模板:主题_日期_说明.扩展名。比如python_oop_20240418_类继承笔记.md,一眼就能看出这段笔记的主题、记录时间和内容概要。这个模板看着简单,但落地的时候有几个细节要考虑。
第一,日期格式要统一。中文里习惯写“2024年4月18日”,但在文件名里最好用20240418这种紧凑格式,因为带年、月、日三个字符的文件名在排序时会乱掉,“年”和“月”是中文,编码顺序不直观。第二,主题词要控制在两三个以内,不能太长,否则文件名失去快速扫读的意义。第三,扩展名必须保留,不能改动,这是系统识别文件类型的关键。
实际写脚本的时候,主题词并不是我想出来的,而是从原文件名和正文内容里提取的。这就涉及到第二个核心概念——正则表达式。
2.2 正则表达式:从一团文字里捞关键词
如果你第一次接触正则表达式,不用怕,它的思想其实很简单:定义一套规则,让程序按规则在文本里找匹配的内容。我打个比方,正则表达式就像一张带孔的面具,你把面具往文字堆上一贴,只有形状刚好对的文字能露出头来,其他都被挡住。这个“形状”,就是你自己定义的匹配模式。
我用到的正则功能其实很基础,一个是匹配固定的字符模式,比如\d{8}能匹配8位连续数字,恰好用来识别日期;另一个是按字符类别匹配,比如\.md$能判断一个文件名是不是以.md结尾的,拿来做文件类型分类;还有一个是替换捕获内容,把匹配到的部分提取出来,变成我要的命名结果。
在标题的场景里,正则表达式的容错设计很关键。文档名称里经常有各种奇怪的字符、空格、中文标点,如果规则写得过于严格,就会漏掉一批文件;写得太宽松,又可能把不该匹配的内容卷进来。我的策略是:先用一个“比较保守”的匹配规则跑一遍,把能识别的文件先整理出来,识别不了的清单留在日志里,再根据日志给文件补一个默认分类。这是后面实操过程中反复用到的思路。
2.3 批量操作的安全边界:永远先跑演练模式
在讲代码之前,我必须先强调一个血的教训:批量操作类脚本,绝对不能一上来就对原始文件动手。我当时把“操作前先演练”当作铁律,任何移动、改名、删改动作,都先在一个副本目录或者虚拟模式下试运行,确认无误了才针对真实文件执行。
你可以把“演练”理解成一次只输出日志不执行动作的跑法。脚本遍历所有文件、计算目标位置和文件名,打印出来一行行检查,但就是不真正移动文件。这一步能防住八成以上的低级错误。
另外,无论运行什么脚本,第一步永远是全量备份。我用了一个很简单的方法:把原始目录压缩成一个带时间戳的zip包放到其他盘里。这句话看起来像废话,但真的有人不备份就直接跑批量脚本,结果出现意外时哭都来不及。
3. 实操过程与核心代码实现
3.1 环境准备
脚本运行环境很轻量:一台装了Python 3.8以上版本的电脑就行。
我用到的库有三个:os(遍历文件和目录)、re(正则表达式匹配)、shutil(移动文件)。这三个都是Python自带的标准库,不需要任何第三方依赖,也不会因为环境问题跑不起来。这样做的考虑是,一个工具如果依赖一大堆额外安装,别人想复现时就会在环境上卡住,我用最基础的标准库,把门槛降到最低。
在项目目录E:\learning_notes下,我建了一个脚本文件organize_notes.py,还按整理后的结构设计好了目标文件夹:md_notes放Markdown笔记,text_notes放TXT文件,pdf_docs放PDF资料,code_snippets放代码文件,others放暂时无法归类的文件。
3.2 核心代码:遍历、识别、重命名、移动
我的第一版脚本大致长这样:
import os import re import shutil from datetime import datetime ROOT_DIR = r"E:\learning_notes" DATE_PATTERN = re.compile(r"(\d{8})|(\d{4})[-_.](\d{1,2})[-_.](\d{1,2})") TYPE_MAP = { ".md": "md_notes", ".txt": "text_notes", ".pdf": "pdf_docs", } def extract_date_from_name(filename): """从文件名中提取日期,返回yyyyMMdd格式字符串,提取不到则返回日期未知""" match = DATE_PATTERN.search(filename) if not match: return "date_unknown" if match.group(1): return match.group(1) year, month, day = match.group(2), match.group(3), match.group(4) return f"{year}{month.zfill(2)}{day.zfill(2)}" def build_new_name(filename, category, date_str, tag=""): """按照 主题_日期_说明.扩展名 的结构生成新文件名""" stem, ext = os.path.splitext(filename) tag = tag or "未分类" safe_date = date_str return f"{tag}_{safe_date}_{stem}_整理.{ext}"这段代码的思路是这样:DATE_PATTERN负责从文件名里扒出日期,支持20240418和2024-04-18两种格式;TYPE_MAP是文件类型到目标文件夹的映射;build_new_name把提取到的信息组合成一个带“整理”标记的新文件名,避免和可能存在的重名文件冲突。
接下来是遍历目录、执行移动的主逻辑。我在这里特意加入了一个dry_run参数,也就是前面说的演练模式。传入True时只打印将要做什么,传False时才真正执行操作。
def organize(root_path, dry_run=True): for current_dir, _, files in os.walk(root_path): for filename in files: file_path = os.path.join(current_dir, filename) if file_path == os.path.abspath(__file__): continue # 跳过脚本自身 ext = os.path.splitext(filename)[1].lower() category = TYPE_MAP.get(ext, "others") date_str = extract_date_from_name(filename) new_name = build_new_name(filename, category, date_str) target_dir = os.path.join(root_path, category) target_path = os.path.join(target_dir, new_name) if dry_run: print(f"[演练] {file_path} -> {target_path}") else: os.makedirs(target_dir, exist_ok=True) if not os.path.exists(target_path): shutil.move(file_path, target_path) if __name__ == "__main__": organize(ROOT_DIR, dry_run=True)如果你第一次跑这个脚本,建议先调用organize(ROOT_DIR, dry_run=True),把所有重命名和移动计划打印出来过目一遍。确认无误后,再把参数改成False正式执行。这个习惯我到现在都保留着,它帮我挡掉了不知道多少次误操作。
3.3 一次真实运行的效果比对
我那天正式执行前,先跑演练模式,发现一个问题:大量文件的日期没有被正确提取,全都变成了date_unknown,导致文件名里出现了很多“未分类_日期未知”的组合。这个结果虽然不好看,但很有价值,它暴露了原始文件不用标准日期格式的严重程度。
我随机抽了十几条日志检查,发现主要有三种情况:一是有文件名写的是“2024年4月18日”,中文年月日,我的正则没认出来;二是日期藏在文件内容第一行,文件名里根本没有;三是完全没有日期信息,比如“临时想法.txt”。
针对这个问题,我加了一段辅助逻辑:当文件名提取不到日期时,从文件内容的前五行里用同样的正则再去摘一下日期。如果还是找不到,再考虑用文件的创建时间作为兜底。
def extract_date_from_content(file_path): """从文件前几行内容中尝试提取日期""" try: with open(file_path, "r", encoding="utf-8", errors="ignore") as f: head = "".join(f.readlines()[:5]) except Exception: return "" match = DATE_PATTERN.search(head) if not match: return "" # 逻辑与extract_date_from_name类似,此处省略重复实现 return date_str重新跑完演练模式后,正确识别日期的比例从不足三成提升到了八成以上,效果非常明显。正式跑了一遍脚本,原来400多个文件被整齐地分到了5个分类目录里,文件名也全部统一成了主题_日期_说明_整理.扩展名的格式。整理完那一刻,我打开文件管理器看着新的目录结构,那种顺滑感是真真切切让人上瘾的。
3.4 顺带解决重复文件的问题
重复文件这个问题,我单独写了一个小模块来处理。思路很简单:对候选文件计算一个简单的哈希值,把哈希值相同的文件挑出来,保留命名最规范的那一个,其他的移到一个duplicates_suspect文件夹里,不直接删除。
这里我刻意选择了“移动而非删除”,原因有两个:一是哈希值相同的文件内容可能只是部分相同,比如同一个笔记的不同版本,不能断言内容完全冗余;二是人检查比脚本判断可靠,“疑似重复”这个状态本身就已经足够提醒我人工确认一次了。
当天的实测结果是:从400多个文件里找出了32个可疑重复项,人工确认后,真正可以安全清理的有25个左右。这个比例不算小,也从侧面说明了日常笔记管理里“随手复制一份”这个习惯带来的浪费有多严重。
4. 当天踩过的坑与排查过程实录
4.1 第一个坑:中文文件名乱码
首批正式跑脚本,我遇到的头号问题是中文文件名乱码。我日常用的是Windows系统,很多历史文件是早期从别的地方拷贝过来的,文件名本身是正常的简体中文,但用Python读取时部分文件名变成了乱码。
排查思路是这样的:先确认是不是脚本读取错误,于是单独打印了几个乱码文件名的字节表示。结果发现,os.listdir()返回的文件名其实是正常的,只是控制台输出时因为编码设置不对显示成了乱码。我改用把日志写入文件的方式去查看,乱码现象消失了。
但这还没完。真正的问题在移动文件时,部分目标路径因为包含特殊字符而找不到文件。我后来排查,发现是源文件的文件名里包含了Windows不允许的字符,比如?和*,这些字符在系统层面已经被替换,但脚本用同名去操作时就容易出错。最终我加了过滤逻辑:新文件名只保留中文、英文、数字、下划线和连字符,其余全部替换成下划线。
4.2 第二个坑:日期提取规则误伤文件名
另一个坑比较隐蔽。我的正则规则里用\d{8}去匹配8位连续数字,结果把某些文件名里的类似“20231201_会议纪要”的部分给拆错了。更离谱的是,有个文件名是“程序员必须知道的100个问题”,里面没有数字日期,但“100”被当成三位数字干扰匹配吗?不会,因为规则要求8位连续数字,所以没有误伤。真正的问题出在另一条规则上:日期格式支持2024-04-18,而文件名里像“小红书-2023-笔记”这种带分隔符的文本,会把“2023”单独提取出来当作年份。
排查时我用了一段小而独立的测试脚本,把目录里所有文件名逐一打印出来,对照正则匹配结果,才定位到问题。我调整了匹配策略:只有在上下文同时满足“月份在1-12、日期在1-31”时,才认定为日期;否则跳过。这里的教训是:正则表达式的威力恰恰在于它的精确可控,保持规则严格但不过度,需要反复校验边界条件。
4.3 第三个坑:执行到一半报找不到目标目录
正式执行时,脚本跑到第112个文件的时候突然报错:目标目录不存在。我检查代码发现,我在move之前其实已经用os.makedirs(target_dir, exist_ok=True)创建了目录,但这个调用是在循环内部、且带条件的,如果某个分类的目录先被意外创建而后续路径写错了,就会出现问题。更细看,其实是同一个脚本里有两处创建目录的逻辑,其中一处少加了exist_ok=True,另一处则会覆盖掉之前设定的分类名,导致目标路径不一样。
这个问题花了我快二十分钟才定位出来。解决方法是统一封装一个ensure_dir()函数,所有创建目录的地方都走同一个入口,并且把分类名的映射表写成一个全局常量,保证任何地方引用都是同一个值。现在想想,如果一开始把代码写得模块化一点,就不至于调试这么久。
4.4 常见问题排查速查表
当天结束后,我把踩过的坑整理成一张速查表,方便以后自查:
| 遇到的现象 | 可能的根因 | 排查思路 | 解决办法 |
|---|---|---|---|
| 文件名显示乱码 | 控制台输出编码不一致 | 把输出改为写入日志文件观察 | 设置输出编码为UTF-8,日志用文件记录 |
| 目标路径找不到 | 文件名含特殊符号或映射不统一 | 打印实际路径和异常栈 | 过滤特殊字符,统一封装目录创建逻辑 |
| 日期全部是unknown | 文件名格式不标准,无日期字段 | 抽样打印文件名和匹配结果 | 增加从文件内容提取日期的兜底逻辑 |
| 移动后文件缺失 | 移动目标已存在同名文件 | 检查是否skip或覆盖 | 对目标文件名做唯一性检查,加序号后缀 |
4.5 两个价值很高的避坑心得
第一个心得是关于备份的。前七天我差点直接对原始目录跑脚本,因为当时的“演练”输出看起来非常完美,没有报错。但就在正式跑之前,我鬼使神差地先把整个目录压缩备份了一份。等到执行到中途报错,尝试各种修复办法都没有让所有文件移动成功时,我才真正理解备份的价值——它让我们在出问题时永远有回头路。
第二个心得是关于小步迭代的。与其写一个超大的脚本一次性解决所有问题,不如把任务拆成“先分类再重命名最后查重”三个独立步骤,每完成一步就检查一遍结果。我第一次就是把所有逻辑揉在一个主函数里,导致出错后要在一百多行代码里找漏洞,效率极低。改用小步骤之后,后面再跑任何批量脚本,我都遵循这个“分阶段验证”的原则。
5. 这次整理带来的蝴蝶效应
第74天整理完文档之后,当天的收获其实已经超出了脚本本身。我开始重新审视自己做学习笔记的方式,后续在每一天的学习日记里,都先把新产生的笔记按统一模板命名,放进对应分类,不再让文档积压成山。这个习惯坚持到现在,我的笔记库再没有重新陷入混乱过。
另一个意外收获是,这次脚本里写的“正则表达式匹配日期”的方法,被我复制到了别的地方用。比如我手头有一些按日期命名的日志文件要批量分析,直接就拿这个函数改一改,省了很多事。所以说,很多学习项目的价值不只是解决眼下的某个问题,它会像一个工具箱,以后遇到类似场景时自动产生复用价值。
那天下午我重新打开整理过的笔记目录,把几个月来想写但一直没理清楚的几个专题一口气梳理了出来。因为我能在五秒内定位到任何一条过去的笔记,知识之间的连接一下子变得更加清晰。这种体验让我觉得,花一个上午写那个整理脚本,可能是第74天做出得最有价值的一个决定。
如果你也想动手整理自己的文档库,我的建议是:第一步不要追求完美,先做一次演练和备份,把文件按类型分开;第二步再慢慢统一命名规则;第三步才考虑去重和内容索引。脚本越简单越好,能用标准库就不用第三方依赖。过程中出问题时多想想“这个异常信息在告诉我什么”,而不是急着去网上找现成答案。这样跑完一遍,你学到的会比任何一篇教程都多。