用python写个文件大小扫描工具
背景
近期电脑一个盘亮红了,需要找出那个文件或文件夹吃了空间。手动找太麻烦了,就用python写个文件夹大小扫描工具。
程序界面展示
双击可打开文件夹!!
程序功能说明
文件/文件夹标识:
- 在树状视图中添加了"类型"列,清晰地标识每个项目是"文件"还是"文件夹"
- 使用
item.is_dir()
方法判断项目类型 - 在
item_types
字典中存储每个树项目的类型信息
基本扫描功能:
- 扫描指定文件夹下所有文件和文件夹的大小
- 显示每个项目的路径、大小和占总大小的百分比
- 按大小降序排列结果
用户界面:
- 路径输入框和浏览按钮
- 扫描进度条
- 状态栏显示当前操作状态
- 树状视图显示扫描结果
交互功能:
- 右键菜单提供"打开"、"打开所在文件夹"和"复制路径"功能
- 双击项目可直接打开
- 跨平台支持(Windows、macOS、Linux)
错误处理:
- 处理权限错误和IO错误
- 文件操作失败时显示错误消息
使用这个工具,用户可以轻松地:
- 分析文件夹中哪些文件/子文件夹占用空间最大
- 快速访问这些文件/文件夹
- 复制路径用于其他操作
这个工具在管理磁盘空间、清理大文件时特别有用。
打包程序
为了方便其他用户使用,使用pyinstaller将程序打包成可执行文件。
pyinstaller -F -w 扫描磁盘工具.py
程序源码
import os
import tkinter as tk
from tkinter import filedialog, ttk, messagebox
import threading
from pathlib import Path
import humanize # 用于格式化文件大小
import subprocess
import platform
import pyperclip # 用于复制到剪贴板
class FolderScannerApp:
def __init__(self, root):
self.root = root
self.root.title("文件夹大小扫描工具")
self.root.geometry("1000x600")
self.root.minsize(1000, 600)
# 创建主框架
self.main_frame = ttk.Frame(self.root, padding=10)
self.main_frame.pack(fill=tk.BOTH, expand=True)
# 创建顶部控制区域
self.control_frame = ttk.Frame(self.main_frame)
self.control_frame.pack(fill=tk.X, pady=5)
# 路径输入框
self.path_var = tk.StringVar()
ttk.Label(self.control_frame, text="文件夹路径:").pack(side=tk.LEFT, padx=5)
self.path_entry = ttk.Entry(self.control_frame, textvariable=self.path_var, width=50)
self.path_entry.pack(side=tk.LEFT, padx=5, fill=tk.X, expand=True)
# 浏览按钮
self.browse_btn = ttk.Button(self.control_frame, text="浏览", command=self.browse_folder)
self.browse_btn.pack(side=tk.LEFT, padx=5)
# 扫描按钮
self.scan_btn = ttk.Button(self.control_frame, text="扫描", command=self.start_scan)
self.scan_btn.pack(side=tk.LEFT, padx=5)
# 创建进度条
self.progress_var = tk.DoubleVar()
self.progress_bar = ttk.Progressbar(self.main_frame, variable=self.progress_var, maximum=100)
self.progress_bar.pack(fill=tk.X, pady=5)
# 创建状态标签
self.status_var = tk.StringVar(value="就绪")
self.status_label = ttk.Label(self.main_frame, textvariable=self.status_var)
self.status_label.pack(anchor=tk.W, pady=5)
# 创建结果显示区域
self.result_frame = ttk.Frame(self.main_frame)
self.result_frame.pack(fill=tk.BOTH, expand=True, pady=5)
# 创建树状视图 - 添加了type列显示文件类型
self.tree = ttk.Treeview(self.result_frame, columns=("type", "path", "size", "percentage"), show="headings")
self.tree.heading("type", text="类型")
self.tree.heading("path", text="路径")
self.tree.heading("size", text="大小")
self.tree.heading("percentage", text="占比")
self.tree.column("type", width=80, anchor=tk.CENTER)
self.tree.column("path", width=500, anchor=tk.W)
self.tree.column("size", width=100, anchor=tk.E)
self.tree.column("percentage", width=100, anchor=tk.CENTER)
# 添加滚动条
self.scrollbar_y = ttk.Scrollbar(self.result_frame, orient=tk.VERTICAL, command=self.tree.yview)
self.tree.configure(yscrollcommand=self.scrollbar_y.set)
self.scrollbar_x = ttk.Scrollbar(self.result_frame, orient=tk.HORIZONTAL, command=self.tree.xview)
self.tree.configure(xscrollcommand=self.scrollbar_x.set)
# 布局树状视图和滚动条
self.scrollbar_y.pack(side=tk.RIGHT, fill=tk.Y)
self.tree.pack(side=tk.TOP, fill=tk.BOTH, expand=True)
self.scrollbar_x.pack(side=tk.BOTTOM, fill=tk.X)
# 创建右键菜单
self.context_menu = tk.Menu(self.tree, tearoff=0)
self.context_menu.add_command(label="打开", command=self.open_item)
self.context_menu.add_command(label="打开所在文件夹", command=self.open_containing_folder)
self.context_menu.add_separator()
self.context_menu.add_command(label="复制路径", command=self.copy_path)
# 绑定右键点击事件
self.tree.bind("<Button-3>", self.show_context_menu)
# 绑定双击事件
self.tree.bind("<Double-1>", lambda event: self.open_item())
# 扫描线程
self.scan_thread = None
# 存储项目路径的字典
self.item_paths = {}
# 存储项目类型的字典
self.item_types = {}
def browse_folder(self):
folder_path = filedialog.askdirectory()
if folder_path:
self.path_var.set(folder_path)
def start_scan(self):
folder_path = self.path_var.get()
if not folder_path or not os.path.isdir(folder_path):
self.status_var.set("错误:请选择有效的文件夹路径")
return
# 清空树状视图和路径字典
for item in self.tree.get_children():
self.tree.delete(item)
self.item_paths = {}
self.item_types = {}
# 禁用扫描按钮
self.scan_btn.configure(state=tk.DISABLED)
self.browse_btn.configure(state=tk.DISABLED)
# 更新状态
self.status_var.set("正在扫描...")
# 启动扫描线程
self.scan_thread = threading.Thread(target=self.scan_folder, args=(folder_path,))
self.scan_thread.daemon = True
self.scan_thread.start()
def scan_folder(self, folder_path):
try:
# 获取文件夹下所有项目
items = list(os.scandir(folder_path))
total_items = len(items)
# 计算总大小
total_size = 0
item_data = []
for i, item in enumerate(items):
# 获取项目大小
size = self.get_item_size(item)
# 确定项目类型
item_type = "文件夹" if item.is_dir() else "文件"
item_data.append((item, size, item_type))
total_size += size
# 更新进度
progress = (i + 1) / total_items * 100
self.progress_var.set(progress)
self.root.update_idletasks()
# 处理每个项目
results = []
for item, size, item_type in item_data:
percentage = (size / total_size * 100) if total_size > 0 else 0
results.append((item.path, item.name, size, percentage, item_type))
# 按大小排序
results.sort(key=lambda x: x[2], reverse=True)
# 更新UI
self.root.after(0, lambda: self.update_results(results, total_size, folder_path))
except Exception as e:
self.root.after(0, lambda: self.status_var.set(f"错误:{str(e)}"))
finally:
self.root.after(0, self.reset_ui)
def get_item_size(self, item):
"""获取文件或文件夹的大小"""
try:
if item.is_file():
return item.stat().st_size
elif item.is_dir():
return sum(f.stat().st_size for f in Path(item.path).glob('**/*') if f.is_file())
return 0
except (PermissionError, OSError):
# 处理权限错误或其他IO错误
return 0
def update_results(self, results, total_size, base_folder):
# 添加总大小信息
item_id = self.tree.insert("", tk.END, values=(
"文件夹",
base_folder,
humanize.naturalsize(total_size, binary=True),
"100%"
))
self.item_paths[item_id] = base_folder
self.item_types[item_id] = "文件夹"
# 添加每个项目
for full_path, name, size, percentage, item_type in results:
item_id = self.tree.insert("", tk.END, values=(
item_type,
full_path,
humanize.naturalsize(size, binary=True),
f"{percentage:.2f}%"
))
self.item_paths[item_id] = full_path
self.item_types[item_id] = item_type
# 更新状态
self.status_var.set(
f"扫描完成,共 {len(results)} 个项目,总大小 {humanize.naturalsize(total_size, binary=True)}")
def get_selected_path(self):
"""获取当前选中项目的路径"""
selected = self.tree.selection()
if not selected:
return None
item_id = selected[0]
if item_id in self.item_paths:
return self.item_paths[item_id]
return None
def reset_ui(self):
# 启用扫描按钮
self.scan_btn.configure(state=tk.NORMAL)
self.browse_btn.configure(state=tk.NORMAL)
# 重置进度条
self.progress_var.set(0)
def show_context_menu(self, event):
"""显示右键菜单"""
item = self.tree.identify_row(event.y)
if item:
self.tree.selection_set(item)
self.context_menu.post(event.x_root, event.y_root)
def open_item(self):
"""打开所选项目"""
path = self.get_selected_path()
if not path:
return
try:
if platform.system() == "Windows":
os.startfile(path)
elif platform.system() == "Darwin": # macOS
subprocess.run(["open", path])
else: # Linux
subprocess.run(["xdg-open", path])
except Exception as e:
messagebox.showerror("错误", f"无法打开文件: {str(e)}")
def open_containing_folder(self):
"""打开所选项目所在的文件夹"""
path = self.get_selected_path()
if not path:
return
# 如果是文件,获取其所在目录
if os.path.isfile(path):
dir_path = os.path.dirname(path)
try:
if platform.system() == "Windows":
# 在Windows上,我们可以选中文件
subprocess.run(["explorer", "/select,", path])
return
elif platform.system() == "Darwin": # macOS
# 在macOS上,我们可以显示文件
subprocess.run(["open", "-R", path])
return
except Exception:
pass # 如果特定方法失败,回退到打开文件夹
path = dir_path
try:
if platform.system() == "Windows":
os.startfile(path)
elif platform.system() == "Darwin": # macOS
subprocess.run(["open", path])
else: # Linux
subprocess.run(["xdg-open", path])
except Exception as e:
messagebox.showerror("错误", f"无法打开文件夹: {str(e)}")
def copy_path(self):
"""复制所选项目的路径到剪贴板"""
path = self.get_selected_path()
if path:
pyperclip.copy(path)
self.status_var.set(f"已复制路径: {path}")
if __name__ == "__main__":
# 安装依赖:pip install humanize pyperclip
try:
import humanize
import pyperclip
except ImportError:
print("请先安装所需库: pip install humanize pyperclip")
import sys
sys.exit(1)
root = tk.Tk()
app = FolderScannerApp(root)
root.mainloop()
关注@运维躬行录,在聊天框输入【文件大小扫描】,可免费领取打包文件及《python自动化办公教程》祝你效率翻倍