在日常开发中,桌面或下载目录常常堆满各种后缀的文件:PDF、图片、压缩包、代码文件混在一起,找文件时非常低效。利用 Python 内置的 os、shutil 和 pathlib 模块,可以写一个按后缀名自动归档的小脚本,不仅不依赖第三方库,还能避免同名覆盖,并提供 dry_run 预览模式,适合作为 Python 文件处理入门的练手项目。
一、设计思路
核心逻辑很简单:遍历目标目录下的文件,提取每个文件的后缀名,根据预设的映射表把文件移动到对应分类文件夹中。如果分类文件夹不存在则自动创建;如果目标位置已有同名文件,则自动追加序号防止覆盖。为了安全,先通过 dry_run 模式只打印移动计划,确认无误后再真正执行移动。
二、环境与使用前提
脚本使用 Python 3 运行,只需标准库,无需 pip 安装额外包。适用于 Windows、macOS、Linux 桌面环境。运行时需要确保对目标目录有读写权限,文件被占用时可能跳过并记录错误。
三、完整代码实现
- import os
- import shutil
- from pathlib import Path
- # 文件类型分类规则
- FILE_CATEGORIES = {
- # 文档类
- '.pdf': 'PDF文档',
- '.doc': 'Word文档',
- '.docx': 'Word文档',
- '.wps': 'Word文档',
- # 表格类
- '.xls': 'Excel表格',
- '.xlsx': 'Excel表格',
- '.csv': 'Excel表格',
- '.tsv': 'Excel表格',
- # 演示类
- '.ppt': 'PPT演示',
- '.pptx': 'PPT演示',
- # 图片类
- '.jpg': '图片',
- '.jpeg': '图片',
- '.png': '图片',
- '.gif': '图片',
- '.bmp': '图片',
- '.svg': '图片',
- '.webp': '图片',
- '.ico': '图片',
- '.tiff': '图片',
- # 视频类
- '.mp4': '视频',
- '.avi': '视频',
- '.mov': '视频',
- '.mkv': '视频',
- '.flv': '视频',
- '.wmv': '视频',
- # 音频类
- '.mp3': '音频',
- '.wav': '音频',
- '.flac': '音频',
- '.aac': '音频',
- '.ogg': '音频',
- # 压缩包类
- '.zip': '压缩包',
- '.rar': '压缩包',
- '.7z': '压缩包',
- '.tar': '压缩包',
- '.gz': '压缩包',
- # 代码类
- '.py': '代码文件',
- '.js': '代码文件',
- '.java': '代码文件',
- '.c': '代码文件',
- '.cpp': '代码文件',
- '.html': '代码文件',
- '.css': '代码文件',
- # 其他
- '.txt': '文本文件',
- '.md': '文本文件',
- '.exe': '可执行程序',
- '.msi': '可执行程序',
- '.dmg': '可执行程序',
- }
- def organize_files(target_dir, dry_run=False):
- """
- 按文件后缀名自动归类文件夹中的文件
- 参数:
- target_dir: 要整理的目录路径
- dry_run: True=只预览不移动,False=执行移动
- """
- target_path = Path(target_dir)
- if not target_path.exists():
- print(f"错误:目录 {target_dir} 不存在!")
- return
- moved_count = 0
- skipped_count = 0
- errors = []
- # 遍历目录中的所有文件(不递归子文件夹)
- for file_path in target_path.iterdir():
- if file_path.is_dir():
- continue
- # 获取文件后缀并统一小写,避免 .PDF 和 .pdf 被分开
- suffix = file_path.suffix.lower()
- category = FILE_CATEGORIES.get(suffix, '其他文件')
- # 创建分类文件夹
- category_dir = target_path / category
- category_dir.mkdir(exist_ok=True)
- # 构造目标路径
- dest = category_dir / file_path.name
- # 如果目标已存在,加序号避免覆盖
- if dest.exists():
- stem = file_path.stem
- counter = 1
- while dest.exists():
- dest = category_dir / f"{stem}_{counter}{suffix}"
- counter += 1
- # 执行移动或预览
- if dry_run:
- print(f"[预览] {file_path.name} → {category}/")
- moved_count += 1
- else:
- try:
- shutil.move(str(file_path), str(dest))
- print(f"[已移动] {file_path.name} → {category}/")
- moved_count += 1
- except Exception as e:
- errors.append(f"{file_path.name}: {e}")
- skipped_count += 1
- # 汇总报告
- print(f"\n{'='*40}")
- print(f"整理完成!")
- print(f"移动文件: {moved_count} 个")
- if skipped_count > 0:
- print(f"跳过文件: {skipped_count} 个")
- if errors:
- print(f"\n错误列表:")
- for err in errors:
- print(f" - {err}")
- if __name__ == "__main__":
- # 先预览:organize_files(r"C:\Users\用户名\Desktop", dry_run=True)
- # 再执行:organize_files(r"C:\Users\用户名\Desktop", dry_run=False)
- organize_files(r"D:\Downloads", dry_run=False)
复制代码
四、关键实现细节
1. 用 pathlib 替代 os.path 简化路径处理
Path(target_dir) 生成路径对象后,可以直接用 / 拼接路径,用 .suffix 获取扩展名,用 .name 取文件名,比 os.path 更简洁。例如 f.suffix 等价于 os.path.splitext(f)[1],f.name 等价于 os.path.basename(f)。
2. 后缀统一转小写
file_path.suffix.lower() 能避免 .PDF 和 .pdf 因大小写不同而被分到不同文件夹。这是实际操作中很常见的问题,Windows 大小写不敏感,但 macOS 和 Linux 大小写敏感。
3. 防覆盖处理
当同名文件已经存在于目标分类目录时,脚本会基于原文件名的主干部分追加序号:比如“报告.pdf”变成“报告_1.pdf”,如果还有冲突再继续尝试“报告_2.pdf”。这个逻辑通过 while dest.exists() 循环实现,避免了 shutil.move 直接覆盖同名文件导致的数据丢失。
4. dry_run 安全预览
dry_run=True 时只打印每个文件的移动去向,不执行任何移动操作。建议首次运行先打开预览,确认分类规则符合预期再真正执行。
五、扩展技巧
技巧1:按文件大小二次归类
可以在移动前读取 file_path.stat().st_size,按照字节数划分“小文件(<1MB)”“中等文件(1-100MB)”“大文件(>100MB)”,然后拼接到分类文件夹名中,实现二级分类。
技巧2:按创建月份归档
使用 datetime.fromtimestamp(file_path.stat().st_ctime) 获取文件创建时间,再用 strftime('%Y-%m') 生成如“2025-03”的月份文件夹,适合按时间维度整理日志或截图。
技巧3:清理整理后的空文件夹
在移动完成后,可以用 os.walk 从下往上遍历,删除所有既没有文件也没有子目录的空目录,保持目录结构干净。
六、常见问题与处理
Q1:移动后找不到文件?
文件名没有改变,只是被移动到了目标目录下的分类子文件夹中。在移动前先运行 dry_run=True 可以避免误操作。
Q2:提示“文件正在使用中”?
原文件可能被其他程序打开或占用。关闭相关程序后重新运行;或者从脚本输出的错误列表中找到对应文件,手动处理。
Q3:想忽略 .lnk、.ini 这类后缀?
在遍历循环开头增加判断即可:- if file_path.suffix.lower() in ('.lnk', '.ini', '.tmp'):
- continue
复制代码
七、总结
这个脚本的核心操作可以概括为四步:获取小写后缀、查映射表得到分类名、创建分类目录、用 shutil.move 移动文件。加上防覆盖序号和 dry_run 预览后,已经能安全应对绝大多数桌面整理场景。你还可以根据个人习惯自由增删 FILE_CATEGORIES 中的映射关系,比如把 .log 归入文本文件,把 .psd 归入图片,让整理规则更符合自己的工作流。 |