☰
Python手写文件批处理脚本:自动分类、重命名、校验与归档
2026/9/30 8:29:08 网站建设 项目流程

一个听起来像随手滚键盘的项目代号,ASFFSAFASF3,其实是我上个月花了两周业余时间捣鼓出来的内部小工具。它的全称我自己拼了一下:Auto Sort, Format, Filter, Search and Facilitate Archiving System,第三个迭代版。说白了,就是一个批量处理文件的小系统,负责把散落在下载目录、桌面、临时文件夹里的各种文档、图片、压缩包,按规则自动分类、重命名、过滤垃圾文件,再做校验和归档。适合谁看?想自己写一套文件自动化处理脚本的朋友,以及被乱七八糟文件管理搞得头疼的非程序员,也可以从这里拿走思路。这一篇我不谈高大上的框架,就讲实际操作里真能落地的东西,包括每一段脚本为什么这么写、参数怎么定、坑在哪里。

1. 项目概述与设计思路

1.1 这个工具到底解决什么问题

先说痛点。我电脑里最乱的是Downloads目录,常年两千多个文件,有安装包、截图、PDF、微信传过来的图片、各种不知名tmp文件。每次找一份合同都要翻半天,更别说谁有时间去手工整理。市面上其实有不少文件整理软件,但我有特殊需求:一部分文件要按客户编号重命名,一部分文件要检查是否损坏,还有一部分要从脏数据里过滤掉广告图片。通用软件做不到这么细,所以我决定自己写。

ASFFSAFASF3的思路非常简单:不做一个带界面的复杂软件,而是做一个跑在命令行里的批处理流水线。输入是一个目录,输出是整理好的归档目录,中间按阶段处理:先扫描,再分类,然后格式化重命名,接着做校验,最后归档。这个过程完全可重复,跑一遍不够就再跑一遍,规则改了也能随时重跑。

很多人一开始容易犯的错就是想把所有功能一次写成,结果脚本越写越复杂,最后自己都看不懂。我这版明确拆成了几个独立模块,每个模块只做一件事,模块之间用标准输入输出衔接。好处是单独改某一环不影响全局,出了问题也能快速定位。

1.2 为什么叫 ASFFSAFASF3:模块划分

这个代号看起来乱,实际是按功能缩写来的:

  • A:Auto,自动化
  • S:Sort,排序分类
  • F:Format,格式规范与重命名
  • F:Filter,过滤无用文件
  • S:Search,检索与去重
  • A:Archive,归档
  • F:Facilitate,辅助
  • A:Archiving System,整体就是归档体系
  • S:System,系统
  • 3:第三个大版本

前两个版本都烂尾了,原因很一致:我把精力花在纠结用什么框架、要不要做GUI上,核心整理逻辑反而没写透。第三个版本我完全放弃花架子,核心就五个处理阶段:

  1. 扫描阶段:递归读取目录里所有文件,记录路径、大小、扩展名、修改时间。
  2. 分类阶段:按扩展名和文件头识别类型,分成文档、图片、视频、压缩包、安装包、临时文件等。
  3. 过滤阶段:标记并隔离垃圾文件,包括Windows临时文件、浏览器缓存、重复文件。
  4. 格式化阶段:按可配置的规则对文件名做重命名,补充日期、客户编号、来源标记。
  5. 归档阶段:将文件移动到按类型和月份组织的目录里,并对敏感文件做哈希校验。

这套划分不是拍脑袋,而是我在整理文件时观察到的自然步骤。如果你自己也要写类似的东西,建议先不要写代码,把自己模拟整理一个月文件的过程写下来,流程清楚了再动手。

1.3 技术选型:为什么是 Python + 纯命令行

选Python的理由很直接:标准库足够处理文件和正则,不用装第三方依赖,跨平台也能跑。其实用shell脚本也能实现很多功能,但正则和哈希校验在Python里写起来更顺手,Windows和macOS行为也一致。

