下载文件夹里300个文件躺成一片,找一张上周的截图要翻两分钟?
今天写一个“自动收纳师”,写完当天就能用上。
🎯本篇成品:一条命令按类型整理任意文件夹——图片、文档、压缩包各归各位,重名文件绝不覆盖。还带预览模式:先看它打算干什么,再决定让它干。含注释120行左右。
📌 太长不看版(给想快速上手的你)
| 项目信息 | 一句话说明 |
|---|---|
| 项目名称 | 下载文件夹整理助手(File Organizer) |
| 代码行数 | ~120行(含注释) |
| 依赖 | 零依赖,Python自带模块 |
| 核心功能 | 按扩展名自动归类 + 重名保护 + 预览模式 |
| 跑起来的命令 | python main.py ~/Downloads --dry-run(先预览再执行) |
| 技术点 | pathlib、shutil.move、argparse开关参数 |
| 做完你能得到 | 第一个能真正改变你电脑文件的工具 |
一、成品预览
$ python main.py ~/Downloads --dry-run [预览] 报告.pdf → 文档/ [预览] IMG_2026.jpg → 图片/ [预览] setup.exe → 安装包/ [预览] 神曲.mp3 → 音频/ [预览] 未命名.psd → 其他/ —— 预览 5 个文件,未做任何改动 —— (预览模式:未做任何改动;去掉 --dry-run 正式执行) $ python main.py ~/Downloads 已移动:报告.pdf → 文档/ 已移动:IMG_2026.jpg → 图片/ 已移动:setup.exe → 安装包/ 已移动:神曲.mp3 → 音频/ 已移动:未命名.psd → 其他/ —— 整理 5 个文件 —— $ ls ~/Downloads # Windows用dir 文档 图片 安装包 音频 其他🚀 和上一篇的待办清单比,这个项目特殊在哪?
⚠️它会动你磁盘上的真实文件。所以本篇除了代码,还有三个安全设计:
| 安全设计 | 作用 |
|---|---|
| 🛡️预览模式(dry-run) | 先看它打算干什么,确认无误再执行 |
| 🛡️重名不覆盖 | 两个报告.pdf→ 第二个自动变报告(1).pdf |
| 🛡️只动文件不递归 | 只整理当前层,不动子文件夹里的文件 |
这三个习惯,会让你以后写的每一个“会改东西”的脚本都值得信任。
二、前置知识:pathlib四件套 + 一个搬运工
上一项目见过Path,这次用它的四个核心技能:
| 技能 | 写法 | 说明 |
|---|---|---|
| 遍历文件夹 | folder.iterdir() | 逐个给出里面的文件和文件夹 |
| 判断 | item.is_dir()/dest.exists() | 是文件夹吗 / 路径存在吗 |
| 拆文件名 | item.suffix/item.stem | 报告.pdf→ 后缀".pdf"、名字"报告" |
| 换个名字 | dest.with_name("新名.pdf") | 同文件夹下改个名字 |
再配一个搬运工:shutil.move(源, 目标)—— 把文件从一个路径移到另一个路径。
⚠️注意:
item.suffix返回的后缀自带一个点(".pdf"而不是"pdf")。记住这个细节,第九节③有个著名的坑等着它。
三、第0步:setup(3分钟,流程已是老朋友)
1. 新建文件夹organizer,VSCode打开 2. python -m venv venv 3. 激活:windows: venv\Scripts\activate(cmd) | .\venv\Scripts\Activate.ps1(PowerShell) mac: source venv/bin/activate 4. git init,写好.gitignore 5. 新建空文件:main.py💡 零依赖项目,venv照建——习惯不因项目而变。
四、第1步:规则表——把"分类逻辑"写成一份数据
先别急着写功能代码。分类的本质是什么?查表:拿到扩展名,查它属于哪一类。那就把表写出来:
"""organizer —— 下载文件夹整理助手"""importargparseimportshutilfrompathlibimportPath# 扩展名 → 文件夹 的归类规则(想加类别,在这里加一行就行)RULES={"图片":[".jpg",".jpeg",".png",".gif",".webp",".bmp"],"文档":[".pdf",".docx",".doc",".txt",".md",".xlsx",".pptx"],"压缩包":[".zip",".rar",".7z",".tar",".gz"],"安装包":[".exe",".msi",".dmg",".pkg"],"视频":[".mp4",".mkv",".mov",".avi"],"音频":[".mp3",".wav",".flac",".m4a"],}defclassify(suffix:str)->str:"""查规则表:这个扩展名该进哪个文件夹;都不认识就进"其他" """forfolder,extsinRULES.items():ifsuffix.lower()inexts:# .lower() 让 .JPG 和 .jpg 一视同仁returnfolderreturn"其他"🎯 体会一下这个设计
| 好在哪里 | 说明 |
|---|---|
| 数据驱动 | 分类规则全在RULES里,classify只是查表的人 |
| 易扩展 | 以后想加“电子书”类别,改数据就行,一行逻辑代码都不用动 |
| 容错 | suffix.lower()让.JPG和.jpg一视同仁 |
💡把变化放进数据,把逻辑写成通用——这是好程序的第一个形状。
五、第2步:重名保护——数据无价(重点!)
如果不做任何保护,两个都叫报告.pdf的文件,后到的会悄悄覆盖先到的——没有任何提示,丢了就是丢了。
defunique_path(dest:Path)->Path:"""目标已存在就起个不重名的新名字:报告.pdf → 报告(1).pdf"""ifnotdest.exists():returndest stem,ext=dest.stem,dest.suffix n=1whileTrue:candidate=dest.with_name(f"{stem}({n}){ext}")ifnotcandidate.exists():returncandidate n+=1逻辑简单粗暴:
报告.pdf 被占了 → 试试 报告(1).pdf 报告(1).pdf 也被占了 → 试试 报告(2).pdf 直到没人用为止 ✅🚨 以后你写任何涉及移动、复制、写文件的代码,都先想想这句口诀:
📌写文件操作前,先问“会不会覆盖”。
六、第3步:主流程——遍历、跳过、预览或搬移
deforganize(folder:Path,dry_run:bool=False):"""主流程:遍历 → 分类 → 预览或搬移"""moved=0foriteminsorted(folder.iterdir()):# 跳过文件夹和隐藏文件(.DS_Store这类)ifitem.is_dir()oritem.name.startswith("."):continuetarget_dir=folder/classify(item.suffix)dest=unique_path(target_dir/item.name)ifdry_run:print(f"[预览]{item.name}→{target_dir.name}/")else:target_dir.mkdir(exist_ok=True)# 文件夹不存在就建shutil.move(str(item),str(dest))print(f"已移动:{item.name}→{target_dir.name}/")moved+=1action="预览"ifdry_runelse"整理"print(f"——{action}{moved}个文件 ——")ifdry_run:print("(预览模式:未做任何改动;去掉 --dry-run 正式执行)")📌 三个安全细节都在这一段里
| 安全点 | 代码 | 作用 |
|---|---|---|
| 不搬文件夹 | if item.is_dir(): continue | 包括我们自己生成的归类文件夹,不会搬自己家 |
| 不搬隐藏文件 | if item.name.startswith(".") | 跳过Mac的.DS_Store等系统文件 |
| 预览/执行分离 | if dry_run: | 同一个逻辑,只差“搬”这一个动作 |
🎯预览模式的精髓:计划与执行分离。先看计划,再决定是否执行。
七、第4步:argparse接上两个开关
defmain():parser=argparse.ArgumentParser(description="下载文件夹整理助手")parser.add_argument("folder",nargs="?",default=".",help="要整理的文件夹路径,默认当前目录")parser.add_argument("--dry-run",action="store_true",help="预览模式:只显示计划,不动任何文件")args=parser.parse_args()folder=Path(args.folder).expanduser()# ~ 自动展开成用户主目录ifnotfolder.is_dir():print(f"文件夹不存在:{folder}")returnorganize(folder,dry_run=args.dry_run)if__name__=="__main__":main()📌 两个新知识点
| 知识点 | 写法 | 效果 |
|---|---|---|
| 可选参数 | nargs="?"+default="." | 不传路径就整理当前目录 |
| 开关参数 | action="store_true" | 写了--dry-run它就是True,不写就是False |
| 路径展开 | .expanduser() | 把~翻译成你的主目录(/Users/你或C:\Users\你) |
八、第5步:验收——先沙盒,后实战(重点!)
🚨文件操作脚本有一条铁律:先在沙盒里验证,再上真实数据。千万别拿下载文件夹当小白鼠。
第一步:造一个沙盒并塞满假文件
VSCode终端执行:
# 建沙盒mkdirsandbox&&cdsandbox# 造假文件(Windows用 echo hi> 文件名)echohi>报告.pdfechohi>神曲.mp3echohi>setup.exeechohi>IMG_001.jpgechohi>未知.xyz# 回到项目根目录cd..第二步:沙盒里先预览再实跑
python main.py sandbox --dry-run# 看计划:5个文件各归各类python main.py sandbox# 正式执行lssandbox# 图片/ 音频/ 安装包/ 文档/ 其他/,全齐第三步:真实战场,先预览
python main.py ~/Downloads --dry-run盯着清单看10秒——有没有不该动的文件?确认无误,去掉--dry-run正式执行。
🎉 从此你的下载文件夹,一条命令治好。
九、常见报错:这6个,全是“动文件”脚本的高发区(重点!)
① 路径带空格被切断:C:\Users\my name\Downloads只认出了C:\Users\my
🔍 原因:空格是参数分隔符。
✅ 解法:整个路径套引号:
python main.py"C:\Users\my name\Downloads"②PermissionError: [Errno 13],移动到一半中断
🔍 原因:文件正被别的程序占用——PDF还开着、视频还在播放器里、下载还没完成。
✅ 解法:
- 关掉占用它的程序再跑;
- 本脚本可以放心重跑:已搬好的会跳过(在文件夹里,是目录),没搬的继续搬。
③ 所有文件都进了"其他"
🔍 原因:规则表里写的是"pdf",不是".pdf"——suffix自带点(.pdf),查表永远查不中。
✅ 解法:用断点法,盯一眼item.suffix的真实长相,和你规则表里的键对一对。
📌数据对不上,先看数据的真实形状。
④ 手机拍的.JPG全进了“其他”
🔍 原因:classify里忘了suffix.lower(),大写后缀查不到表。
✅ 解法:代码里已经防了;如果你自查,造一个.JPG假文件跑沙盒试试——这就是沙盒的第二个用处:给边界情况做回归测试。
⑤ “其他”文件夹里混着.DS_Store(Mac)
🔍 原因:macOS会往文件夹里塞隐藏配置文件,它们也是“文件”,会被遍历到。
✅ 解法:startswith(".")已经跳过它们。Windows的隐藏文件(desktop.ini)也可以按这个逻辑处理。
⑥ 自己写move没防重名,老文件被悄悄覆盖
🔍 原因:shutil.move(item, target_dir / item.name)遇到同名文件时,行为会因操作系统而异——覆盖或报错,都不受你控制。
✅ 解法:永远走unique_path。再念一遍口诀:
🚨写文件操作前,先问“会不会覆盖”。
十、课后练习
| # | 练习 | 难度 | 提示 |
|---|---|---|---|
| 1 | 加类别:给RULES加“电子书”(.epub/.mobi/.azw3)和“代码”(.py/.ipynb/.html) | ⭐ | 改数据不改逻辑,体会有多爽 |
| 2 | 安全升级:把默认行为改成“只预览”,加一个--yes参数才真正执行 | ⭐⭐ | action="store_true",逻辑反过来 |
| 3 | 自动化测试雏形:用循环造20个假文件,跑整理,断言各类别文件数正确 | ⭐⭐⭐ | Path(f"报告{i}.pdf").touch() |
| 4(选做) | 定时自动整理:Windows用“任务计划程序”,Mac用launchd,每天21:00自动执行 | ⭐⭐⭐⭐ | 务必先跑过--dry-run确认无误再上定时 |
📦 配套代码
完整main.py与沙盒测试脚本已上传GitHub(08-file-organizer/organizer):【https://gitee.com/xqshadow/python-project-in-practice】