[python开发] 文件信息导出工具
功能特性
- 导出文件夹里的文件信息
- 统计文件夹的数据及各类文件数量
- 导出为csv或者xlsx
下载
美化版截图
老版原始代码
import os
import openpyxl
import time
import tkinter as tk
from tkinter import filedialog, messagebox, ttk, Listbox, Scrollbar
import threading
import math
import csv
from collections import defaultdict
class FileScanner:
def __init__(self):
self.scanning = False
self.exporting = False
self.current_thread = None
self.file_cache = []
self.file_count = 0
self.large_files_warning_given = False
def export_file_info_to_excel(self, folder_path, export_options, status_label, all_items, include_subfolders, progress_var, progress_label, progress_bar):
"""将文件夹中的文件信息导出到Excel,基于已扫描的文件列表"""
self.exporting = True
try:
if not os.path.exists(folder_path):
messagebox.showerror("错误", f"文件夹路径不存在: {folder_path}")
return
status_label.config(text="正在处理...")
window.update_idletasks()
folder_path = os.path.normpath(folder_path)
drive, tail = os.path.splitdrive(folder_path)
if os.path.isdir(folder_path) and drive and tail in ('\\', '/'):
folder_name = drive.rstrip(':')
else:
folder_name = os.path.basename(folder_path)
excel_path = os.path.join(folder_path, f"{folder_name}.xlsx")
workbook = openpyxl.Workbook()
sheet = workbook.active
headers = ["序号", "文件夹", "文件名"]
if export_options["size"]:
headers.append("文件大小")
if export_options["ctime"]:
headers.append("创建时间")
if export_options["mtime"]:
headers.append("修改时间")
if export_options["ext"]:
headers.append("文件类型")
if export_options["path"]:
headers.append("文件路径")
sheet.append(headers)
total_size = 0
file_count = 0
folder_count = 0
file_type_counts = defaultdict(int)
index = 1
all_items = sorted(all_items)
total_items = len(all_items)
batch_size = max(1000, total_items // 50)
self.update_progress_bar(progress_var, progress_label, 0, total_items, progress_bar)
for i, itempath in enumerate(all_items):
if i % batch_size == 0:
self.update_progress_bar(progress_var, progress_label, i, total_items, progress_bar)
window.update_idletasks()
if os.path.isfile(itempath):
file_size, file_ctime, file_mtime, file_ext = self.get_file_info(itempath)
if file_size is not None:
relative_path = os.path.relpath(itempath, folder_path)
folder_display = ""
if os.path.dirname(relative_path) != ".":
folder_display = os.path.dirname(relative_path)
row = [index, folder_display, os.path.basename(itempath)]
if export_options["size"]:
row.append(self.convert_size(file_size))
if export_options["ctime"]:
row.append(file_ctime)
if export_options["mtime"]:
row.append(file_mtime)
if export_options["ext"]:
row.append(file_ext)
if export_options["path"]:
row.append(itempath)
sheet.append(row)
total_size += file_size
file_count += 1
file_type_counts[file_ext] += 1
index += 1
elif os.path.isdir(itempath):
relative_path = os.path.relpath(itempath, folder_path)
folder_display = relative_path
row = [index, folder_display, ""]
sheet.append(row)
folder_count += 1
index += 1
self.update_progress_bar(progress_var, progress_label, total_items, total_items, progress_bar)
sheet.append([])
sheet.append(["统计信息"])
sheet.append(["文件总数", file_count])
sheet.append(["文件夹总数", folder_count])
sheet.append(["文件夹总大小", self.convert_size(total_size)])
for file_type, count in file_type_counts.items():
sheet.append([f"{file_type} 文件数量", count])
workbook.save(excel_path)
status_label.config(text=f"文件信息已导出到: {excel_path}")
messagebox.showinfo("成功", f"Excel文件已保存到: {excel_path}")
except Exception as e:
status_label.config(text="发生错误!")
messagebox.showerror("错误", f"导出到Excel失败: {e}")
finally:
self.exporting = False
self.update_progress_bar(progress_var, progress_label, 0, 1, progress_bar)
def convert_size(self, size_bytes):
"""转换文件大小为KB, MB, GB, TB等"""
if size_bytes == 0:
return "0B"
size_name = ("B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB")
i = int(math.floor(math.log(size_bytes, 1024)))
p = math.pow(1024, i)
s = round(size_bytes / p, 2)
return f"{s} {size_name[i]}"
def get_file_info(self, filepath):
"""获取文件信息"""
try:
file_size = os.path.getsize(filepath)
file_ctime = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(os.path.getctime(filepath)))
file_mtime = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(os.path.getmtime(filepath)))
file_ext = os.path.splitext(filepath)[1]
return file_size, file_ctime, file_mtime, file_ext
except FileNotFoundError:
return None, None, None, None
except Exception as e:
return None, None, None, None
def update_progress_bar(self, progress_var, progress_label, current, total, progress_bar):
"""更新进度条和进度标签"""
progress = int((current / total) * 100) if total > 0 else 0
progress_var.set(progress)
progress_label.config(text=f"进度: {progress}%")
if progress == 0:
progress_bar.pack_forget()
progress_label.pack_forget()
else:
progress_bar.pack(pady=(0, 10), fill=tk.X, padx=5)
progress_label.pack(pady=(0, 10))
window.update_idletasks()
def start_export(self, folder_entry, export_options, status_label, file_listbox, include_subfolders, progress_var, progress_label, progress_bar):
"""启动导出操作"""
if not self.exporting and not self.scanning:
folder_path = folder_entry.get()
if not folder_path:
messagebox.showerror("错误", "请选择文件夹")
return
if file_listbox.size() == 0:
messagebox.showerror("错误", "请先扫描文件夹获取文件列表")
return
self.exporting = True
thread = threading.Thread(target=self.export_file_info_to_excel,
args=(folder_path, export_options, status_label, file_listbox, include_subfolders, progress_var, progress_label, progress_bar))
thread.start()
def show_large_files_warning(self):
"""显示大文件数量警告"""
if messagebox.askyesno("扫描提示",
"当前检测到超过10,000个文件,扫描可能需要较长时间\n是否要继续扫描?") == tk.NO:
self.stop_scan()
def export_file_info_to_csv(self, folder_path, export_options, status_label, all_items, include_subfolders, progress_var, progress_label, progress_bar):
"""将文件夹中的文件信息导出到CSV,基于已扫描的文件列表"""
self.exporting = True
try:
if not os.path.exists(folder_path):
messagebox.showerror("错误", f"文件夹路径不存在: {folder_path}")
return
status_label.config(text="正在处理...")
window.update_idletasks()
folder_path = os.path.normpath(folder_path)
drive, tail = os.path.splitdrive(folder_path)
if os.path.isdir(folder_path) and drive and tail in ('\\', '/'):
folder_name = drive.rstrip(':')
else:
folder_name = os.path.basename(folder_path)
csv_path = os.path.join(folder_path, f"{folder_name}.csv")
headers = ["序号", "文件夹", "文件名"]
if export_options["size"]:
headers.append("文件大小")
if export_options["ctime"]:
headers.append("创建时间")
if export_options["mtime"]:
headers.append("修改时间")
if export_options["ext"]:
headers.append("文件类型")
if export_options["path"]:
headers.append("文件路径")
total_size = 0
file_count = 0
folder_count = 0
file_type_counts = defaultdict(int)
index = 1
all_items = sorted(all_items)
total_items = len(all_items)
batch_size = max(1000, total_items // 50)
self.update_progress_bar(progress_var, progress_label, 0, total_items, progress_bar)
with open(csv_path, 'w', newline='', encoding='utf-8-sig') as csvfile:
writer = csv.writer(csvfile)
writer.writerow(headers)
for i, itempath in enumerate(all_items):
if i % batch_size == 0:
self.update_progress_bar(progress_var, progress_label, i, total_items, progress_bar)
window.update_idletasks()
if os.path.isfile(itempath):
file_size, file_ctime, file_mtime, file_ext = self.get_file_info(itempath)
if file_size is not None:
relative_path = os.path.relpath(itempath, folder_path)
folder_display = ""
if os.path.dirname(relative_path) != ".":
folder_display = os.path.dirname(relative_path)
row = [index, folder_display, os.path.basename(itempath)]
if export_options["size"]:
row.append(self.convert_size(file_size))
if export_options["ctime"]:
row.append(file_ctime)
if export_options["mtime"]:
row.append(file_mtime)
if export_options["ext"]:
row.append(file_ext)
if export_options["path"]:
row.append(itempath)
writer.writerow(row)
total_size += file_size
file_count += 1
file_type_counts[file_ext] += 1
index += 1
elif os.path.isdir(itempath):
relative_path = os.path.relpath(itempath, folder_path)
folder_display = relative_path
row = [index, folder_display, ""]
writer.writerow(row)
folder_count += 1
index += 1
self.update_progress_bar(progress_var, progress_label, total_items, total_items, progress_bar)
writer.writerow([])
writer.writerow(["统计信息"])
writer.writerow(["文件总数", file_count])
writer.writerow(["文件夹总数", folder_count])
writer.writerow(["文件夹总大小", self.convert_size(total_size)])
for file_type, count in file_type_counts.items():
writer.writerow([f"{file_type} 文件数量", count])
status_label.config(text=f"文件信息已导出到: {csv_path}")
if messagebox.askyesno("导出完成", f"CSV文件已保存到: {csv_path}\n是否需要转换为Excel格式?"):
self.convert_csv_to_excel(csv_path, status_label)
else:
messagebox.showinfo("成功", f"文件信息已导出到: {csv_path}")
except Exception as e:
status_label.config(text="发生错误!")
messagebox.showerror("错误", f"导出到CSV失败: {e}")
finally:
self.exporting = False
self.update_progress_bar(progress_var, progress_label, 0, 1, progress_bar)
def convert_csv_to_excel(self, csv_path, status_label):
"""将CSV文件转换为Excel格式"""
try:
status_label.config(text="正在转换为Excel格式...")
window.update_idletasks()
excel_path = os.path.splitext(csv_path)[0] + ".xlsx"
workbook = openpyxl.Workbook(write_only=True)
sheet = workbook.create_sheet()
with open(csv_path, 'r', encoding='utf-8-sig') as f:
reader = csv.reader(f)
for row in reader:
sheet.append(row)
workbook.save(excel_path)
status_label.config(text=f"文件信息已导出到: {excel_path}")
messagebox.showinfo("成功", f"Excel文件已保存到: {excel_path}")
except Exception as e:
status_label.config(text="转换Excel失败!")
messagebox.showerror("错误", f"转换为Excel失败: {e}")
def browse_folder(self, folder_entry, file_listbox, include_subfolders, progress_var, progress_label, progress_bar, scan_button, stop_button):
"""浏览文件夹,带进度显示"""
if self.scanning or self.exporting:
messagebox.showerror("提示", "请停止当前任务再选择目录!")
return
folder_selected = filedialog.askdirectory()
if folder_selected:
folder_entry.delete(0, tk.END)
folder_entry.insert(0, folder_selected)
def start_scan(self, folder_path, file_listbox, include_subfolders, progress_var, progress_label, progress_bar, scan_button, stop_button):
"""启动扫描操作"""
if not self.scanning and not self.exporting:
if not os.path.exists(folder_path):
messagebox.showerror("错误", f"文件夹路径不存在: {folder_path}")
return
self.scanning = True
scan_button.config(state=tk.DISABLED)
stop_button.config(state=tk.NORMAL)
self.current_thread = threading.Thread(target=self.update_file_list,
args=(folder_path, file_listbox, include_subfolders, progress_var, progress_label, progress_bar, scan_button, stop_button))
self.current_thread.start()
def update_file_list(self, folder_path, file_listbox, include_subfolders, progress_var, progress_label, progress_bar, scan_button=None, stop_button=None):
"""更新文件列表,带进度显示"""
self.file_count = 0
self.large_files_warning_given = False
if not self.scanning:
return
all_items = []
if include_subfolders:
for root, dirs, files in os.walk(folder_path, topdown=True):
if not self.scanning:
break
with os.scandir(root) as it:
all_items.extend(entry.path for entry in it)
self.file_count += sum(1 for entry in it if entry.is_file())
else:
with os.scandir(folder_path) as it:
all_items = [entry.path for entry in it]
self.file_count = sum(1 for entry in it if entry.is_file())
batch_size = 1000
total_items = len(all_items)
file_listbox.delete(0, tk.END)
for i in range(0, total_items, batch_size):
if not self.scanning:
break
batch = all_items[i:i+batch_size]
file_listbox.insert(tk.END, *batch)
self.update_progress_bar(progress_var, progress_label, min(i+batch_size, total_items), total_items, progress_bar)
window.update_idletasks()
self.update_progress_bar(progress_var, progress_label, total_items, total_items, progress_bar)
self.stop_scan(scan_button, stop_button)
def stop_scan(self, scan_button=None, stop_button=None):
"""停止扫描操作"""
self.scanning = False
if self.current_thread and self.current_thread.is_alive() and self.current_thread != threading.current_thread():
self.current_thread.join(timeout=1.0)
if self.current_thread.is_alive():
import ctypes
thread_id = self.current_thread.ident
ctypes.pythonapi.PyThreadState_SetAsyncExc(thread_id, ctypes.py_object(SystemExit))
if scan_button and stop_button:
scan_button.config(state=tk.NORMAL)
stop_button.config(state=tk.DISABLED)
def start_export(self, folder_entry, export_options, status_label, file_listbox, include_subfolders, progress_var, progress_label, progress_bar, export_format):
"""启动导出操作"""
if not self.exporting and not self.scanning:
folder_path = folder_entry.get()
if not folder_path:
messagebox.showerror("错误", "请选择文件夹")
return
if file_listbox.size() == 0:
messagebox.showerror("错误", "请先扫描文件夹获取文件列表")
return
all_items = [file_listbox.get(i) for i in range(file_listbox.size())]
self.exporting = True
if export_format.get() == "csv":
thread = threading.Thread(target=self.export_file_info_to_csv,
args=(folder_path, export_options, status_label, all_items, include_subfolders, progress_var, progress_label, progress_bar))
else:
thread = threading.Thread(target=self.export_file_info_to_excel,
args=(folder_path, export_options, status_label, all_items, include_subfolders, progress_var, progress_label, progress_bar))
thread.start()
def create_gui():
"""创建GUI界面"""
global window
window = tk.Tk()
window.title("文件信息导出工具")
window.geometry("1000x700")
window.resizable(True, True)
window.minsize(800, 600)
scanner = FileScanner()
font_style = ("微软雅黑", 10)
folder_frame = tk.Frame(window)
folder_frame.pack(pady=10, fill=tk.X, padx=10)
folder_label = tk.Label(folder_frame, text="选择文件夹:", font=font_style)
folder_label.pack(side=tk.LEFT, padx=10)
folder_entry = tk.Entry(folder_frame, width=20, font=font_style)
folder_entry.pack(side=tk.LEFT, padx=10, expand=True, fill=tk.X)
include_subfolders = tk.BooleanVar(value=False)
subfolders_check = tk.Checkbutton(folder_frame, text="包含子文件夹", variable=include_subfolders, font=font_style)
subfolders_check.pack(side=tk.LEFT, padx=10)
folder_button = tk.Button(folder_frame, text="浏览", font=font_style, width=10,
command=lambda: scanner.browse_folder(folder_entry, file_listbox, include_subfolders, progress_var, progress_label, progress_bar, scan_button, stop_button))
folder_button.pack(side=tk.LEFT, padx=10)
scan_button = tk.Button(folder_frame, text="开始扫描", state=tk.NORMAL, font=font_style, width=12)
scan_button.pack(side=tk.LEFT, padx=10)
stop_button = tk.Button(folder_frame, text="停止扫描", state=tk.DISABLED, font=font_style, width=12,
command=lambda: scanner.stop_scan(scan_button, stop_button))
stop_button.pack(side=tk.LEFT, padx=10)
scan_button.config(command=lambda: scanner.start_scan(folder_entry.get(), file_listbox, include_subfolders.get(), progress_var, progress_label, progress_bar, scan_button, stop_button))
listbox_frame = tk.Frame(window)
listbox_frame.pack(pady=10, fill=tk.BOTH, expand=True, padx=10)
scrollbar = Scrollbar(listbox_frame)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
global file_listbox
file_listbox = Listbox(listbox_frame, yscrollcommand=scrollbar.set, selectmode=tk.EXTENDED, font=font_style)
file_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
scrollbar.config(command=file_listbox.yview)
options_frame = tk.LabelFrame(window, text="导出选项", font=font_style)
options_frame.pack(pady=10, padx=10, fill=tk.X)
export_options = {
"size": tk.BooleanVar(value=True),
"ctime": tk.BooleanVar(value=True),
"mtime": tk.BooleanVar(value=True),
"ext": tk.BooleanVar(value=False),
"path": tk.BooleanVar(value=False),
}
tk.Checkbutton(options_frame, text="文件大小", variable=export_options["size"], font=font_style).pack(side=tk.LEFT, padx=10)
tk.Checkbutton(options_frame, text="创建时间", variable=export_options["ctime"], font=font_style).pack(side=tk.LEFT, padx=10)
tk.Checkbutton(options_frame, text="修改时间", variable=export_options["mtime"], font=font_style).pack(side=tk.LEFT, padx=10)
tk.Checkbutton(options_frame, text="文件类型", variable=export_options["ext"], font=font_style).pack(side=tk.LEFT, padx=10)
tk.Checkbutton(options_frame, text="文件路径", variable=export_options["path"], font=font_style).pack(side=tk.LEFT, padx=10)
format_frame = tk.Frame(window)
format_frame.pack(pady=10, fill=tk.X, padx=10)
tk.Label(format_frame, text="导出格式:", font=font_style).pack(side=tk.LEFT, padx=10)
export_format = tk.StringVar(value="csv")
tk.Radiobutton(format_frame, text="CSV格式 (更快)", variable=export_format, value="csv", font=font_style).pack(side=tk.LEFT, padx=10)
tk.Radiobutton(format_frame, text="Excel格式", variable=export_format, value="excel", font=font_style).pack(side=tk.LEFT, padx=10)
progress_container = tk.Frame(window)
progress_container.pack(pady=(5, 0), fill=tk.X, padx=10)
progress_var = tk.DoubleVar()
progress_bar = ttk.Progressbar(progress_container, orient="horizontal", mode="determinate", variable=progress_var)
progress_label = tk.Label(progress_container, text="进度: 0%", font=font_style)
progress_label.pack(pady=(2, 0))
status_label = tk.Label(window, text="", font=font_style)
status_label.pack(pady=3)
export_button_frame = tk.Frame(window)
export_button_frame.pack(fill=tk.X, pady=(5, 10))
export_button = tk.Button(export_button_frame, text="开始导出", font=("微软雅黑", 10, "bold"), width=20,
bg="#3d8af7", fg="white", activebackground="#4a6fa5", activeforeground="white",
command=lambda: scanner.start_export(folder_entry, {k: v.get() for k, v in export_options.items()},
status_label, file_listbox, include_subfolders, progress_var, progress_label, progress_bar, export_format))
export_button.pack(pady=5, anchor="center")
scanner.update_progress_bar(progress_var, progress_label, 0, 1, progress_bar)
window.mainloop()
if __name__ == "__main__":
create_gui()
美化版原始代码
import os
import openpyxl
import time
import tkinter as tk
from tkinter import filedialog, messagebox, ttk, Listbox, Scrollbar
import threading
import math
import csv
from collections import defaultdict
COLORS = {
"primary": "#4a6fa5",
"secondary": "#6e9cd1",
"accent": "#3d8af7",
"background": "#f5f7fa",
"text": "#333333",
"light_text": "#666666",
"success": "#4caf50",
"warning": "#ff9800",
"error": "#f44336",
"disabled": "#cccccc"
}
class CustomStyle:
@staticmethod
def configure_styles():
style = ttk.Style()
style.configure(
"Custom.Horizontal.TProgressbar",
troughcolor=COLORS["background"],
background=COLORS["accent"],
thickness=10
)
style.configure(
"Custom.TButton",
background=COLORS["primary"],
foreground="white",
font=("微软雅黑", 10),
padding=5
)
style.configure(
"Custom.TCheckbutton",
background=COLORS["background"],
foreground=COLORS["text"],
font=("微软雅黑", 10)
)
style.configure(
"Custom.TRadiobutton",
background=COLORS["background"],
foreground=COLORS["text"],
font=("微软雅黑", 10)
)
style.configure(
"Custom.TLabel",
background=COLORS["background"],
foreground=COLORS["text"],
font=("微软雅黑", 10)
)
style.configure(
"Custom.TFrame",
background=COLORS["background"]
)
style.configure(
"Custom.TLabelframe",
background=COLORS["background"],
foreground=COLORS["text"],
font=("微软雅黑", 10, "bold")
)
style.configure(
"Custom.TLabelframe.Label",
background=COLORS["background"],
foreground=COLORS["primary"],
font=("微软雅黑", 10, "bold")
)
class HoverButton(tk.Button):
def __init__(self, master, **kw):
self.default_bg = kw.get('background', COLORS["primary"])
self.hover_bg = kw.get('activebackground', COLORS["accent"])
self.default_fg = kw.get('foreground', 'white')
self.hover_fg = kw.get('activeforeground', 'white')
kw['background'] = self.default_bg
kw['foreground'] = self.default_fg
kw['borderwidth'] = 0
kw['padx'] = 15
kw['pady'] = 5
kw['font'] = ("微软雅黑", 10)
tk.Button.__init__(self, master, **kw)
self.bind("<Enter>", self.on_enter)
self.bind("<Leave>", self.on_leave)
def on_enter(self, e):
self['background'] = self.hover_bg
self['foreground'] = self.hover_fg
def on_leave(self, e):
self['background'] = self.default_bg
self['foreground'] = self.default_fg
class FileScanner:
def __init__(self):
self.scanning = False
self.exporting = False
self.current_thread = None
self.file_cache = []
self.file_count = 0
self.large_files_warning_given = False
def export_file_info_to_excel(self, folder_path, export_options, status_label, all_items, include_subfolders, progress_var, progress_label, progress_bar):
"""将文件夹中的文件信息导出到Excel,基于已扫描的文件列表"""
self.exporting = True
try:
if not os.path.exists(folder_path):
messagebox.showerror("错误", f"文件夹路径不存在: {folder_path}")
return
status_label.config(text="正在处理...", foreground=COLORS["accent"])
window.update_idletasks()
folder_path = os.path.normpath(folder_path)
drive, tail = os.path.splitdrive(folder_path)
if os.path.isdir(folder_path) and drive and tail in ('\\', '/'):
folder_name = drive.rstrip(':')
else:
folder_name = os.path.basename(folder_path)
excel_path = os.path.join(folder_path, f"{folder_name}.xlsx")
workbook = openpyxl.Workbook()
sheet = workbook.active
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
header_font = Font(name='微软雅黑', size=11, bold=True, color='FFFFFF')
header_fill = PatternFill(start_color="4A6FA5", end_color="4A6FA5", fill_type="solid")
centered = Alignment(horizontal='center', vertical='center')
thin_border = Border(
left=Side(style='thin'),
right=Side(style='thin'),
top=Side(style='thin'),
bottom=Side(style='thin')
)
headers = ["序号", "文件夹", "文件名"]
if export_options["size"]:
headers.append("文件大小")
if export_options["ctime"]:
headers.append("创建时间")
if export_options["mtime"]:
headers.append("修改时间")
if export_options["ext"]:
headers.append("文件类型")
if export_options["path"]:
headers.append("文件路径")
sheet.append(headers)
for col_num, _ in enumerate(headers, 1):
cell = sheet.cell(row=1, column=col_num)
cell.font = header_font
cell.fill = header_fill
cell.alignment = centered
cell.border = thin_border
for i, header in enumerate(headers):
col_letter = openpyxl.utils.get_column_letter(i+1)
if header == "文件名":
sheet.column_dimensions[col_letter].width = 30
elif header == "文件路径":
sheet.column_dimensions[col_letter].width = 50
elif header in ["创建时间", "修改时间"]:
sheet.column_dimensions[col_letter].width = 20
else:
sheet.column_dimensions[col_letter].width = 15
total_size = 0
file_count = 0
folder_count = 0
file_type_counts = defaultdict(int)
index = 1
all_items = sorted(all_items)
total_items = len(all_items)
batch_size = max(1000, total_items // 50)
self.update_progress_bar(progress_var, progress_label, 0, total_items, progress_bar)
normal_font = Font(name='微软雅黑', size=10)
alt_fill = PatternFill(start_color="F5F7FA", end_color="F5F7FA", fill_type="solid")
for i, itempath in enumerate(all_items):
if i % batch_size == 0:
self.update_progress_bar(progress_var, progress_label, i, total_items, progress_bar)
window.update_idletasks()
if os.path.isfile(itempath):
file_size, file_ctime, file_mtime, file_ext = self.get_file_info(itempath)
if file_size is not None:
relative_path = os.path.relpath(itempath, folder_path)
folder_display = ""
if os.path.dirname(relative_path) != ".":
folder_display = os.path.dirname(relative_path)
row = [index, folder_display, os.path.basename(itempath)]
if export_options["size"]:
row.append(self.convert_size(file_size))
if export_options["ctime"]:
row.append(file_ctime)
if export_options["mtime"]:
row.append(file_mtime)
if export_options["ext"]:
row.append(file_ext)
if export_options["path"]:
row.append(itempath)
sheet.append(row)
row_num = len(sheet._cells) // len(headers) + (1 if len(sheet._cells) % len(headers) else 0)
for col_num in range(1, len(row) + 1):
cell = sheet.cell(row=row_num, column=col_num)
cell.font = normal_font
cell.border = thin_border
if row_num % 2 == 0:
cell.fill = alt_fill
total_size += file_size
file_count += 1
file_type_counts[file_ext] += 1
index += 1
elif os.path.isdir(itempath):
relative_path = os.path.relpath(itempath, folder_path)
folder_display = relative_path
folder_name = os.path.basename(itempath)
row = [index, folder_display, folder_name]
sheet.append(row)
row_num = len(sheet._cells) // len(headers) + (1 if len(sheet._cells) % len(headers) else 0)
folder_font = Font(name='微软雅黑', size=10, bold=True)
folder_fill = PatternFill(start_color="E3E9F2", end_color="E3E9F2", fill_type="solid")
for col_num in range(1, len(headers) + 1):
cell = sheet.cell(row=row_num, column=col_num)
cell.font = folder_font
cell.fill = folder_fill
cell.border = thin_border
folder_count += 1
index += 1
self.update_progress_bar(progress_var, progress_label, total_items, total_items, progress_bar)
summary_row = sheet.max_row + 1
summary_cell = sheet.cell(row=summary_row, column=1)
summary_cell.value = " "
summary_row = sheet.max_row + 1
sheet.merge_cells(f'A{summary_row}:B{summary_row}')
summary_cell = sheet.cell(row=summary_row, column=1)
summary_cell.value = "统计信息"
summary_cell.font = Font(name='微软雅黑', size=12, bold=True, color='FFFFFF')
summary_cell.fill = PatternFill(start_color="6E9CD1", end_color="6E9CD1", fill_type="solid")
summary_cell.alignment = centered
summary_cell.border = thin_border
for col_num in range(1, 2 + 1):
cell = sheet.cell(row=summary_row, column=col_num)
cell.fill = PatternFill(start_color="6E9CD1", end_color="6E9CD1", fill_type="solid")
cell.border = thin_border
stat_font = Font(name='微软雅黑', size=10, bold=False)
stat_fill = PatternFill(start_color="F5F7FA", end_color="F5F7FA", fill_type="solid")
stat_alignment = Alignment(horizontal='left', vertical='center')
stat_rows = [
["文件总数", file_count],
["文件夹总数", folder_count],
["文件夹总大小", self.convert_size(total_size)]
]
for idx, stat_row in enumerate(stat_rows):
sheet.append(stat_row)
row_num = sheet.max_row
for col_num in range(1, 2 + 1):
cell = sheet.cell(row=row_num, column=col_num)
cell.font = stat_font
cell.border = thin_border
cell.alignment = stat_alignment
if idx % 2 == 0:
cell.fill = stat_fill
count_font = Font(name='微软雅黑', size=10, bold=True)
count_fill = PatternFill(start_color="F5F7FA", end_color="F5F7FA", fill_type="solid")
count_alignment = Alignment(horizontal='left', vertical='center')
sorted_file_types = sorted(file_type_counts.items(), key=lambda x: x[1], reverse=True)
for idx, (file_type, count) in enumerate(sorted_file_types):
row = [f"{file_type} 文件数量", count]
sheet.append(row)
row_num = sheet.max_row
for col_num in range(1, 2 + 1):
cell = sheet.cell(row=row_num, column=col_num)
cell.font = count_font
cell.border = thin_border
cell.alignment = count_alignment
if idx % 2 == 0:
cell.fill = count_fill
workbook.save(excel_path)
status_label.config(text=f"文件信息已导出到: {excel_path}", foreground=COLORS["success"])
messagebox.showinfo("成功", f"Excel文件已保存到: {excel_path}")
except Exception as e:
status_label.config(text="发生错误!", foreground=COLORS["error"])
messagebox.showerror("错误", f"导出到Excel失败: {e}")
finally:
self.exporting = False
self.update_progress_bar(progress_var, progress_label, 0, 1, progress_bar)
def convert_size(self, size_bytes):
"""转换文件大小为KB, MB, GB, TB等"""
if size_bytes == 0:
return "0B"
size_name = ("B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB")
i = int(math.floor(math.log(size_bytes, 1024)))
p = math.pow(1024, i)
s = round(size_bytes / p, 2)
return f"{s} {size_name[i]}"
def get_file_info(self, filepath):
"""获取文件信息"""
try:
file_size = os.path.getsize(filepath)
file_ctime = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(os.path.getctime(filepath)))
file_mtime = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(os.path.getmtime(filepath)))
file_ext = os.path.splitext(filepath)[1]
return file_size, file_ctime, file_mtime, file_ext
except FileNotFoundError:
return None, None, None, None
except Exception as e:
return None, None, None, None
def update_progress_bar(self, progress_var, progress_label, current, total, progress_bar):
"""更新进度条和进度标签"""
progress = int((current / total) * 100) if total > 0 else 0
progress_var.set(progress)
progress_label.config(text=f"进度: {progress}%", foreground=COLORS["primary"])
if progress == 0:
progress_bar.pack_forget()
progress_label.pack_forget()
else:
progress_bar.pack(side=tk.LEFT, fill=tk.X, expand=True, pady=(0, 5), padx=5)
progress_label.pack(side=tk.RIGHT, pady=(0, 5), padx=(10, 0))
window.update_idletasks()
def export_file_info_to_csv(self, folder_path, export_options, status_label, all_items, include_subfolders, progress_var, progress_label, progress_bar):
"""将文件夹中的文件信息导出到CSV,基于已扫描的文件列表"""
self.exporting = True
try:
if not os.path.exists(folder_path):
messagebox.showerror("错误", f"文件夹路径不存在: {folder_path}")
return
status_label.config(text="正在处理...", foreground=COLORS["accent"])
window.update_idletasks()
folder_path = os.path.normpath(folder_path)
drive, tail = os.path.splitdrive(folder_path)
if os.path.isdir(folder_path) and drive and tail in ('\\', '/'):
folder_name = drive.rstrip(':')
else:
folder_name = os.path.basename(folder_path)
csv_path = os.path.join(folder_path, f"{folder_name}.csv")
headers = ["序号", "文件夹", "文件名"]
if export_options["size"]:
headers.append("文件大小")
if export_options["ctime"]:
headers.append("创建时间")
if export_options["mtime"]:
headers.append("修改时间")
if export_options["ext"]:
headers.append("文件类型")
if export_options["path"]:
headers.append("文件路径")
total_size = 0
file_count = 0
folder_count = 0
file_type_counts = defaultdict(int)
index = 1
all_items = sorted(all_items)
total_items = len(all_items)
batch_size = max(1000, total_items // 50)
self.update_progress_bar(progress_var, progress_label, 0, total_items, progress_bar)
with open(csv_path, 'w', newline='', encoding='utf-8-sig') as csvfile:
writer = csv.writer(csvfile)
writer.writerow(headers)
for i, itempath in enumerate(all_items):
if i % batch_size == 0:
self.update_progress_bar(progress_var, progress_label, i, total_items, progress_bar)
window.update_idletasks()
if os.path.isfile(itempath):
file_size, file_ctime, file_mtime, file_ext = self.get_file_info(itempath)
if file_size is not None:
relative_path = os.path.relpath(itempath, folder_path)
folder_display = ""
if os.path.dirname(relative_path) != ".":
folder_display = os.path.dirname(relative_path)
row = [index, folder_display, os.path.basename(itempath)]
if export_options["size"]:
row.append(self.convert_size(file_size))
if export_options["ctime"]:
row.append(file_ctime)
if export_options["mtime"]:
row.append(file_mtime)
if export_options["ext"]:
row.append(file_ext)
if export_options["path"]:
row.append(itempath)
writer.writerow(row)
total_size += file_size
file_count += 1
file_type_counts[file_ext] += 1
index += 1
elif os.path.isdir(itempath):
relative_path = os.path.relpath(itempath, folder_path)
folder_display = relative_path
row = [index, folder_display, ""]
writer.writerow(row)
folder_count += 1
index += 1
self.update_progress_bar(progress_var, progress_label, total_items, total_items, progress_bar)
writer.writerow([])
writer.writerow(["统计信息"])
writer.writerow(["文件总数", file_count])
writer.writerow(["文件夹总数", folder_count])
writer.writerow(["文件夹总大小", self.convert_size(total_size)])
sorted_file_types = sorted(file_type_counts.items(), key=lambda x: x[1], reverse=True)
for file_type, count in sorted_file_types:
writer.writerow([f"{file_type} 文件数量", count])
status_label.config(text=f"文件信息已导出到: {csv_path}", foreground=COLORS["success"])
if messagebox.askyesno("导出完成", f"CSV文件已保存到: {csv_path}\n是否需要转换为Excel格式?"):
self.convert_csv_to_excel(csv_path, status_label)
else:
messagebox.showinfo("成功", f"文件信息已导出到: {csv_path}")
except Exception as e:
status_label.config(text="发生错误!", foreground=COLORS["error"])
messagebox.showerror("错误", f"导出到CSV失败: {e}")
finally:
self.exporting = False
self.update_progress_bar(progress_var, progress_label, 0, 1, progress_bar)
def convert_csv_to_excel(self, csv_path, status_label):
"""将CSV文件转换为Excel格式"""
try:
status_label.config(text="正在转换为Excel格式...", foreground=COLORS["accent"])
window.update_idletasks()
excel_path = os.path.splitext(csv_path)[0] + ".xlsx"
workbook = openpyxl.Workbook(write_only=True)
sheet = workbook.create_sheet()
with open(csv_path, 'r', encoding='utf-8-sig') as f:
reader = csv.reader(f)
for row in reader:
sheet.append(row)
workbook.save(excel_path)
status_label.config(text=f"文件信息已导出到: {excel_path}", foreground=COLORS["success"])
messagebox.showinfo("成功", f"Excel文件已保存到: {excel_path}")
except Exception as e:
status_label.config(text="转换Excel失败!", foreground=COLORS["error"])
messagebox.showerror("错误", f"转换为Excel失败: {e}")
def browse_folder(self, folder_entry, file_listbox, include_subfolders, progress_var, progress_label, progress_bar, scan_button, stop_button):
"""浏览文件夹,带进度显示"""
if self.scanning or self.exporting:
messagebox.showerror("提示", "请停止当前任务再选择目录!")
return
folder_selected = filedialog.askdirectory()
if folder_selected:
folder_entry.delete(0, tk.END)
folder_entry.insert(0, folder_selected)
def start_scan(self, folder_path, file_listbox, include_subfolders, progress_var, progress_label, progress_bar, scan_button, stop_button, status_label=None):
"""启动扫描操作"""
if not self.scanning and not self.exporting:
if not os.path.exists(folder_path):
messagebox.showerror("错误", f"文件夹路径不存在: {folder_path}")
if status_label:
status_label.config(text="扫描失败:文件夹不存在", foreground=COLORS["error"])
return
self.scanning = True
scan_button.config(state=tk.DISABLED)
stop_button.config(state=tk.NORMAL)
if status_label:
status_label.config(text="正在扫描...", foreground=COLORS["accent"])
self.current_thread = threading.Thread(target=self.update_file_list,
args=(folder_path, file_listbox, include_subfolders, progress_var, progress_label, progress_bar, scan_button, stop_button, status_label))
self.current_thread.start()
def update_file_list(self, folder_path, file_listbox, include_subfolders, progress_var, progress_label, progress_bar, scan_button=None, stop_button=None, status_label=None):
"""更新文件列表,带进度显示"""
self.file_count = 0
self.large_files_warning_given = False
if not self.scanning:
if status_label:
status_label.config(text="扫描已停止", foreground=COLORS["warning"])
return
all_items = []
if include_subfolders:
for root, dirs, files in os.walk(folder_path, topdown=True):
if not self.scanning:
break
with os.scandir(root) as it:
all_items.extend(entry.path for entry in it)
self.file_count += sum(1 for entry in it if entry.is_file())
else:
with os.scandir(folder_path) as it:
all_items = [entry.path for entry in it]
self.file_count = sum(1 for entry in it if entry.is_file())
batch_size = 1000
total_items = len(all_items)
file_listbox.delete(0, tk.END)
for i in range(0, total_items, batch_size):
if not self.scanning:
break
batch = all_items[i:i+batch_size]
file_listbox.insert(tk.END, *batch)
self.update_progress_bar(progress_var, progress_label, min(i+batch_size, total_items), total_items, progress_bar)
window.update_idletasks()
self.update_progress_bar(progress_var, progress_label, total_items, total_items, progress_bar)
if status_label:
status_label.config(text="扫描完成", foreground=COLORS["success"])
self.stop_scan(scan_button, stop_button)
def stop_scan(self, scan_button=None, stop_button=None, status_label=None):
"""停止扫描操作"""
self.scanning = False
if self.current_thread and self.current_thread.is_alive() and self.current_thread != threading.current_thread():
self.current_thread.join(timeout=1.0)
if self.current_thread.is_alive():
import ctypes
thread_id = self.current_thread.ident
ctypes.pythonapi.PyThreadState_SetAsyncExc(thread_id, ctypes.py_object(SystemExit))
if status_label:
status_label.config(text="扫描已强制停止", foreground=COLORS["warning"])
if scan_button and stop_button:
scan_button.config(state=tk.NORMAL)
stop_button.config(state=tk.DISABLED)
def start_export(self, folder_entry, export_options, status_label, file_listbox, include_subfolders, progress_var, progress_label, progress_bar, export_format):
"""启动导出操作"""
if not self.exporting and not self.scanning:
folder_path = folder_entry.get()
if not folder_path:
messagebox.showerror("错误", "请选择文件夹")
return
if file_listbox.size() == 0:
messagebox.showerror("错误", "请先扫描文件夹获取文件列表")
return
all_items = [file_listbox.get(i) for i in range(file_listbox.size())]
self.exporting = True
if export_format.get() == "csv":
thread = threading.Thread(target=self.export_file_info_to_csv,
args=(folder_path, export_options, status_label, all_items, include_subfolders, progress_var, progress_label, progress_bar))
else:
thread = threading.Thread(target=self.export_file_info_to_excel,
args=(folder_path, export_options, status_label, all_items, include_subfolders, progress_var, progress_label, progress_bar))
thread.start()
def create_gui():
"""创建GUI界面"""
global window
window = tk.Tk()
window.title("文件信息导出工具")
window.iconbitmap('folder.ico')
window.geometry("1000x700")
window.resizable(True, True)
window.minsize(900, 600)
window.configure(bg=COLORS["background"])
CustomStyle.configure_styles()
scanner = FileScanner()
font_style = ("微软雅黑", 10)
main_frame = ttk.Frame(window, style="Custom.TFrame")
main_frame.pack(fill=tk.BOTH, expand=True, padx=20, pady=20)
title_frame = ttk.Frame(main_frame, style="Custom.TFrame")
title_frame.pack(fill=tk.X, pady=(0, 20))
title_label = tk.Label(title_frame, text="文件信息导出工具", font=("微软雅黑", 16, "bold"),
fg=COLORS["primary"], bg=COLORS["background"])
title_label.pack()
subtitle_label = tk.Label(title_frame, text="扫描文件夹并导出文件信息到Excel或CSV",
font=("微软雅黑", 10), fg=COLORS["light_text"], bg=COLORS["background"])
subtitle_label.pack(pady=(5, 0))
folder_frame = ttk.LabelFrame(main_frame, text="选择文件夹", style="Custom.TLabelframe")
folder_frame.pack(pady=10, fill=tk.X)
folder_content_frame = ttk.Frame(folder_frame, style="Custom.TFrame")
folder_content_frame.pack(padx=10, pady=15, fill=tk.X)
folder_entry = tk.Entry(folder_content_frame, font=font_style, bg="white", fg=COLORS["text"])
folder_entry.pack(side=tk.LEFT, padx=10, expand=True, fill=tk.X)
include_subfolders = tk.BooleanVar(value=False)
subfolders_check = ttk.Checkbutton(folder_content_frame, text="包含子文件夹",
variable=include_subfolders, style="Custom.TCheckbutton")
subfolders_check.pack(side=tk.LEFT, padx=10)
folder_button = HoverButton(folder_content_frame, text="浏览",
command=lambda: scanner.browse_folder(folder_entry, file_listbox, include_subfolders,
progress_var, progress_label, progress_bar, scan_button, stop_button))
folder_button.pack(side=tk.LEFT, padx=10)
button_frame = ttk.Frame(folder_content_frame, style="Custom.TFrame")
button_frame.pack(side=tk.RIGHT, padx=10)
scan_button = HoverButton(button_frame, text="开始扫描", state=tk.NORMAL)
scan_button.pack(side=tk.LEFT, padx=5)
stop_button = HoverButton(button_frame, text="停止扫描", state=tk.DISABLED,
background=COLORS["error"], activebackground="#d32f2f",
command=lambda: scanner.stop_scan(scan_button, stop_button))
stop_button.pack(side=tk.LEFT, padx=5)
scan_button.config(command=lambda: scanner.start_scan(folder_entry.get(), file_listbox,
include_subfolders.get(), progress_var,
progress_label, progress_bar, scan_button, stop_button, status_label))
options_frame = ttk.LabelFrame(main_frame, text="导出选项", style="Custom.TLabelframe")
options_frame.pack(pady=10, fill=tk.X)
options_content_frame = ttk.Frame(options_frame, style="Custom.TFrame")
options_content_frame.pack(padx=10, pady=10, fill=tk.X)
export_options = {
"size": tk.BooleanVar(value=True),
"ctime": tk.BooleanVar(value=True),
"mtime": tk.BooleanVar(value=True),
"ext": tk.BooleanVar(value=False),
"path": tk.BooleanVar(value=False),
}
option_items = [
("文件大小", "size"),
("创建时间", "ctime"),
("修改时间", "mtime"),
("文件类型", "ext"),
("文件路径", "path")
]
for i, (text, key) in enumerate(option_items):
ttk.Checkbutton(options_content_frame, text=text, variable=export_options[key],
style="Custom.TCheckbutton").pack(side=tk.LEFT, padx=5)
format_frame = ttk.Frame(options_content_frame, style="Custom.TFrame")
format_frame.pack(side=tk.RIGHT, padx=10)
ttk.Label(format_frame, text="导出格式:", style="Custom.TLabel").pack(side=tk.LEFT, padx=5)
export_format = tk.StringVar(value="excel")
ttk.Radiobutton(format_frame, text="CSV格式 (更快)", variable=export_format, value="csv",
style="Custom.TRadiobutton").pack(side=tk.LEFT, padx=5)
ttk.Radiobutton(format_frame, text="Excel格式 (美观)", variable=export_format, value="excel",
style="Custom.TRadiobutton").pack(side=tk.LEFT, padx=5)
export_button_frame = ttk.Frame(main_frame, style="Custom.TFrame")
export_button_frame.pack(fill=tk.X, pady=10, padx=0)
export_button = HoverButton(export_button_frame, text="开始导出", width=20,
background=COLORS["accent"],
activebackground=COLORS["primary"],
command=lambda: scanner.start_export(folder_entry,
{k: v.get() for k, v in export_options.items()},
status_label, file_listbox, include_subfolders,
progress_var, progress_label, progress_bar, export_format))
export_button.pack(side=tk.RIGHT, pady=5, padx=0)
progress_frame = ttk.Frame(main_frame, style="Custom.TFrame")
progress_frame.pack(fill=tk.X, pady=10)
progress_status_frame = ttk.Frame(progress_frame, style="Custom.TFrame")
progress_status_frame.pack(fill=tk.X)
progress_left_frame = ttk.Frame(progress_status_frame, style="Custom.TFrame")
progress_left_frame.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 5), anchor=tk.W)
progress_container = ttk.Frame(progress_left_frame, style="Custom.TFrame")
progress_container.pack(fill=tk.X)
progress_var = tk.DoubleVar()
progress_bar = ttk.Progressbar(progress_container, orient="horizontal", mode="determinate",
variable=progress_var, style="Custom.Horizontal.TProgressbar")
progress_bar.pack(side=tk.LEFT, fill=tk.X, expand=True)
progress_label = tk.Label(progress_container, text="进度: 0%", font=font_style,
bg=COLORS["background"], fg=COLORS["primary"])
progress_label.pack(side=tk.LEFT, padx=(10, 0))
status_right_frame = ttk.Frame(progress_status_frame, style="Custom.TFrame")
status_right_frame.pack(side=tk.RIGHT, fill=tk.X, expand=True, padx=(5, 0), anchor=tk.E)
status_label = tk.Label(status_right_frame, text="", font=font_style,
bg=COLORS["background"], fg=COLORS["text"], justify=tk.RIGHT, anchor=tk.E)
status_label.pack(pady=1, fill=tk.X, expand=True, padx=0, anchor=tk.E)
list_frame = ttk.LabelFrame(main_frame, text="文件列表", style="Custom.TLabelframe")
list_frame.pack(pady=10, fill=tk.BOTH, expand=True)
listbox_frame = ttk.Frame(list_frame, style="Custom.TFrame")
listbox_frame.pack(padx=15, pady=15, fill=tk.BOTH, expand=True)
scrollbar = Scrollbar(listbox_frame)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
global file_listbox
file_listbox = Listbox(listbox_frame, yscrollcommand=scrollbar.set, selectmode=tk.EXTENDED,
font=font_style, bg="white", fg=COLORS["text"], borderwidth=1)
file_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
scrollbar.config(command=file_listbox.yview)
scanner.update_progress_bar(progress_var, progress_label, 0, 1, progress_bar)
floating_copyright_frame = tk.Frame(window, bg=COLORS["background"])
floating_copyright_frame.configure(bg=COLORS["background"], bd=0, relief=tk.GROOVE)
floating_copyright_label = tk.Label(floating_copyright_frame,
text="© 2025 文件信息导出工具 by Nobiyou ",
font=("微软雅黑", 8),
fg=COLORS["primary"],
bg=COLORS["background"])
floating_copyright_label.pack(padx=10, pady=1)
floating_copyright_frame.place(relx=0.5, rely=1.0, anchor="s", y=-5)
floating_copyright_frame.lift()
def update_copyright_position(event=None):
floating_copyright_frame.place(relx=0.5, rely=1.0, anchor="s", y=-5)
floating_copyright_frame.lift()
window.bind("<Configure>", update_copyright_position)
window.mainloop()
if __name__ == "__main__":
create_gui()