有人问我为什么不做成GUI或者Web服务,我的回答是:文件整理本质是一次性的批处理动作,用命令行跑一遍、看日志、改配置、再跑一遍,是最高效的闭环。GUI需要处理各种点击事件、界面状态、异常弹窗,这些和工作目标无关。命令行工具可以像这样用:

python asffsafasf3.py --input ~/Downloads --output ~/Archive --config config.json --dry-run

我把dry-run模式放在最显眼的位置,这是血泪教训。没有试跑模式的批处理脚本,第一次基本都会出事故。不管代码写得多自信,先跑一遍空转,绝对能救回你一堆文件。

2. 核心功能拆解与关键参数

2.1 文件分类与自动重命名

分类看起来是判断扩展名,实际没这么简单。很多文件扩展名是错的,尤其从网上下载的文件,PDF可能是个HTML伪装的,图片可能是损坏的空文件。所以分类不能只看后缀,我会用文件头魔数校验。

一份靠谱的分类规则应该分三层:

  1. 优先看扩展名,快速判断大概类别。
  2. 再看文件头,确认真实格式。
  3. 最后看大小,比如0字节文件直接进可疑区,大于1GB的视频单独归入大文件区。

重命名规则我用的是配置驱动而不是硬编码。配置文件里定义了一系列模板,例如:

  • 合同类:{客户编号}_{签订日期}_{原文件名}
  • 图片类:{拍摄年份}{拍摄月份}{当日序号}_{来源}

这样做的好处是,你不需要改核心代码,只需要改JSON或YAML配置就能适配不同工作流。比如我把原始文件名的后半段保留,是因为有些文件名里包含发票号,拆分丢掉了以后很难找回来。

重命名时还要处理非法字符。Windows下不能用\/:*?"<>|,macOS和Linux主要是不能用/,如果你写的脚本要在多平台同步目录里跑,必须统一把这些字符替换成下划线。

2.2 格式校验与内容过滤

文件整理的过程中,最容易被忽略的是校验。我以前出现过这样的情况:批量重命名时,文件名排重逻辑写错,直接覆盖了同名文件,等到发现时源文件已经没了。后来我在格式化阶段之前加入了哈希校验。

具体做法是:

  • 扫描时先对每个文件计算SHA-256哈希,记录在扫描清单里。
  • 归档前重新计算一次,如果哈希不匹配,说明文件被修改或复制出错,立刻报警,不继续操作。
  • 所有重命名操作都走“先复制到临时目录、校验通过后再移动”的流程,避免在源文件上直接操作。

这个流程看起来多了一步,但对数据安全来说是必要的。特别是当你整理的是客户合同、发票、项目素材这类不可再生文件时,多花几秒校验,比事后找回重要得多。

过滤模块主要负责识别不需要归档的文件:

  • .tmp、.bak、.crdownload这类临时文件;
  • 小于1KB且扩展名为图片的图标碎片;
  • 文件名匹配已知垃圾规则,比如“广告_”开头的截图;
  • 重复文件,按哈希分组,只保留最早修改的那份。

过滤策略我特意设成“隔离而不删除”,因为误删比不整理更难受。所有命中规则的文件会移动到_quarantine目录,保留30天,确认没用后再手动清空。

2.3 归档、备份与命名冲突策略

归档目录的层级我用了“类型/年份/月份”的结构,比如:

Archive/ documents/ 202406/ images/ 202406/ archives/ 202406/

为什么不用“客户/项目”这种业务结构?因为整理脚本无法稳定判断某个文件属于哪个客户,等业务信息足够清晰时,你已经能靠人脑漂移了,但脚本不能。用“类型+时间”能保证零错误地自动归档,具体业务归属以后再靠命名前缀体现。

命名冲突是最容易出事故的地方。两个文件重名时,常见策略是自动加序号,比如report_01.pdf、report_02.pdf。但如果你在重命名阶段已经把业务前缀加上了,再遇到冲突就要警惕:很可能是同一份文件的不同版本,也可能是重复文件。我的做法是,遇到冲突先放进_conflict目录,不自动处理,由人来判断。因为版本保留的优先级因人而异,自动覆盖那个选项我是绝对不做的。

