首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >我写了个小工具,让文件下载目录再也不会变成垃圾场

我写了个小工具,让文件下载目录再也不会变成垃圾场

作者头像
用户11081884
发布2026-07-20 20:39:19
发布2026-07-20 20:39:19
270
举报

周五下午,我打开下载文件夹找个PDF合同。翻了三分钟没找到。

文件夹里躺着487个文件。图片、压缩包、安装包、文档、视频,全混在一起。按修改时间排序吧,那个PDF是两周前下载的,早就被淹没在几百个新文件底下。按名称排序更惨,一堆“download(1).pdf”、“新建文件(2).docx”之类的鬼名字。

最后我放弃了,重新让对方发了一份。

这事不是一次两次了。每次下载完东西,我都想着“待会整理”。待会是什么时候?从来没有。日积月累,下载文件夹就成了一个数字垃圾场。

我同事更夸张。他的下载文件夹有2000多个文件,找东西全靠搜索。有时候搜索也搜不到,因为文件名是一串随机字符。他试过整理一次,花了两个小时,分类到一半就放弃了——太多了,分着分着就忘了前面分过什么。

那天晚上我花了半小时,写了个Python脚本。现在下载任何东西,它会自动帮我分类。用了一个多月,下载文件夹再也没乱过。

用什么来解决

watchdog 这个库。它能监控文件夹变化,有新文件进来就触发事件。再加个JSON配置文件,想怎么分类自己定。

思路很简单:

  1. 监控下载文件夹
  2. 发现新文件
  3. 按扩展名或文件名,移到对应子文件夹

完整代码

先装依赖:

代码语言:javascript
复制
pip install watchdog

配置文件 config.json,放在脚本同目录:

代码语言:javascript
复制
{
  "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"
}

主程序:

代码语言:javascript
复制
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 函数:

代码语言:javascript
复制
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}/")

运行效果

启动脚本后,控制台会显示:

代码语言:javascript
复制
开始监控: C:/Users/YourName/Downloads
按 Ctrl+C 停止...

下载一个PDF文件:

代码语言:javascript
复制
✓ 已移动: 合同_2024.pdf → Documents/

下载一张图片:

代码语言:javascript
复制
✓ 已移动: screenshot.png → Images/

下载文件夹会自动变成这样:

代码语言:javascript
复制
Downloads/
├── Documents/
│   ├── 合同.pdf
│   └── 报告.docx
├── Images/
│   └── photo.jpg
├── Videos/
├── Archives/
│   └── project.zip
└── Others/

小结

这个脚本我跑了一个多月。下载文件夹终于不像垃圾场了。找文件从翻半天变成直接进对应文件夹,省的时间没法算,但心情好多了。

说几个实际使用中踩过的坑:

  1. 浏览器临时文件问题。Chrome下载大文件时会先创建一个 .crdownload 临时文件,下完才改名。脚本里已经处理了这个情况,遇到临时文件会跳过,等正式文件创建后再移动。
  2. 文件名重复。下载同名文件时,脚本会自动加时间戳后缀,不会覆盖。比如下载了两个“报告.pdf”,第二个会变成“报告_20240315_143022.pdf”。
  3. 配置热更新。改了 config.json 之后需要重启脚本才能生效。如果你想让它自动检测配置变化,可以再加一个文件监控,不过我觉得没必要,改配置的次数不多。

配置文件可以随便改。我后来加了几个规则:

  • 文件名包含“发票”的归到“财务”文件夹
  • .csv 文件归到“数据”文件夹
  • .apk 文件归到“安卓应用”文件夹

你也可以按自己需求加,JSON格式改起来很方便。

代码在后台跑着就行,开机自启动也行。我是用Windows任务计划程序设的开机启动,Mac和Linux可以用launchd或systemd。具体操作:Windows搜索“任务计划程序”,创建一个基本任务,触发器选“登录时”,操作选“启动程序”,程序填python路径,参数填脚本路径就行。

如果你觉得一直跑个进程太浪费资源,也可以换个思路:不实时监控,改成每天定时跑一次整理脚本。把 main() 函数里的 Observer 那段去掉,改成遍历目录里所有文件做一次性整理。配合定时任务,每天凌晨跑一次,效果也差不多。

“无他,惟手熟尔”!有需要的用起来!

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2026-07-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Nicholas与Pypi 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 用什么来解决
  • 完整代码
  • 运行效果
  • 小结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档