ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

用Python整理下载文件夹:从pathlib到异常处理的实战与笔记启示

用Python整理下载文件夹:从pathlib到异常处理的实战与笔记启示 1. 写在开篇第十七天是笔记方法的分水岭2026年1月6日凌晨我写完第十六天的笔记顺手想从下载文件夹里找一份PDF结果花了四分钟。里面什么都有前年下载的软件安装包、好几个版本的简历、手机备份的图片、一堆命名混乱的压缩包甚至还有一个我自己都没印象的视频。那一刻我突然意识到过去十六天我一直在认认真真记录“今天学了什么”但对“学的东西能拿来干什么”几乎没有做过任何输出。所以“第十七天笔记2026.1.6”这个标题对我来说不是一次普通的日期记录而是调整学习方式的起点从“输入式笔记”转向“输出式实践”。第十六天之前我的笔记内容大多是知识点摘录、教程关键词、收藏夹里的链接看起来密密麻麻实际上隔一天再翻根本想不起那些概念到底怎么用。从第十七天开始我给自己定了一条规矩每天必须交出一个能运行的、能解决真实问题的小产物。这篇文章就是那个小产物的完整复盘。我会写清楚为什么把目标锁定在“用Python整理下载文件夹”、需求怎么拆解、代码怎么写、运行过程中踩了哪几个坑以及这次实践如何改变了我记笔记的方法。如果你正在自学Python却总觉得自己卡在“会看不会写”的状态这篇文章应该对你有用如果你和我一样用“每日记录”对抗学习焦虑从第四章开始的内容更值得多看几遍。2. 今天要交的作业用Python写一个下载文件夹整理器定这个课题之前我其实列了三四个候选做个人记账小工具、写爬虫抓公开数据、或者做一个简单网页。最终都被我否掉了原因主要不在于技术难度而是“离日常生活太远”。记账工具要处理很多财务细节做着做着很容易陷入需求膨胀爬虫涉及反爬规则和网络请求的各种异常对一个只有十七天编程基础的新手来说变量实在太多网页开发虽然有意思但调试环境和部署又是一套新体系。整理下载文件夹不一样它面向的是每天都在发生的真实痛点文件越堆越多想找某个文件时只能靠记忆。需求足够简单、结果立竿见影还能顺带把Python的路径处理、文件操作、异常处理这些基本功全部练一遍属于典型的“小切口、深练习”。2.1 为什么不用现成工具非要自己写市面上能整理文件的工具并不少有些还自带图形界面一键就能扫描。选择自己写有两个原因。第一我正处在“学而不会用”的阶段每天看教程都觉得自己懂了一关掉页面就大脑空白必须有一个自己握得住的需求来验证到底学会了什么。用现成工具解决的是“文件乱”的问题解决不了“我不会写代码”的问题。第二现成工具往往带着一堆预设规则什么文件归什么类型、按什么目录存放都是别人定好的。自己写则可以从自己的习惯出发。比如我下载的Markdown文档不希望和Word文件混在一起想单独建一个“md笔记”目录比如安装包里面有些是绿色软件有些需要双击安装我想直接按后缀命名这类个性化需求现成工具未必支持。2.2 需求拆解一个整理器到底该干什么动手写代码前我先把需求写成了人话而不是直接开写。这一步很重要因为目标不清晰的时候写着写着就容易加功能最后变成一辆没有刹车的车。扫描指定文件夹里的所有文件根据扩展名把文件分成“图片、文档、压缩包、安装包、代码、音频、视频、其他”几大类在目标目录下创建对应分类文件夹遇到重名文件时自动改名绝不能覆盖原文件跳过还没下载完的临时文件比如.crdownload、.part只在屏幕上打印关键信息并把操作记录存进一份日志文件前两条是核心功能后四条是我在写之前就隐约预料会出问题的边界条件。事实证明最后踩的坑也基本集中在边界条件上。这也算是我那几天最大的体会新手写脚本功能很快就能跑通真正考验人的永远是各种“特殊情况”。分类规则一开始我做得特别细结果发现自己给自己挖坑。视频格式那么多、音频格式也那么多靠手工一个一个枚举永远列不完。最后我采用的办法是保留常见后缀作为第一层映射再用一个“其他”目录兜底。分类目录扩展名示例图片.jpg .jpeg .png .gif .webp .bmp .svg文档.pdf .doc .docx .txt .md .xlsx .pptx压缩包.zip .rar .7z .tar .gz安装包.exe .msi .dmg .pkg .apk代码.py .js .ts .java .c .cpp .css .html音频.mp3 .wav .flac .aac .ogg视频.mp4 .mkv .avi .mov .flv其他无法匹配以上任何规则的扩展名这种“白名单兜底”的思路在后面很多项目里都帮了我大忙。不要试图把所有情况都写进代码只把能预期到的常见情况写进去剩下的交给兜底分支这样代码会简洁很多也更容易维护。2.3 代码骨架为什么我坚持用 pathlib 而不是 os.path下面这段代码是可运行的样子。我没有用任何第三方重依赖全程只用了Python标准库唯一额外引入了send2trash——这是为了删除文件时能进回收站而不是直接消失安全第一。如果你不想装它直接改用os.remove也行但那就真的“删了不回头”了。#!/usr/bin/env python3 # -*- coding: utf-8 -*- download-helper.py 把指定文件夹中的文件按扩展名分类归档。 用法: python download-helper.py [扫描路径] [归档路径] import sys import time import shutil import logging from pathlib import Path from collections import defaultdict try: from send2trash import send2trash except ImportError: send2trash None DEFAULT_RULES { 图片: {.jpg, .jpeg, .png, .gif, .webp, .bmp, .svg}, 文档: {.pdf, .doc, .docx, .txt, .md, .xlsx, .pptx}, 压缩包: {.zip, .rar, .7z, .tar, .gz}, 安装包: {.exe, .msi, .dmg, .pkg, .apk}, 代码: {.py, .js, .ts, .java, .c, .cpp, .css, .html}, 音频: {.mp3, .wav, .flac, .aac, .ogg}, 视频: {.mp4, .mkv, .avi, .mov, .flv}, 其他: set(), } SKIP_SUFFIXES {.crdownload, .part, .tmp} def resolve_dest_dir(file_path: Path, target_root: Path) - Path: suffix file_path.suffix.lower() if suffix in SKIP_SUFFIXES: return None for category, suffix_set in DEFAULT_RULES.items(): if suffix in suffix_set: return target_root / category return target_root / 其他 def unique_path(path: Path) - Path: if not path.exists(): return path stem path.stem suffix path.suffix parent path.parent counter 1 while True: new_path parent / f{stem}_{counter}{suffix} if not new_path.exists(): return new_path counter 1 def move_file(file_path: Path, target_root: Path, dry_run: bool False): dest_dir resolve_dest_dir(file_path, target_root) if dest_dir is None: return skip dest_dir.mkdir(parentsTrue, exist_okTrue) dest_path unique_path(dest_dir / file_path.name) if dry_run: print(f[DRY RUN] {file_path} - {dest_path}) return dry_run shutil.move(str(file_path), str(dest_path)) print(f[MOVE] {file_path} - {dest_path}) return moved def main(): source_dir Path(sys.argv[1]) if len(sys.argv) 1 else Path.home() / Downloads target_root Path(sys.argv[2]) if len(sys.argv) 2 else Path.home() / Downloads_Archive dry_run --dry-run in sys.argv logging.basicConfig( filenamePath.home() / download_helper.log, levellogging.INFO, format%(asctime)s %(levelname)s %(message)s, ) target_root.mkdir(parentsTrue, exist_okTrue) stats defaultdict(int) for file_path in source_dir.iterdir(): if not file_path.is_file(): continue if file_path.resolve().parent target_root.resolve(): continue result move_file(file_path, target_root, dry_rundry_run) stats[result] 1 logging.info(%s | %s, result, file_path) print(完成。统计结果:, dict(stats)) if __name__ __main__: main()有人可能会问现在网上大量教程还在用os.path处理路径为什么这里坚持用pathlib.Path。我的理由是它的可读性和表达能力实在好太多。Path(/User/tom/Downloads) / 图片可以直接拼接路径不用反复写os.path.join.suffix直接返回扩展名.stem返回不带扩展名的文件名.iterdir()也比os.listdir配合os.path.isfile少写好几行。对刚入门的人来说少写代码就是少出错心智负担会小很多。2.4 第一次顺利运行的体验写完代码后我在测试文件夹里放了几十种类型的文件不同后缀的图片、压缩包、文档、安装包甚至还有一个乱命名成.txt的Python脚本。第一次运行时大部分文件都成功归位控制台连续打印十几行[MOVE]那种一个个文件被准确放进对应目录的感觉非常直观比看任何教程都有成就感。我刻意保留了这种“能看到过程”的打印方式没有为了界面干净把所有输出都关掉。原因很简单第一版逻辑还很糙程序如果出错我能从输出里一眼看出是哪个文件、哪一步出了问题。等以后逻辑稳定了再改成只打印错误信息也不迟。新手做工具先让自己看得见再追求好不好看。3. 我踩过的三个坑和完整排查链路真正让第十七天这篇笔记有含金量的其实不是顺利运行而是接下来这段排查过程。没有这些报错我可能一个星期后就忘掉了这个脚本但把每个问题都查了一遍之后Python的文件操作、编码机制、异常处理在我脑子里变成了具体的画面而不是干巴巴的文档段落。3.1 坑一中文文件名在控制台输出时直接报编码错误第一版程序处理一个名叫“《年度总结》最终版 2025.docx”的文件时控制台突然冒出一行UnicodeEncodeError: gbk codec cant encode character \u2018 in position ...。我第一反应是文件名有问题后来仔细看了半天才反应过来这是Windows控制台默认编码的问题不是文件名本身有问题。排查链路是这样的先把文件名单独打印发现只要字符串里有中文引号或者特殊符号print就会报错再去检查系统环境变量确认当前控制台用gbk编码最后在启动脚本时临时加上PYTHONIOENCODINGutf-8问题立刻消失。为了以后换台电脑不再踩同样的坑我在脚本开头专门加了一段重定向标准输出编码的逻辑。下面这段代码就是用来处理编码问题的放在脚本开头即可import sys if sys.stdout.encoding and sys.stdout.encoding.lower() not in (utf-8, utf8): sys.stdout.reconfigure(encodingutf-8, errorsreplace)这里我特意选errorsreplace而不是errorsignore。忽略字符会让输出不完整排查问题时反而少信息替换成?至少能看出原位置发生了什么后续真要定位问题也能判断是哪个字符出了岔子。3.2 坑二未下载完成的文件被跳过但报错信息让人误解这个坑比编码问题隐蔽得多。我虽然用SKIP_SUFFIXES跳过了.crdownload、.part、.tmp但实测时仍然会碰到PermissionError。打开浏览器设置后我发现当前浏览器的下载中间文件后缀并不在常见清单里它会生成一套随机字母组合的临时文件而且文件依然处于被写入的状态移动它必然权限不足。排查思路是从异常本身出发的。我没有把PermissionError直接“硬屏蔽”而是先打印文件路径和异常信息观察一段时间。随后发现规律出问题的文件几乎都存在“正在被写入”的特征。于是我做了两层处理第一把已知的未完成后缀全部加进跳过名单第二遇到PermissionError不让程序炸掉而是做三次重试三次都失败就放进“待处理”名单跳过去继续处理下一个文件。def safe_move(file_path: Path, target_root: Path): for attempt in range(3): try: return move_file(file_path, target_root) except PermissionError: if attempt 2: time.sleep(1) else: print(f[SKIP] 文件被占用: {file_path}) logging.warning(PermissionError for %s, file_path) return busy return failed这个改动在代码量上很小但意义很大它把“整个程序崩溃”变成了“单个文件跳过程序继续跑完”。下载目录里永远会有临时文件出现与其因为一个文件退出还不如让它带着完整日志跑完最后一个目录回头再看哪些文件没处理成功。3.3 坑三第二次运行时脚本把上一次归档出的目录又当成处理对象这个问题触发得非常隐蔽。我原本把扫描目录设成Downloads归档目录设成Downloads_Archive而且后者正好也放在Downloads目录下。第二次运行时脚本扫描到Downloads_Archive文件夹发现它不是文件走的是if not file_path.is_file(): continue理论上应该会跳过目录。但实际上如果目录里面某个文件是直接放在Downloads_Archive根目录下的那么按当前扫描逻辑它会被当成普通文件再次移动一次。解决办法是提前排除目标根目录。这里有个细节直接比较两个Path对象时如果一个是绝对路径、一个是相对路径可能比较失败所以先用.resolve()把两边都变成绝对路径再比较。同时我在move_file里保留了unique_path的逻辑万一真的发生重复移动也不会覆盖旧文件而是自动改成xxx_1、xxx_2这种名字。问题根因最终处理控制台中文报错Windows默认gbk编码不支持部分字符重定向stdout编码为utf-8文件被占用导致中断下载中的临时文件、权限不足跳过临时后缀 重试3次二次运行处理了归档目录扫描范围包含了输出目录扫描前排除目标根目录三个问题解决完脚本终于算是能在真实环境里稳定跑了。我把这段排查过程记在第十七天的笔记里过了几天再回头看仍然能一眼回忆起当时的思路比任何“文件操作十讲”都更有记忆点。4. 第十七天之后我调整了笔记记录的几个原则脚本写完我原本以为今天的学习就结束了。但那天晚上整理笔记时我发现我记录的东西和过去十六天完全不一样不再是一堆碎知识点而是一个完整项目从需求到代码再到排错的全过程。这种差别让我开始认真反思过去那种笔记方式为什么效率不高。4.1 从“我学了什么”变成“我做成了什么”过去我写笔记最爱干的事是摘抄教程里的定义、整理概念脑图、收藏一堆“以后再看”的链接。看似很努力实际上第二天什么都记不住。原因也简单这些内容只是经过了我的眼睛没有经过我的手。从第十七天开始我强迫自己把笔记的第一行写成“今天的交付物”。可以是“写了一个文件整理脚本”“修好了一个编码问题”“跑通了一个接口调用”。交付物越具体复盘时的线索就越清晰。拿第十七天来举例交付物就是“可自动归档下载文件夹的脚本”这个描述哪怕过一个月再看我也知道那天到底做了什么。4.2 错误记录比正确代码更值钱第二个原则和大多数新手直觉相反错误比正确更值得写。我在这篇笔记里完整记录了三次排查过程没有只贴一个最终可以运行的代码。事后证明这些错误记录是我最宝贵的复习素材。编码问题让我记住了Windows控制台的编码机制权限问题让我记住了临时文件的存在归档目录二次处理问题让我记住了扫描范围要排除输出目录。这三个坑每一个都比十行“优雅代码”更让我长记性。所以我也会建议你遇到报错先不要慌也别急着删掉报错信息把它完整抄进笔记里再写出排查过程。一个月后再翻这些就是别人付费也买不到的经验。4.3 连续比完美重要第三个原则是我反复提醒自己的宁可笔记短不要中断。第十七天之所以有它的意义是因为前面十六天没有断过。哪怕某天真的忙到只能写出三行话也应记录一行“今天做了什么”、一行“卡在哪里”、一行“明天打算试什么”。这里分享一个我正在用的“三行模板”你也可以直接抄走交付物今天做成了什么卡点遇到了什么具体问题下一步明天最想尝试的方向用这个模板之后复习成本变得特别低。回看一周前的笔记时只要扫一眼“卡点”那行就能快速判断自己当时卡在哪个环节。这种结构化笔记写起来快用起来也快非常适合用来维持长期记录的习惯。5. 下一步让第十七天的成果长成更大的东西脚本跑通之后我并没有停下来。因为需求还在下载文件夹每周仍然会产生新文件所以我想让这个工具从“手动运行”变成“自动运转”。接下来的计划是加一个配置文件把文件分类规则放到外部以后想改某个扩展名、添加新后缀不需要在代码里翻找直接编辑YAML或JSON就能生效。5.1 从手动运行到自动化再远一点的规划是增加定时任务。在Windows上可以用任务计划程序在macOS或Linux上可以写一个cron脚本。这样就不用总是想着手动执行系统会定期把下载目录整理干净。如果再加一个简单的tkinter界面让完全不懂命令行的人也能选个文件夹、点一下按钮就跑这个项目就可以真正交给家里人用了。自动化的关键不是代码本身而是“可预期的运行条件”。脚本需要知道什么时候扫描、扫描到哪里、归档到哪里、遇到占用文件怎么办。这些问题在手动运行场景下可以靠人临场判断但一旦交给定时任务就必须全部提前写清楚。这也是从“脚本”走向“小工具”必然要经历的一步。5.2 新学Python最需要补的一课按照我个人经验新学Python最危险的阶段不是“什么都看不懂”而是“什么都看了一点、什么都不会做”。不要等到掌握所有标准库再开始写项目哪怕只是做一个小脚本它也会逼你快速体会到编程的真实逻辑需求拆解、边界条件、报错、调试、重构。第十七天这篇笔记给我的最大改变是我开始用“今天能做到什么”来衡量学习进度而不是用“今天看了多少页文档”。如果这篇笔记对你有用我建议你也找一个反复困扰自己的小烦恼把它变成一个具体的小项目然后隔几天记录一次进度。等你和我一样写到第二十天、第三十天的时候手头积累的东西会完全不一样。我就是从第十七天写起才彻底想明白这件事的。
返回列表