2.4 日志与状态输出

命令行工具的输出要有层次,不能全是打点。我的设计分三级:

  1. --quiet:只输出错误和最终统计。
  2. 默认:输出每个阶段的统计和异常文件列表。
  3. --verbose:输出每个文件的具体操作,包括改名前后对照、哈希值、移动路径。

日志文件单独写,格式是纯文本,每行包含时间、级别、模块、动作、文件路径。写日志的理由不只是事后排查,更重要的是让你能回滚。比如有一天你发现一批文件被误分类,可以读日志把改动反向恢复。日志文件名带上日期,按天滚动,避免单文件无限膨胀。

除了日志,我还会在结束时输出一张摘要表:

阶段成功跳过异常
扫描2345012
分类2320187
过滤18021400
重命名96713734
归档228005

这个摘要让整个流程变得透明,不是黑盒操作。我在检查异常项时发现,大批量整理的心理压力主要来自“不知道发生了什么”,有摘要之后,排查思路会清晰很多。

3. 实操过程:从零搭一个迷你版

3.1 项目结构与初始化

为了让你能直接抄作业,我把核心项目的结构列出来:

asffsafasf3/ asffsafasf3.py # 主入口 scanner.py # 扫描模块 classifier.py # 分类模块 filter.py # 过滤模块 renamer.py # 重命名模块 archiver.py # 归档模块 validator.py # 哈希校验模块 config.json # 规则配置 run.sh # Linux/macOS 启动脚本 run.bat # Windows 启动脚本

初始化时我做了两件事:第一,先创建临时目录和工作目录,保证脚本中途失败也不会污染原始数据。第二,加载配置并校验配置项是否齐全,比如出现错误我会直接给出缺失字段名,而不是等到运行时崩溃。

跑起来之前,记得用虚拟环境把项目隔离,避免依赖混乱。虽然这个工具只需要Python标准库,我还是建了venv,为后续加第三方库留空间。初始化命令就这几行:

mkdir asffsafasf3 cd asffsafasf3 python3 -m venv venv source venv/bin/activate

在Windows上激活命令改成venv\\Scripts\\activate,这是一个经常被问到的细节。

3.2 核心脚本实现

我不建议你照抄全部代码,关键逻辑拆开看更有价值。先说扫描模块,它的核心是遍历目录并返回文件清单:

import os from pathlib import Path def scan_directory(root: Path) -> list: results = [] for dirpath, dirnames, filenames in os.walk(root): for name in filenames: full_path = Path(dirpath) / name try: stat = full_path.stat() except OSError: continue results.append({ 'path': full_path, 'size': stat.st_size, 'mtime': stat.st_mtime, 'ext': full_path.suffix.lower(), }) return results

os.walk会递归访问所有子目录,这里需要注意权限错误。我在处理网络磁盘时经常遇到某些目录无法访问,如果直接让异常抛出,整个扫描会中断。所以这里捕获了OSError,跳过无法访问的目录,同时在日志里记录。

分类模块用文件头识别真实类型:

def sniff_type(path: Path) -> str: with open(path, 'rb') as f: head = f.read(8) if head[:4] == b'%PDF': return 'pdf' if head[:2] == b'\xff\xd8': return 'jpeg' if head[:4] == b'\x89PNG': return 'png' return None

这里的关键逻辑是:先看扩展名,如果文件头识别出的类型和扩展名不一致,标记为“可疑”。在归档时,可疑文件单独存放,不参与正常分类。用这个方法,我成功找出很多下载后损坏的PDF,它们实际上是HTML错误页。

重命名模块里包含日期提取和非法字符清洗:

import re def clean_name(name: str) -> str: name = re.sub(r'[\\/*?:"><|]', '_', name) return name.strip() def build_new_name(meta: dict, rule: str) -> str: # rule 类似:{client}_{date}_{original} client = meta.get('client', 'unknown') date = meta.get('date', '20240101') original = clean_name(meta.get('original', 'file')) return rule.format(client=client, date=date, original=original)

