
周五下午,我打开下载文件夹找个PDF合同。翻了三分钟没找到。
文件夹里躺着487个文件。图片、压缩包、安装包、文档、视频,全混在一起。按修改时间排序吧,那个PDF是两周前下载的,早就被淹没在几百个新文件底下。按名称排序更惨,一堆“download(1).pdf”、“新建文件(2).docx”之类的鬼名字。
最后我放弃了,重新让对方发了一份。
这事不是一次两次了。每次下载完东西,我都想着“待会整理”。待会是什么时候?从来没有。日积月累,下载文件夹就成了一个数字垃圾场。
我同事更夸张。他的下载文件夹有2000多个文件,找东西全靠搜索。有时候搜索也搜不到,因为文件名是一串随机字符。他试过整理一次,花了两个小时,分类到一半就放弃了——太多了,分着分着就忘了前面分过什么。
那天晚上我花了半小时,写了个Python脚本。现在下载任何东西,它会自动帮我分类。用了一个多月,下载文件夹再也没乱过。
用 watchdog 这个库。它能监控文件夹变化,有新文件进来就触发事件。再加个JSON配置文件,想怎么分类自己定。
思路很简单:
先装依赖:
pip install watchdog配置文件 config.json,放在脚本同目录:
{
"watch_dir": "C:/Users/YourName/Downloads",
"rules": [
{
"name": "文档",
"extensions": [".pdf", ".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx", ".txt"],
"target": "Documents"
},
{
"name": "图片",
"extensions": [".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp", ".svg"],
"target": "Images"
},
{
"name": "视频",
"extensions": [".mp4", ".avi", ".mkv", ".mov", ".wmv", ".flv"],
"target": "Videos"
},
{
"name": "压缩包",
"extensions": [".zip", ".rar", ".7z", ".tar", ".gz"],
"target": "Archives"
},
{
"name": "安装包",
"extensions": [".exe", ".msi", ".dmg", ".deb", ".rpm"],
"target": "Installers"
},
{
"name": "代码",
"extensions": [".py", ".js", ".html", ".css", ".json", ".xml", ".sql"],
"target": "Code"
}
],
"default_target": "Others"
}主程序:
import os
import json
import shutil
import time
from pathlib import Path
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
# 加载配置文件
def load_config(config_path="config.json"):
with open(config_path, "r", encoding="utf-8") as f:
return json.load(f)
# 根据文件名判断应该归类到哪个文件夹
def get_target_folder(filename, rules, default_target):
ext = Path(filename).suffix.lower()
# 遍历规则,匹配扩展名
for rule in rules:
if ext in rule["extensions"]:
return rule["target"]
# 没匹配到,用默认分类
return default_target
# 移动文件到目标文件夹
def move_file(file_path, watch_dir, target_folder):
# 构建目标路径
target_dir = os.path.join(watch_dir, target_folder)
# 如果目标文件夹不存在,创建它
if not os.path.exists(target_dir):
os.makedirs(target_dir)
# 目标文件路径
filename = os.path.basename(file_path)
target_path = os.path.join(target_dir, filename)
# 如果目标路径已存在,加个时间戳避免覆盖
if os.path.exists(target_path):
name, ext = os.path.splitext(filename)
timestamp = time.strftime("%Y%m%d_%H%M%S")
new_filename = f"{name}_{timestamp}{ext}"
target_path = os.path.join(target_dir, new_filename)
# 移动文件
shutil.move(file_path, target_path)
print(f"✓ 已移动: {filename} → {target_folder}/")
# 文件事件处理器
class DownloadHandler(FileSystemEventHandler):
def __init__(self, config):
self.config = config
self.watch_dir = config["watch_dir"]
self.rules = config["rules"]
self.default_target = config["default_target"]
def on_created(self, event):
# 只处理文件,不处理文件夹
if event.is_directory:
return
file_path = event.src_path
# 等待文件写入完成(有些大文件下载需要时间)
time.sleep(1)
# 检查文件是否还在(可能被浏览器临时占用)
if not os.path.exists(file_path):
return
# 跳过临时文件(浏览器下载时的临时文件)
filename = os.path.basename(file_path)
if filename.endswith((".tmp", ".crdownload", ".part")):
return
# 获取分类并移动
target = get_target_folder(filename, self.rules, self.default_target)
move_file(file_path, self.watch_dir, target)
# 主函数
def main():
# 加载配置
config = load_config()
watch_dir = config["watch_dir"]
# 检查监控目录是否存在
if not os.path.exists(watch_dir):
print(f"错误: 目录不存在 - {watch_dir}")
return
print(f"开始监控: {watch_dir}")
print("按 Ctrl+C 停止...")
# 创建事件处理器和观察者
handler = DownloadHandler(config)
observer = Observer()
observer.schedule(handler, watch_dir, recursive=False)
# 启动监控
observer.start()
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
print("\n停止监控")
observer.stop()
observer.join()
if __name__ == "__main__":
main()进阶版:按日期分子文件夹
如果你想按年月再细分,可以改一下 move_file 函数:
def move_file_by_date(file_path, watch_dir, target_folder):
# 获取当前日期
now = time.localtime()
date_folder = f"{now.tm_year}{now.tm_mon:02d}"
# 目标路径:Downloads/文档/202401/
target_dir = os.path.join(watch_dir, target_folder, date_folder)
if not os.path.exists(target_dir):
os.makedirs(target_dir)
filename = os.path.basename(file_path)
target_path = os.path.join(target_dir, filename)
if os.path.exists(target_path):
name, ext = os.path.splitext(filename)
timestamp = time.strftime("%Y%m%d_%H%M%S")
new_filename = f"{name}_{timestamp}{ext}"
target_path = os.path.join(target_dir, new_filename)
shutil.move(file_path, target_path)
print(f"✓ 已移动: {filename} → {target_folder}/{date_folder}/")启动脚本后,控制台会显示:
开始监控: C:/Users/YourName/Downloads
按 Ctrl+C 停止...下载一个PDF文件:
✓ 已移动: 合同_2024.pdf → Documents/下载一张图片:
✓ 已移动: screenshot.png → Images/下载文件夹会自动变成这样:
Downloads/
├── Documents/
│ ├── 合同.pdf
│ └── 报告.docx
├── Images/
│ └── photo.jpg
├── Videos/
├── Archives/
│ └── project.zip
└── Others/这个脚本我跑了一个多月。下载文件夹终于不像垃圾场了。找文件从翻半天变成直接进对应文件夹,省的时间没法算,但心情好多了。
说几个实际使用中踩过的坑:
.crdownload 临时文件,下完才改名。脚本里已经处理了这个情况,遇到临时文件会跳过,等正式文件创建后再移动。config.json 之后需要重启脚本才能生效。如果你想让它自动检测配置变化,可以再加一个文件监控,不过我觉得没必要,改配置的次数不多。配置文件可以随便改。我后来加了几个规则:
.csv 文件归到“数据”文件夹.apk 文件归到“安卓应用”文件夹你也可以按自己需求加,JSON格式改起来很方便。
代码在后台跑着就行,开机自启动也行。我是用Windows任务计划程序设的开机启动,Mac和Linux可以用launchd或systemd。具体操作:Windows搜索“任务计划程序”,创建一个基本任务,触发器选“登录时”,操作选“启动程序”,程序填python路径,参数填脚本路径就行。
如果你觉得一直跑个进程太浪费资源,也可以换个思路:不实时监控,改成每天定时跑一次整理脚本。把 main() 函数里的 Observer 那段去掉,改成遍历目录里所有文件做一次性整理。配合定时任务,每天凌晨跑一次,效果也差不多。
“无他,惟手熟尔”!有需要的用起来!
本文分享自 Nicholas与Pypi 微信公众号,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文参与 腾讯云自媒体同步曝光计划 ,欢迎热爱写作的你一起参与!