解放Mac存储空间!这个Python脚本让你秒变重复文件清理大师

内容分享5小时前发布
0 0 0

轻轻一点,重复文件无处遁形,安全高效地释放数十GB存储空间。

你是否也常常在Mac上遇到这样的困扰:下载的文件重复保存,照片库中充斥着类似图片,文档多个版本混乱不堪?不知不觉间,重复文件蚕食了你宝贵的存储空间。

今天,我们为Mac用户带来一个安全、智能、高效的重复文件清理解决方案!无需安装臃肿的清理软件,只需几行Python代码,就能让你的Mac重获新生。

一键式智能清理脚本

第一,确保你的Mac已安装Python 3。打开终端,输入以下命令安装所需工具:

pip3 install send2trash

然后,创建我们的智能清理脚本 clean_duplicates.py:

#!/usr/bin/env python3
"""
Mac重复文件安全清理工具
安全、智能、高效,释放你的存储空间
"""

import os
import time
import hashlib
import json
from pathlib import Path
import send2trash
from datetime import datetime

class MacDuplicateCleaner:
    """Mac专用重复文件清理器"""
    
    def __init__(self):
        self.file_hashes = {}
        self.duplicates = []
        self.scanned_files = 0
        self.start_time = time.time()
        
    def get_file_fingerprint(self, file_path, use_quick_hash=True):
        """
        获取文件指纹:大小 + 部分哈希
        兼顾准确性和性能
        """
        try:
            file_size = os.path.getsize(file_path)
            
            if use_quick_hash:
                # 快速模式:读取文件前1MB计算哈希
                with open(file_path, 'rb') as f:
                    # 小文件直接读取全部,大文件读取前1MB
                    sample = f.read(1024 * 1024) if file_size > 1024 * 1024 else f.read()
                    if sample:
                        return f"{file_size}_{hashlib.md5(sample).hexdigest()}"
            else:
                # 准确模式:完整文件哈希
                hash_md5 = hashlib.md5()
                with open(file_path, 'rb') as f:
                    for chunk in iter(lambda: f.read(8192), b""):
                        hash_md5.update(chunk)
                return f"{file_size}_{hash_md5.hexdigest()}"
                
        except (PermissionError, OSError, IOError):
            return None
    
    def scan_directory(self, directory, extensions=None, min_size_kb=10):
        """
        扫描目录查找重复文件
        """
        directory = os.path.expanduser(directory)
        if not os.path.exists(directory):
            print(f"⚠️  目录不存在: {directory}")
            return
        
        print(f" 正在扫描: {directory}")
        
        for root, dirs, files in os.walk(directory):
            # 跳过系统隐藏目录
            dirs[:] = [d for d in dirs if not d.startswith('.')]
            
            for file in files:
                # 跳过隐藏文件和系统文件
                if file.startswith('.') or file.startswith('._'):
                    continue
                
                file_path = os.path.join(root, file)
                
                # 过滤文件类型
                if extensions:
                    if not any(file.lower().endswith(ext) for ext in extensions):
                        continue
                
                # 过滤小文件
                try:
                    file_size_kb = os.path.getsize(file_path) / 1024
                    if file_size_kb < min_size_kb:
                        continue
                except OSError:
                    continue
                
                # 获取文件指纹
                fingerprint = self.get_file_fingerprint(file_path)
                if fingerprint is None:
                    continue
                
                self.scanned_files += 1
                
                # 检查是否重复
                if fingerprint in self.file_hashes:
                    original_file = self.file_hashes[fingerprint]
                    self.duplicates.append({
                        'original': original_file,
                        'duplicate': file_path,
                        'size_kb': file_size_kb,
                        'fingerprint': fingerprint
                    })
                    print(f"  发现重复: {file}")
                else:
                    self.file_hashes[fingerprint] = file_path
                
                # 显示进度
                if self.scanned_files % 100 == 0:
                    elapsed = time.time() - self.start_time
                    print(f"  已扫描 {self.scanned_files} 个文件,耗时 {elapsed:.1f}秒")
    
    def smart_review_duplicates(self):
        """
        智能审查重复文件
        """
        if not self.duplicates:
            print(" 祝贺!未发现重复文件")
            return
        
        print(f"
 扫描完成!发现 {len(self.duplicates)} 个重复文件")
        print("=" * 60)
        
        total_size_mb = sum(d['size_kb'] for d in self.duplicates) / 1024
        
        # 按文件类型分组
        file_groups = {}
        for dup in self.duplicates:
            ext = os.path.splitext(dup['duplicate'])[1].lower() or '无扩展名'
            if ext not in file_groups:
                file_groups[ext] = []
            file_groups[ext].append(dup)
        
        print("
 重复文件分类统计:")
        for ext, files in sorted(file_groups.items(), key=lambda x: len(x[1]), reverse=True):
            group_size = sum(f['size_kb'] for f in files) / 1024
            print(f"  {ext}: {len(files)} 个文件,约 {group_size:.1f} MB")
        
        print(f"
 可释放空间: {total_size_mb:.1f} MB")
        
        return total_size_mb
    
    def interactive_cleanup(self):
        """
        交互式清理重复文件
        """
        if not self.duplicates:
            return
        
        print("
️ 请选择清理模式:")
        print("  1. 自动模式(保留最新修改的文件)")
        print("  2. 手动选择(逐个确认)")
        print("  3. 仅查看,不删除")
        
        choice = input("请输入选项 (1/2/3): ").strip()
        
        removed_count = 0
        saved_space = 0
        
        if choice == '1':
            # 自动模式:保留最新修改的文件
            for dup in self.duplicates:
                try:
                    time_original = os.path.getmtime(dup['original'])
                    time_duplicate = os.path.getmtime(dup['duplicate'])
                    
                    # 删除较旧的文件
                    file_to_remove = dup['duplicate'] if time_duplicate < time_original else dup['original']
                    
                    print(f"️  删除: {os.path.basename(file_to_remove)}")
                    send2trash.send2trash(file_to_remove)
                    
                    removed_count += 1
                    saved_space += dup['size_kb']
                    
                except Exception as e:
                    print(f"❌ 删除失败: {e}")
        
        elif choice == '2':
            # 手动模式
            for i, dup in enumerate(self.duplicates, 1):
                print(f"
 重复组 {i}/{len(self.duplicates)}:")
                print(f"  A: {dup['original']}")
                print(f"    大小: {dup['size_kb']:.1f} KB")
                print(f"    修改时间: {datetime.fromtimestamp(os.path.getmtime(dup['original']))}")
                
                print(f"
  B: {dup['duplicate']}")
                print(f"    大小: {dup['size_kb']:.1f} KB")
                print(f"    修改时间: {datetime.fromtimestamp(os.path.getmtime(dup['duplicate']))}")
                
                action = input("
请选择 (A保留/B保留/跳过): ").strip().upper()
                
                if action == 'A':
                    try:
                        send2trash.send2trash(dup['duplicate'])
                        print("✅ 已删除B文件")
                        removed_count += 1
                        saved_space += dup['size_kb']
                    except Exception as e:
                        print(f"❌ 删除失败: {e}")
                elif action == 'B':
                    try:
                        send2trash.send2trash(dup['original'])
                        print("✅ 已删除A文件")
                        removed_count += 1
                        saved_space += dup['size_kb']
                    except Exception as e:
                        print(f"❌ 删除失败: {e}")
                else:
                    print("⏭️  跳过")
        
        if removed_count > 0:
            saved_mb = saved_space / 1024
            print(f"
✨ 清理完成!")
            print(f"   删除文件数: {removed_count}")
            print(f"   释放空间: {saved_mb:.1f} MB")
            print(f"   总耗时: {time.time() - self.start_time:.1f}秒")
    
    def save_report(self, filename="duplicates_report.json"):
        """
        保存扫描报告
        """
        report = {
            'scan_time': datetime.now().isoformat(),
            'scanned_files': self.scanned_files,
            'duplicates_found': len(self.duplicates),
            'total_size_mb': sum(d['size_kb'] for d in self.duplicates) / 1024,
            'duplicates': self.duplicates
        }
        
        with open(filename, 'w', encoding='utf-8') as f:
            json.dump(report, f, ensure_ascii=False, indent=2)
        
        print(f"
 报告已保存: {filename}")

def main():
    """
    主函数:提供用户友善的清理体验
    """
    print("""
     Mac重复文件清理助手
    ======================
    安全 · 高效 · 智能
    
    特点:
    ✅ 安全删除(移至废纸篓)
    ✅ 智能识别(大小+哈希双重验证)
    ✅ 多格式支持(图片、文档、压缩包等)
    ✅ 进度显示,随时掌控
    """)
    
    cleaner = MacDuplicateCleaner()
    
    # 让用户选择扫描目录
    print("
 请选择扫描目录:")
    print("  1. 下载文件夹 (~/Downloads)")
    print("  2. 桌面 (~/Desktop)")
    print("  3. 文档 (~/Documents)")
    print("  4. 图片 (~/Pictures)")
    print("  5. 自定义目录")
    
    dir_choice = input("请选择 (1-5): ").strip()
    
    directories = {
        '1': '~/Downloads',
        '2': '~/Desktop',
        '3': '~/Documents',
        '4': '~/Pictures',
        '5': input("请输入目录路径: ").strip()
    }
    
    target_dir = directories.get(dir_choice, '~/Downloads')
    
    # 选择文件类型
    print("
 选择文件类型:")
    print("  1. 所有类型")
    print("  2. 图片 (.jpg, .png, .heic)")
    print("  3. 文档 (.pdf, .doc, .ppt, .xls)")
    print("  4. 压缩包 (.zip, .rar, .7z)")
    print("  5. 视频 (.mp4, .mov, .avi)")
    
    type_choice = input("请选择 (1-5): ").strip()
    
    extensions_map = {
        '1': None,
        '2': ['.jpg', '.jpeg', '.png', '.gif', '.heic', '.tiff', '.bmp'],
        '3': ['.pdf', '.doc', '.docx', '.ppt', '.pptx', '.xls', '.xlsx', '.txt', '.rtf'],
        '4': ['.zip', '.rar', '.7z', '.tar', '.gz'],
        '5': ['.mp4', '.mov', '.avi', '.mkv', '.flv', '.wmv']
    }
    
    extensions = extensions_map.get(type_choice)
    
    # 设置最小文件大小
    print("
 设置最小文件大小(默认10KB):")
    min_size = input("最小文件大小(KB,回车使用默认值10): ").strip()
    min_size_kb = int(min_size) if min_size.isdigit() else 10
    
    # 开始扫描
    print("
" + "="*60)
    print("开始扫描...")
    print("="*60)
    
    cleaner.scan_directory(
        directory=target_dir,
        extensions=extensions,
        min_size_kb=min_size_kb
    )
    
    # 显示结果并清理
    cleaner.smart_review_duplicates()
    cleaner.interactive_cleanup()
    
    # 保存报告
    save_report = input("
是否保存扫描报告?(y/n): ").strip().lower()
    if save_report == 'y':
        cleaner.save_report()

if __name__ == "__main__":
    main()

三大核心功能,彻底解决重复文件问题

1.智能识别技术

我们的脚本采用双重验证机制

  • 文件大小比对(快速筛选)
  • 内容哈希校验(准确匹配)

即使是修改时间不同、但内容完全一样的文件,也逃不过它的”法眼”。

2.安全删除保障

使用 send2trash 库,所有删除操作都只是将文件移至废纸篓,而非永久删除。如果误删重大文件,随时可以从废纸篓恢复。

3.智能清理策略

脚本提供多种清理模式:

  • 自动模式:保留最新修改的文件
  • 手动模式:逐个文件确认
  • 预览模式:仅查看不删除

简易操作

运行代码后,根据需要选择功能即可:

  1. 选择扫描目录(下载、桌面、文档等)
  2. 选择文件类型(图片、文档、视频等)
  3. 设置最小文件大小
  4. 查看扫描结果
  5. 选择清理方式

️ 安全保障措施

  1. 只扫描用户目录:绝不触碰系统文件
  2. 废纸篓删除:所有删除都可恢复
  3. 人工确认:重大文件需要你亲自确认
  4. 扫描报告:完整记录所有操作

批量处理多个目录

修改代码中的扫描部分,可以同时处理多个文件夹:

python

# 同时扫描下载和文档文件夹
cleaner.scan_directory('~/Downloads')
cleaner.scan_directory('~/Documents')

定时自动清理

使用macOS的launchd设置每周自动清理:

bash

# 创建plist文件 ~/Library/LaunchAgents/com.user.clean-duplicates.plist
# 设置每周日凌晨3点自动运行

排除特定文件夹

在扫描时跳过某些目录:

python

# 在scan_directory方法中添加
if 'node_modules' in root or '.git' in root:
    dirs[:] = []  # 跳过此目录

注意事项

  1. 首次使用提议选择”预览模式”,确认无误后再清理
  2. 清理前确保废纸篓有足够空间
  3. 重大文件提议先备份到云端
  4. 系统升级前不要进行大规模清理

还在为重复文件烦恼吗?立即复制上面的代码,给你的Mac来一次深度清洁吧!记得先运行预览模式,确认无误后再进行清理操作。

释放存储空间,提升工作效率,从清理重复文件开始!

温馨提示:定期清理是好习惯,但重大文件请务必做好备份。技术虽好,数据无价!

记得收藏这篇文章,方便后来随时使用!也欢迎分享给需要的朋友~

#Mac技巧 #电脑清理 #存储空间 #Python #效率工具


互动话题:你的Mac还剩多少存储空间?用这个方法清理出了多少空间?欢迎在评论区分享你的成果!

© 版权声明

相关文章

暂无评论

none
暂无评论...