这段代码最大的坑是日期提取。不同来源的文件日期格式五花八门,有2024-06-01,有20240601,还有06012024。我在前两个版本里试图写通用解析,后来放弃,改为让用户手动在配置里指定日期格式,反而更可靠。另外建议所有重命名都保留原始文件名的一部分,它是后续回溯的重要线索。

3.3 配置示例与参数计算

配置文件config.json是我整个工具的“大脑”,核心内容如下:

{ "scan_depth": -1, "file_size_limit_mb": 2048, "filter": { "bad_extensions": [".tmp", ".bak", ".crdownload", ".part"], "min_size_bytes": 1024, "hash_duplicate": true }, "rename_rules": { "contract": "{client}_{date}_{original}", "image": "{date}_{original}" }, "archive_structure": "type/year/month", "quarantine_days": 30 }

参数不是随手填的,我说下计算依据。min_size_bytes设成1024,是因为低于1KB且为图片格式的文件大概率是网页图标或碎片,放进正常归档没有意义。file_size_limit_mb设成2048,是考虑到大文件会拖慢哈希校验速度,超过这个阈值的文件直接跳过重命名,只做分类和归档。

哈希校验的耗时估算很重要。SHA-256的处理速度大概在每秒300MB到1GB之间,取决于磁盘和CPU。如果批量整理200GB数据,全量哈希要跑5到10分钟。所以我做了个折中:超过2GB的文件不计算全量哈希,只记录大小和修改时间,因为整理脚本本来就不动内容,风险远小于主动复制场景。

3.4 真实跑一遍的输出演示

我拿真实目录模拟过一次,原始数据是一个混合了1250个文件的目录,其中包含大量缓存文件、重复图片和合同PDF。

执行命令:

python asffsafasf3.py --input ./mixed_dir --output ./clean_dir --config config.json --dry-run --verbose

输出摘录:

[SCAN] total files: 1250, total size: 3.8GB [FILTER] quarantined: 83 tmp files (12.4MB) [DUPLICATE] group: 6 files -> kept 1 (based on earliest mtime) [RENAME] 20240601_customerA_scan001.pdf -> ./clean_dir/documents/202406/20240601_customerA_scan001.pdf [ARCHIVE] moved 892 files, conflict: 4, skipped: 12 [SUMMARY] success: 892, conflict: 4, quarantine: 83, skipped: 12

我看到conflict: 4的时候,检查了日志。其中2个是真正的同名不同内容文件,1个是完全一样的重复文件,还有1个是文件名相同但扩展名大小写不同。这个排查过程让我意识到,排除冲突必须看哈希,不能只看文件名,否则就会在不知情的情况下丢弃有效文件。

如果你自己测试,务必多跑dry-run。dry-run模式下所有移动和重命名都只打印结果,不执行。我第一次跑真实模式前大概跑了十几次空转,把误分类规则改到满意才开始正式操作。

4. 常见问题与排查技巧

4.1 编码问题导致文件名乱码

这个坑在Linux和macOS上尤其明显。文件名编码不统一,有些是老系统遗留的GBK,有些是UTF-8,Python在遍历时可能直接抛出UnicodeEncodeError。

我的排查思路是:在扫描阶段不管编码,先把文件名以surrogateescape方式读入,输出和归档时才做编码转换。日志文件一律用UTF-8写。当你看到乱码时,不代表文件本身坏了,很可能只是终端解码问题和文件名字节不一致。处理办法是保留原始字节,通过os.fsencode和os.fsdecode来回转,只在显示时强行解码。

如果你只是自己用,更稳妥的方案是:在重命名时把文件名统一转成ASCII字符,比如拼音或者日期序号。虽然可读性差一点,但跨平台兼容最好,也彻底避免乱码问题。

4.2 跨平台文件名的兼容性问题

Windows对文件名的限制比Linux严格得多,所以我默认按Windows规则清洗所有平台的输出。常见禁忌字符是\/:*?"<>|,还有尾部空格和点号,Windows会自动去掉它们,导致重命名结果和预期不一致。

解决办法是写完路径后统一做一次校验,使用Path.name检查是否等于清洗后的值,不等则继续替换。此外,保留字比如CON、PRN、AUX也是坑,Windows不会让你建这些名字,我在配置里加了一个保留字列表,命中就直接加前缀x_。

4.3 正则写错导致批量误命名

有一段时间,我的重命名规则用正则提取客户编号,规则是r'([A-Z]{3}-\\d{4})'。结果某天碰到一个文件夹,里面文件名包含好几个匹配片段,程序匹配到了最后一段,导致多份文件被命名成同一个客户编号。表面看是执行成功,实际归档后全部混在一起。

排查方法有两个:第一个是匹配后立刻打印命中内容,肉眼确认没有异常;第二个是写正则时加上起始边界,比如r'\\b([A-Z]{3}-\\d{4})\\b',防止从单词中间截取。更稳妥的做法是,正则匹配结果一定要做唯一性校验,同一个新名字只能对应一个原文件,否则进冲突列表。

4.4 性能瓶颈与大文件处理

当单个文件很大时,哈希计算会显著拖慢流程。另一个隐藏问题是小文件太多,os.walk逐个stat也会慢。我实测过,10万个文件的目录,纯扫描就要两三分钟,后续每阶段再遍历一遍,总时长会拉到十分钟以上。

我做了三个优化:

  1. 扫描结果持久化到本地索引文件,二次运行时只增量扫描。
  2. 哈希计算用多进程,按CPU核心数分配,每个进程处理一批文件。
  3. 对同一类型的文件集中处理,减少目录切换开销。

多进程代码不要一上来就整复杂,先用concurrent.futures.ProcessPoolExecutor,改造成本很低,效果明显。如果文件数量不大,单线程完全够用,没必要为了炫技上多进程。

5. 复盘与个人体会

5.1 我踩过的坑

第一个大坑是最开始没做隔离目录。有一版脚本在处理重复文件时用了“删除保留一份”的策略,结果日志显示删错了文件,原因是我按修改时间排序,以为最早的是原版,但其实那个文件是旧版本,我想保留的反而是新修改的。后来我彻底改掉删除策略,所有疑似重复文件进隔离区,由人决定,不再自动删除。

第二个坑是正则提取文件头时,过度信任扩展名。有一个CSV文件,实际上是个Excel表格,扩展名被手动改过。分类模块按扩展名判断成文档,归档到文本类目录,然后某步统计时才发现内容类型不对。现在所有关键文件头都要做魔数探测,少于8字节的识别逻辑全部不信任。

第三个坑是测试时没有用真实业务数据。我最初用临时生成的一堆假文件测试,文件名规则干净得很。真正跑业务目录时,遇到了中文括号、全角字符、特殊空格、缩进的制表符,各种情况都冒出来。后来我在测试集里专门放了“脏文件包”,从真实目录里复制出来一批,保证测试贴近实际。

5.2 后续扩展方向

这个工具我计划继续扩展两个方向。第一个是加规则热加载,修改config.json后不用重跑整个流程,进程常驻化,监听配置文件变化后自动增量执行。第二个是加一个交互式审查界面,当遇到冲突时,在终端里列出多个候选选项,手动选择保留哪个,而不是只能丢到冲突目录等待人工处理。

如果你只是想解决自己电脑文件乱的问题,不一定要全部照搬我的代码。先把扫描、分类、重命名、归档这四个环节想清楚,再写一个最小可用版本,哪怕只有一百行代码,也比一个永远在规划中的大系统有用得多。ASFFSAFASF3这个名字虽然乱,但它的设计原则很明确:每次只做一件小事,每件事都留日志,所有危险操作都能回滚。按这个思路去写你自己的文件整理工具,大概率不会重蹈我前两个版本的覆辙。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询