发帖
 找回密码
 立即注册
搜索
3 0 0
资源分享 80 3 前天 11:27
我的工作场景会遇到很多文件夹下有很多文件,文件名不太规则而且还很长,我需要抓取这些文件名中的关键字来匹配其他的数据,然后在将这些文件改成规则的文件名,所以做了这个小工具分享给大家。
文件名抓改工具下载:
游客,如果您要查看本帖隐藏内容请回复


文件名修改后可以通过下面这个工具来验证
游客,如果您要查看本帖隐藏内容请回复

wechat_2025-06-06_112450_215.png
wechat_2025-06-06_112540_789.png

文件名抓改工具源码:
  1. import os
  2. import pandas as pd
  3. import tkinter as tk
  4. from tkinter import ttk
  5. from tkinter import filedialog
  6. from tkinter import messagebox
  7. import re
  8. import shutil
  9. from datetime import datetime
  10. from tkinter import scrolledtext
  11. from PIL import Image, ImageTk
  12. import io
  13. import sys

  14. class FileManagementApp:
  15.     def __init__(self, root):
  16.         self.root = root
  17.         self.root.title("文件名抓改系统V2.1 By来乐老弟")
  18.         self.root.geometry("1024x768")
  19.         self.root.minsize(800, 600)
  20.          
  21.         # 配置主窗口的网格布局
  22.         self.root.grid_rowconfigure(0, weight=1)
  23.         self.root.grid_columnconfigure(0, weight=1)
  24.          
  25.         # 设置主题样式
  26.         style = ttk.Style()
  27.         style.configure("TNotebook", background="#f0f0f0")
  28.         style.configure("TFrame", background="#ffffff")
  29.         style.configure("TButton", padding=5)
  30.         style.configure("Reward.TButton", font=("SimHei", 9, "bold"), foreground="blue")
  31.          
  32.         # 初始化选择的文件夹列表
  33.         self.selected_folders = []
  34.          
  35.         # 绑定快捷键
  36.         self.root.bind("<Control-o>", lambda e: self.select_folder())
  37.         self.root.bind("<Control-e>", lambda e: self.export_files())
  38.         self.root.bind("<Control-r>", lambda e: self.rename_files())
  39.         self.root.bind("<Control-z>", lambda e: self.undo_renaming())
  40.          
  41.         # 绑定快捷键
  42.         self.root.bind("<Control-o>", lambda e: self.select_folder())
  43.         self.root.bind("<Control-e>", lambda e: self.export_files())
  44.         self.root.bind("<Control-r>", lambda e: self.rename_files())
  45.         self.root.bind("<Control-z>", lambda e: self.undo_renaming())

  46.         # 初始化文件信息列表
  47.         self.all_files_info = []
  48.         # 初始化备份信息
  49.         self.backup_info = []
  50.          
  51.         # 绑定窗口关闭事件
  52.         self.root.protocol("WM_DELETE_WINDOW", self.on_closing)

  53.         # 创建主选项卡
  54.         self.notebook = ttk.Notebook(root)
  55.         self.notebook.grid(row=0, column=0, sticky="nsew", padx=5, pady=5)

  56.         # 创建文件名抓取页面
  57.         self.create_file_capture_tab()
  58.         # 创建文件名修改页面
  59.         self.create_file_rename_tab()

  60.     def create_file_capture_tab(self):
  61.         # 文件名抓取页面
  62.         self.capture_tab = ttk.Frame(self.notebook)
  63.         self.notebook.add(self.capture_tab, text='文件名抓取')

  64.         # 创建顶部框架
  65.         top_frame = ttk.Frame(self.capture_tab)
  66.         top_frame.pack(fill='x', padx=10, pady=5)
  67.          
  68.         # 文件夹选择
  69.         self.folder_label = ttk.Label(top_frame, text="选择文件夹(Ctrl+O):")
  70.         self.folder_label.pack(side='left', padx=5)

  71.         self.folder_entry = ttk.Entry(top_frame, width=50)
  72.         self.folder_entry.pack(side='left', padx=5)

  73.         self.browse_button = ttk.Button(top_frame, text="浏览...", command=self.select_folder)
  74.         self.browse_button.pack(side='left', padx=5)

  75.         self.append_button = ttk.Button(top_frame, text="追加文件夹", command=self.append_folder)
  76.         self.append_button.pack(side='left', padx=5)

  77.         # 创建主框架
  78.         main_frame = ttk.Frame(self.capture_tab)
  79.         main_frame.pack(fill='both', expand=True, padx=10, pady=5)
  80.          
  81.         # 子目录选项
  82.         options_frame = ttk.Frame(main_frame)
  83.         options_frame.pack(fill='x', pady=5)
  84.          
  85.         self.subdir_var = tk.BooleanVar(value=True)
  86.         self.subdir_check = ttk.Checkbutton(options_frame, text="读取子目录文件", variable=self.subdir_var)
  87.         self.subdir_check.pack(side='left')

  88.         # 文件列表显示(带滚动条)
  89.         list_frame = ttk.Frame(main_frame)
  90.         list_frame.pack(fill='both', expand=True)
  91.          
  92.         self.listbox = tk.Listbox(list_frame, selectmode='extended')
  93.         scrollbar_y = ttk.Scrollbar(list_frame, orient='vertical', command=self.listbox.yview)
  94.         scrollbar_x = ttk.Scrollbar(list_frame, orient='horizontal', command=self.listbox.xview)
  95.          
  96.         self.listbox.configure(yscrollcommand=scrollbar_y.set, xscrollcommand=scrollbar_x.set)
  97.          
  98.         scrollbar_y.pack(side='right', fill='y')
  99.         scrollbar_x.pack(side='bottom', fill='x')
  100.         self.listbox.pack(side='left', fill='both', expand=True)
  101.          
  102.         # 底部工具栏
  103.         bottom_frame = ttk.Frame(self.capture_tab)
  104.         bottom_frame.pack(fill='x', padx=10, pady=5)
  105.          
  106.         self.export_button = ttk.Button(bottom_frame, text="导出到Excel(Ctrl+E)", command=self.export_files)
  107.         self.export_button.pack(side='left', padx=5)

  108.         # 添加赏赞按钮
  109.         self.reward_button = ttk.Button(bottom_frame, text="赏赞", command=self.show_reward, style="Reward.TButton")
  110.         self.reward_button.pack(side='left', padx=5)

  111.         # 进度条
  112.         self.progress_var = tk.DoubleVar()
  113.         self.progress_bar = ttk.Progressbar(bottom_frame, length=300, mode='determinate', variable=self.progress_var)
  114.         self.progress_bar.pack(side='left', padx=10, fill='x', expand=True)
  115.          
  116.         # 手动分割区域
  117.         split_frame = ttk.Frame(self.capture_tab)
  118.         split_frame.pack(fill='x', padx=10, pady=5)
  119.          
  120.         split_label = ttk.Label(split_frame, text="手动分割设置:")
  121.         split_label.pack(side='left', padx=5)
  122.          
  123.         # 添加说明文字
  124.         help_text = "说明:通过添加分割字段来设置文件名的分割规则。在示例值中填入与实际文件名对应位置相同长度的字符,系统将按照示例值的长度依次分割文件名。"
  125.         help_label = ttk.Label(split_frame, text=help_text, wraplength=600)
  126.         help_label.pack(side='top', padx=5, pady=5)
  127.          
  128.         self.split_entries = []
  129.         self.split_frame = split_frame
  130.          
  131.         add_split_button = ttk.Button(split_frame, text="添加分割字段", command=self.add_split_entry)
  132.         add_split_button.pack(side='left', padx=5)
  133.          

  134.         # 状态显示
  135.         self.status_label = ttk.Label(bottom_frame, text="")
  136.         style = ttk.Style()
  137.         style.configure('Status.TLabel', foreground='blue')
  138.         self.status_label.configure(style='Status.TLabel')
  139.         self.status_label.pack(side='right', padx=5)
  140.          
  141.         # 绑定列表选择事件
  142.         self.listbox.bind('<<ListboxSelect>>', self.on_select_file)

  143.     def create_file_rename_tab(self):
  144.         # 文件名修改页面
  145.         self.rename_tab = ttk.Frame(self.notebook)
  146.         self.notebook.add(self.rename_tab, text='文件名修改')

  147.         # 创建顶部框架
  148.         top_frame = ttk.Frame(self.rename_tab)
  149.         top_frame.pack(fill='x', padx=10, pady=5)

  150.         # Excel文件选择
  151.         self.excel_label = ttk.Label(top_frame, text="选择Excel文件(Ctrl+E):")
  152.         self.excel_label.pack(side='left', padx=5)

  153.         self.excel_entry = ttk.Entry(top_frame, width=50)
  154.         self.excel_entry.pack(side='left', padx=5)

  155.         self.excel_button = ttk.Button(top_frame, text="浏览...", command=self.select_excel_file)
  156.         self.excel_button.pack(side='left', padx=5)

  157.         # 创建主框架
  158.         main_frame = ttk.Frame(self.rename_tab)
  159.         main_frame.pack(fill='both', expand=True, padx=10, pady=5)

  160.         # 预览表格
  161.         self.tree = ttk.Treeview(main_frame, columns=('A', 'B'), show='headings')
  162.         self.tree.pack(pady=10, fill='both', expand=True)

  163.         # 列选择
  164.         self.column_frame = ttk.Frame(main_frame)
  165.         self.column_frame.pack(fill='x', pady=5)

  166.         self.old_name_label = ttk.Label(self.column_frame, text="原文件名列:")
  167.         self.old_name_label.pack(side=tk.LEFT, padx=5)

  168.         self.old_name_combobox = ttk.Combobox(self.column_frame, state='readonly')
  169.         self.old_name_combobox.pack(side=tk.LEFT, padx=5)

  170.         self.new_name_label = ttk.Label(self.column_frame, text="新文件名列:")
  171.         self.new_name_label.pack(side=tk.LEFT, padx=5)

  172.         self.new_name_combobox = ttk.Combobox(self.column_frame, state='readonly')
  173.         self.new_name_combobox.pack(side=tk.LEFT, padx=5)

  174.         # 操作按钮
  175.         bottom_frame = ttk.Frame(self.rename_tab)
  176.         bottom_frame.pack(fill='x', padx=10, pady=5)

  177.         self.rename_button = ttk.Button(bottom_frame, text="执行重命名(Ctrl+R)", command=self.rename_files)
  178.         self.rename_button.pack(side='left', padx=5)

  179.         self.undo_button = ttk.Button(bottom_frame, text="撤回修改(Ctrl+Z)", command=self.undo_renaming)
  180.         self.undo_button.pack(side='left', padx=5)

  181.         self.cleanup_button = ttk.Button(bottom_frame, text="清理备份", command=self.cleanup_backups)
  182.         self.cleanup_button.pack(side='left', padx=5)

  183.         # 添加赏赞按钮
  184.         self.reward_button_rename = ttk.Button(bottom_frame, text="赏赞", command=self.show_reward, style="Reward.TButton")
  185.         self.reward_button_rename.pack(side='left', padx=5)

  186.         # 进度条
  187.         self.rename_progress_var = tk.DoubleVar()
  188.         self.rename_progress_bar = ttk.Progressbar(bottom_frame, length=300, mode='determinate', variable=self.rename_progress_var)
  189.         self.rename_progress_bar.pack(side='left', padx=10, fill='x', expand=True)

  190.         # 状态显示
  191.         self.rename_status_label = ttk.Label(bottom_frame, text="")
  192.         style = ttk.Style()
  193.         style.configure('RenameStatus.TLabel', foreground='blue')
  194.         self.rename_status_label.configure(style='RenameStatus.TLabel')
  195.         self.rename_status_label.pack(side='right', padx=5)

  196.     def select_folder(self):
  197.         folder = filedialog.askdirectory()
  198.         if folder:
  199.             self.selected_folders = [folder]
  200.             self.folder_entry.delete(0, tk.END)
  201.             self.folder_entry.insert(0, folder)
  202.             self.collect_files(self.selected_folders)
  203.             
  204.     def append_folder(self):
  205.         folder = filedialog.askdirectory()
  206.         if folder:
  207.             if folder not in self.selected_folders:
  208.                 self.selected_folders.append(folder)
  209.                 current_text = self.folder_entry.get()
  210.                 if current_text:
  211.                     self.folder_entry.delete(0, tk.END)
  212.                     self.folder_entry.insert(0, current_text + "; " + folder)
  213.                 else:
  214.                     self.folder_entry.insert(0, folder)
  215.                 self.collect_files(self.selected_folders)

  216.     def collect_files(self, folders):
  217.         self.listbox.delete(0, tk.END)
  218.         self.all_files_info = []
  219.         if isinstance(folders, str):
  220.             folders = [folders]

  221.         total_files = 0
  222.         for folder in folders:
  223.             total_files += sum([len(files) for _, _, files in os.walk(folder)])

  224.         processed_files = 0
  225.         for folder in folders:
  226.             for root, dirs, files in os.walk(folder):
  227.                 if not self.subdir_var.get() and root != folder:
  228.                     continue
  229.                  
  230.                 for file in files:
  231.                     file_path = os.path.join(root, file)
  232.                     file_info = self.analyze_filename(file_path)
  233.                     self.all_files_info.append(file_info)
  234.                     self.listbox.insert(tk.END, file_info['file_name'])
  235.                      
  236.                     processed_files += 1
  237.                     progress = (processed_files / total_files) * 100
  238.                     self.progress_var.set(progress)
  239.                     self.status_label.config(text=f"正在处理: {processed_files}/{total_files}")
  240.                     self.root.update()

  241.         if not self.all_files_info:
  242.             self.status_label.config(text="没有找到任何文件!", foreground="red")
  243.         else:
  244.             self.status_label.config(text=f"共找到 {len(self.all_files_info)} 个文件", foreground="green")
  245.          
  246.         self.progress_var.set(0)

  247.     def analyze_filename(self, file_path):
  248.         """文件名智能分析逻辑,提取文件名中的各种信息"""
  249.         file_stats = os.stat(file_path)
  250.         # 获取文件名和扩展名
  251.         file_name = os.path.basename(file_path)
  252.         # 获取文件所在文件夹名
  253.         folder_name = os.path.dirname(file_path)
  254.         # 获取文件大小(KB)
  255.         size_kb = file_stats.st_size / 1024
  256.         # 获取文件创建时间
  257.         create_time = datetime.fromtimestamp(file_stats.st_ctime).strftime('%Y-%m-%d %H:%M:%S')
  258.         # 获取文件修改时间
  259.         modify_time = datetime.fromtimestamp(file_stats.st_mtime).strftime('%Y-%m-%d %H:%M:%S')
  260.         # 获取文件扩展名
  261.         _, extension = os.path.splitext(file_path)
  262.          
  263.         # 自动提取文件名中的各种信息
  264.         try:
  265.             # 动态提取所有数字部分
  266.             numbers = re.findall(r'\d+', file_name)
  267.             number = numbers[0] if numbers else ''
  268.             
  269.             # 动态提取所有中文字符
  270.             chinese_chars = re.findall(r'[\u4e00-\u9fa5]+', file_name)
  271.             name = chinese_chars[0] if chinese_chars else ''
  272.             
  273.             # 动态提取电话号码(支持多种格式)
  274.             phone_patterns = [
  275.                 r'1\d{10}',
  276.                 r'\d{3} \d{4} \d{4}',
  277.                 r'\d{3}-\d{4}-\d{4}',
  278.                 r'\d{3}\.\d{4}\.\d{4}'
  279.             ]
  280.             phone = ''
  281.             for pattern in phone_patterns:
  282.                 match = re.search(pattern, file_name)
  283.                 if match:
  284.                     phone = match.group(0)
  285.                     break
  286.             
  287.             # 动态提取特殊符号
  288.             special_chars = re.findall(r'[^\w\s\u4e00-\u9fa5]', file_name)
  289.             special_chars = ''.join(set(special_chars))
  290.             
  291.             # 动态提取英文单词
  292.             english_words = re.findall(r'[A-Za-z]+', file_name)
  293.             
  294.             # 动态提取日期时间(支持更多格式)
  295.             datetime_patterns = [
  296.                 r'\d{4}-\d{2}-\d{2}',
  297.                 r'\d{4}\d{2}\d{2}',
  298.                 r'\d{2}-\d{2}-\d{4}',
  299.                 r'\d{2}\d{2}\d{4}',
  300.                 r'\d{4}年\d{2}月\d{2}日',
  301.                 r'\d{2}月\d{2}日\d{4}'
  302.             ]
  303.             dates = []
  304.             for pattern in datetime_patterns:
  305.                 dates.extend(re.findall(pattern, file_name))
  306.             
  307.             # 动态提取时间格式
  308.             time_patterns = [
  309.                 r'\d{2}:\d{2}:\d{2}',
  310.                 r'\d{2}-\d{2}-\d{2}',
  311.                 r'\d{2}\d{2}\d{2}',
  312.                 r'\d{2}时\d{2}分\d{2}秒'
  313.             ]
  314.             times = []
  315.             for pattern in time_patterns:
  316.                 times.extend(re.findall(pattern, file_name))
  317.             
  318.         except Exception as e:
  319.             number = ''
  320.             name = ''
  321.             phone = ''
  322.             special_chars = ''
  323.             english_words = []
  324.             dates = []
  325.             times = []
  326.          
  327.         return {
  328.             'folder': folder_name,
  329.             'file_name': file_name,
  330.             'number': number,
  331.             'name': name,
  332.             'phone': phone,
  333.             'extension': extension,
  334.             'size_kb': round(size_kb, 2),
  335.             'create_time': create_time,
  336.             'modify_time': modify_time,
  337.             'full_path': file_path,
  338.             'special_chars': special_chars,
  339.             'english_words': ','.join(english_words),
  340.             'dates': ','.join(dates),
  341.             'times': ','.join(times)
  342.         }

  343.     def select_excel_file(self):
  344.         file_path = filedialog.askopenfilename(filetypes=[("Excel files", "*.xlsx")])
  345.         if file_path:
  346.             self.excel_entry.delete(0, tk.END)
  347.             self.excel_entry.insert(0, file_path)
  348.             self.preview_excel(file_path)

  349.     def preview_excel(self, file_path):
  350.         try:
  351.             self.df = pd.read_excel(file_path)
  352.             self.update_column_comboboxes()
  353.             self.update_treeview()
  354.         except Exception as e:
  355.             messagebox.showerror("错误", f"无法打开Excel文件: {str(e)}")

  356.     def update_column_comboboxes(self):
  357.         columns = self.df.columns.tolist()
  358.         self.old_name_combobox['values'] = columns
  359.         self.new_name_combobox['values'] = columns

  360.     def update_treeview(self):
  361.         # 清空Treeview
  362.         for i in self.tree.get_children():
  363.             self.tree.delete(i)

  364.         # 设置列标题
  365.         columns = self.df.columns.tolist()
  366.         self.tree['columns'] = columns
  367.         for col in columns:
  368.             self.tree.heading(col, text=col)
  369.             self.tree.column(col, width=100, anchor='w')

  370.         # 添加数据
  371.         for _, row in self.df.iterrows():
  372.             self.tree.insert('', 'end', values=row.tolist())

  373.     def on_select_file(self, event):
  374.         """处理文件选择事件"""
  375.         pass
  376.      
  377.     def rename_files(self):
  378.         """重命名文件的主要方法"""
  379.         # 验证Excel文件和列选择
  380.         if not hasattr(self, 'df') or self.df.empty:
  381.             messagebox.showerror("错误", "请先选择并预览Excel文件!")
  382.             return

  383.         if not self.old_name_combobox.get() or not self.new_name_combobox.get():
  384.             messagebox.showerror("错误", "请选择原文件名列和新文件名列!")
  385.             return

  386.         try:
  387.             # 获取列名
  388.             old_col = self.old_name_combobox.get()
  389.             new_col = self.new_name_combobox.get()
  390.             rename_count = 0
  391.             failed_files = []
  392.             self.backup_info = []  # 清空之前的备份信息

  393.             # 处理每一行数据
  394.             for _, row in self.df.iterrows():
  395.                 old_name = str(row[old_col])
  396.                 new_name = str(row[new_col])

  397.                 if not old_name or not new_name:
  398.                     continue

  399.                 # 统一路径格式
  400.                 file_path = old_name.replace('\\', '/')
  401.                  
  402.                 # 验证文件路径
  403.                 if not file_path or not os.path.exists(file_path):
  404.                     # 尝试通过文件名匹配获取完整路径
  405.                     if hasattr(self, 'all_files_info') and self.all_files_info:
  406.                         print(f'尝试匹配文件名: {old_name}')
  407.                         file_path = self.find_file_by_name(old_name)
  408.                  
  409.                 # 最终验证路径
  410.                 if not file_path or not os.path.exists(file_path):
  411.                     failed_files.append(f"未找到文件路径: {file_path}")
  412.                     continue
  413.                  
  414.                 # 组合新路径
  415.                 new_path = os.path.join(os.path.dirname(file_path), new_name).replace('\\', '/')
  416.                  
  417.                 # 验证目标路径
  418.                 if os.path.exists(new_path):
  419.                     failed_files.append(f"目标文件已存在: {new_path}")
  420.                     continue
  421.                  
  422.                 # 验证权限
  423.                 if not os.access(file_path, os.W_OK):
  424.                     failed_files.append(f"没有写权限: {file_path}")
  425.                     continue
  426.                  
  427.                 try:
  428.                     # 创建备份
  429.                     backup_path = self.backup_file(file_path)
  430.                     if backup_path:
  431.                         self.backup_info.append((backup_path, file_path, new_path))
  432.                      
  433.                     # 执行重命名
  434.                     os.rename(file_path, new_path)
  435.                     rename_count += 1
  436.                     print(f'成功重命名: {file_path} -> {new_path}')
  437.                 except PermissionError:
  438.                     failed_files.append(f"权限错误: {file_path}")
  439.                     print(f'权限错误: {file_path}')
  440.                 except Exception as e:
  441.                     failed_files.append(f"{old_name} -> {new_name}: {str(e)}")
  442.                     print(f'重命名错误: {str(e)}')

  443.             # 显示结果
  444.             result_message = f"成功重命名 {rename_count} 个文件\n失败 {len(failed_files)} 个文件"
  445.             if failed_files:
  446.                 result_message += "\n失败原因:\n" + "\n".join(failed_files)
  447.             
  448.             style = ttk.Style()
  449.             style.configure('RenameSuccess.TLabel', foreground='green')
  450.             self.rename_status_label.configure(style='RenameSuccess.TLabel')
  451.             self.rename_status_label.configure(text=result_message)
  452.             messagebox.showinfo("完成", result_message)

  453.         except Exception as e:
  454.             messagebox.showerror("错误", f"重命名过程中发生错误: {str(e)}")
  455.             # 保存日志
  456.             if failed_files:
  457.                 self.save_log(failed_files)

  458.     def find_file_by_name(self, name):
  459.         """通过文件名查找文件路径"""
  460.         for file_info in self.all_files_info:
  461.             # 清理名称,忽略大小写、空格和特殊字符
  462.             clean_name = re.sub(r'[^\w\u4e00-\u9fa5]', '', name)
  463.             clean_file_name = re.sub(r'[^\w\u4e00-\u9fa5]', '', file_info['file_name'])
  464.             
  465.             # 尝试多种匹配方式
  466.             if (clean_name.lower() in clean_file_name.lower() or
  467.                 re.search(r'\b' + re.escape(clean_name) + r'\b', file_info['file_name'], re.IGNORECASE)):
  468.                 return file_info['full_path']
  469.         return None
  470.      
  471.     def backup_file(self, file_path):
  472.         """创建文件备份"""
  473.         try:
  474.             backup_dir = os.path.join(os.path.dirname(file_path), '.backup')
  475.             os.makedirs(backup_dir, exist_ok=True)
  476.             
  477.             timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
  478.             filename = os.path.basename(file_path)
  479.             backup_path = os.path.join(backup_dir, f"{timestamp}_{filename}")
  480.             
  481.             shutil.copy2(file_path, backup_path)
  482.             return backup_path
  483.         except Exception as e:
  484.             print(f"备份文件失败: {str(e)}")
  485.             return None

  486.     def add_split_entry(self):
  487.         """添加一个新的分割字段输入框"""
  488.         if len(self.split_entries) >= 10:  # 限制最大输入框数量
  489.             messagebox.showinfo("提示", "最多只能添加10个分割字段!")
  490.             return
  491.             
  492.         entry_frame = ttk.Frame(self.split_frame)
  493.         entry_frame.pack(fill='x', pady=2)
  494.          
  495.         # 添加列标签
  496.         col_label = ttk.Label(entry_frame, text=f"第{len(self.split_entries) + 1}列:")
  497.         col_label.pack(side='left', padx=2)
  498.          
  499.         # 添加示例值输入框
  500.         split_label = ttk.Label(entry_frame, text="示例值:")
  501.         split_label.pack(side='left', padx=2)
  502.          
  503.         split_entry = ttk.Entry(entry_frame, width=20)
  504.         split_entry.pack(side='left', padx=2)
  505.          
  506.         # 隐藏的name_entry用于保持代码兼容性
  507.         name_entry = ttk.Entry(entry_frame)
  508.         name_entry.pack_forget()
  509.          
  510.         def remove_entry():
  511.             entry_frame.destroy()
  512.             self.split_entries.remove((name_entry, split_entry, entry_frame))
  513.          
  514.         remove_button = ttk.Button(entry_frame, text="删除", command=remove_entry)
  515.         remove_button.pack(side='left', padx=2)
  516.          
  517.         self.split_entries.append((name_entry, split_entry, entry_frame))

  518.     def export_files(self):
  519.         """将收集的文件信息导出到Excel"""
  520.         if not self.all_files_info:
  521.             messagebox.showinfo("提示", "没有文件可以导出!")
  522.             return
  523.             
  524.         try:
  525.             # 创建导出文件名
  526.             timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
  527.             export_filename = f"文件列表_{timestamp}.xlsx"
  528.             export_path = filedialog.asksaveasfilename(
  529.                 defaultextension=".xlsx",
  530.                 filetypes=[("Excel files", "*.xlsx")],
  531.                 initialfile=export_filename
  532.             )
  533.             
  534.             if not export_path:
  535.                 return  # 用户取消了保存对话框
  536.                  
  537.             # 创建DataFrame并导出
  538.             df = pd.DataFrame(self.all_files_info)
  539.             
  540.             # 合并并转换文件路径格式
  541.             df['full_path'] = df.apply(lambda row: os.path.join(row['folder'], row['full_path']).replace('\\', '/'), axis=1)
  542.             df = df.drop('folder', axis=1)  # 删除单独的folder列
  543.             
  544.             # 添加手动分割的列
  545.             if self.split_entries:
  546.                 # 为每个文件名创建分割结果
  547.                 for idx, (name_entry, split_entry, _) in enumerate(self.split_entries):
  548.                     split_value = split_entry.get().strip()
  549.                     if not split_value:
  550.                         continue
  551.                         
  552.                     field_name = f'分割_{idx + 1}'  # 使用更有意义的列名
  553.                     split_length = len(split_value)  # 获取示例值的长度
  554.                      
  555.                     # 根据示例值的长度来分割文件名
  556.                     def split_filename(filename):
  557.                         # 如果文件名为空,返回空字符串
  558.                         if not filename:
  559.                             return ''
  560.                         
  561.                         try:
  562.                             # 获取当前处理的文件名位置
  563.                             start_pos = 0
  564.                             for i in range(idx):
  565.                                 prev_split_value = self.split_entries[i][1].get().strip()
  566.                                 if prev_split_value:
  567.                                     start_pos += len(prev_split_value)
  568.                              
  569.                             # 根据示例值的长度提取对应位数的字符
  570.                             if start_pos < len(filename):
  571.                                 return filename[start_pos:start_pos + split_length]
  572.                         except Exception:
  573.                             pass
  574.                              
  575.                         # 如果提取失败,返回空字符串
  576.                         return ''
  577.                      
  578.                     df[field_name] = df['file_name'].apply(split_filename)
  579.             
  580.             # 重新排列列的顺序
  581.             columns_order = ['file_name']  # 首先添加原始文件名
  582.             # 添加分割列(只添加实际存在的分割列)
  583.             split_columns = [f'分割_{i + 1}' for i in range(len(self.split_entries))
  584.                            if self.split_entries[i][1].get().strip()]
  585.             columns_order.extend(split_columns)
  586.             # 添加其他重要信息
  587.             other_columns = ['full_path', 'size_kb', 'create_time', 'modify_time']
  588.             columns_order.extend([col for col in other_columns if col in df.columns])
  589.             # 仅保留存在的列
  590.             existing_columns = [col for col in columns_order if col in df.columns]
  591.             df = df[existing_columns]
  592.             
  593.             # 导出到Excel
  594.             df.to_excel(export_path, index=False)
  595.             
  596.             style = ttk.Style()
  597.             style.configure('Success.TLabel', foreground='green')
  598.             self.status_label.configure(style='Success.TLabel')
  599.             self.status_label.configure(text=f"已成功导出 {len(self.all_files_info)} 个文件信息到 {export_path}")
  600.             messagebox.showinfo("成功", f"文件信息已成功导出到:\n{export_path}")
  601.             
  602.         except Exception as e:
  603.             style = ttk.Style()
  604.             style.configure('Error.TLabel', foreground='red')
  605.             self.status_label.configure(style='Error.TLabel')
  606.             self.status_label.configure(text=f"导出失败: {str(e)}")
  607.             messagebox.showerror("错误", f"导出文件时发生错误:\n{str(e)}")
  608.      
  609.     def undo_renaming(self):
  610.         """撤销之前的重命名操作"""
  611.         if not self.backup_info:
  612.             messagebox.showinfo("提示", "没有可以撤销的重命名操作!")
  613.             return
  614.             
  615.         try:
  616.             undo_count = 0
  617.             failed_undo = []
  618.             
  619.             # 反向遍历备份信息,以便按照相反的顺序撤销操作
  620.             for backup_path, original_path, new_path in reversed(self.backup_info):
  621.                 try:
  622.                     # 检查当前文件是否存在
  623.                     if os.path.exists(new_path):
  624.                         # 检查备份文件是否存在
  625.                         if os.path.exists(backup_path):
  626.                             # 先删除当前文件,然后恢复备份
  627.                             os.remove(new_path)
  628.                             shutil.copy2(backup_path, original_path)
  629.                             undo_count += 1
  630.                         else:
  631.                             # 如果备份不存在,尝试直接重命名回去
  632.                             os.rename(new_path, original_path)
  633.                             undo_count += 1
  634.                     else:
  635.                         # 如果新文件不存在,但备份存在,直接恢复备份
  636.                         if os.path.exists(backup_path):
  637.                             shutil.copy2(backup_path, original_path)
  638.                             undo_count += 1
  639.                         else:
  640.                             failed_undo.append(f"无法撤销: {new_path} -> {original_path} (备份文件不存在)")
  641.                 except Exception as e:
  642.                     failed_undo.append(f"撤销失败: {new_path} -> {original_path}: {str(e)}")
  643.             
  644.             # 清空备份信息
  645.             self.backup_info = []
  646.             
  647.             # 显示结果
  648.             result_message = f"成功撤销 {undo_count} 个重命名操作"
  649.             if failed_undo:
  650.                 result_message += f"\n失败 {len(failed_undo)} 个操作"
  651.                 result_message += "\n失败原因:\n" + "\n".join(failed_undo)
  652.             
  653.             style = ttk.Style()
  654.             style.configure('RenameSuccess.TLabel', foreground='green')
  655.             self.rename_status_label.configure(style='RenameSuccess.TLabel')
  656.             self.rename_status_label.configure(text=result_message)
  657.             messagebox.showinfo("完成", result_message)
  658.             
  659.         except Exception as e:
  660.             messagebox.showerror("错误", f"撤销重命名时发生错误: {str(e)}")
  661.      
  662.     def save_log(self, failed_items, prefix="重命名"):
  663.         """保存操作日志"""
  664.         try:
  665.             timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
  666.             log_filename = f"{prefix}日志_{timestamp}.txt"
  667.             
  668.             with open(log_filename, 'w', encoding='utf-8') as f:
  669.                 f.write(f"{prefix}操作日志 - {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
  670.                 f.write("=" * 50 + "\n\n")
  671.                  
  672.                 for item in failed_items:
  673.                     f.write(f"{item}\n")
  674.             
  675.             print(f"日志已保存到: {log_filename}")
  676.             return log_filename
  677.         except Exception as e:
  678.             print(f"保存日志失败: {str(e)}")
  679.             return None

  680.     def cleanup_backups(self):
  681.         """清理所有备份文件"""
  682.         try:
  683.             cleaned_count = 0
  684.             failed_paths = []
  685.             processed_dirs = set()

  686.             # 从已知的备份信息中获取目录
  687.             for backup_path, _, _ in self.backup_info:
  688.                 backup_dir = os.path.dirname(backup_path)
  689.                 processed_dirs.add(os.path.dirname(backup_dir))

  690.             # 从当前文件列表中获取目录
  691.             if hasattr(self, 'all_files_info'):
  692.                 for file_info in self.all_files_info:
  693.                     dir_path = os.path.dirname(file_info.get('file_path', ''))
  694.                     if dir_path:
  695.                         processed_dirs.add(dir_path)

  696.             # 清理每个目录中的备份文件
  697.             for dir_path in processed_dirs:
  698.                 backup_dir = os.path.join(dir_path, '.backup')
  699.                 if os.path.exists(backup_dir):
  700.                     try:
  701.                         shutil.rmtree(backup_dir)
  702.                         cleaned_count += 1
  703.                     except Exception as e:
  704.                         failed_paths.append(f"{backup_dir}: {str(e)}")

  705.             # 显示结果
  706.             result_message = f"成功清理 {cleaned_count} 个备份目录"
  707.             if failed_paths:
  708.                 result_message += f"\n清理失败 {len(failed_paths)} 个目录"
  709.                 result_message += "\n失败原因:\n" + "\n".join(failed_paths)

  710.             style = ttk.Style()
  711.             style.configure('RenameSuccess.TLabel', foreground='green')
  712.             self.rename_status_label.configure(style='RenameSuccess.TLabel')
  713.             self.rename_status_label.configure(text=result_message)
  714.             messagebox.showinfo("完成", result_message)

  715.         except Exception as e:
  716.             error_message = f"清理备份文件时发生错误: {str(e)}"
  717.             style = ttk.Style()
  718.             style.configure('RenameError.TLabel', foreground='red')
  719.             self.rename_status_label.configure(style='RenameError.TLabel')
  720.             self.rename_status_label.configure(text=error_message)
  721.             messagebox.showerror("错误", error_message)

  722.     def show_reward(self):
  723.         # 创建新窗口
  724.         reward_window = tk.Toplevel(self.root)
  725.         reward_window.title("赏赞码")
  726.         reward_window.geometry("400x500")
  727.          
  728.         # 加载并显示图片
  729.         try:
  730.             # 获取资源文件路径
  731.             if getattr(sys, 'frozen', False):
  732.                 # 如果是打包后的exe
  733.                 base_path = sys._MEIPASS
  734.             else:
  735.                 # 如果是开发环境
  736.                 base_path = os.path.abspath(os.path.dirname(__file__))
  737.             reward_path = os.path.join(base_path, "reward.jpg")
  738.             image = Image.open(reward_path)
  739.             # 调整图片大小以适应窗口
  740.             image = image.resize((300, 300), Image.Resampling.LANCZOS)
  741.             photo = ImageTk.PhotoImage(image)
  742.             
  743.             # 创建标签显示图片
  744.             image_label = ttk.Label(reward_window, image=photo)
  745.             image_label.image = photo  # 保持对图片的引用
  746.             image_label.pack(pady=20)
  747.             
  748.             # 添加文字提示
  749.             text_label = ttk.Label(reward_window, text="您的支持是对作者最大的帮助", font=("SimHei", 12))
  750.             text_label.pack(pady=10)
  751.             
  752.             # 添加关闭按钮
  753.             close_button = ttk.Button(reward_window, text="关闭", command=reward_window.destroy)
  754.             close_button.pack(pady=10)
  755.             
  756.         except Exception as e:
  757.             messagebox.showerror("错误", f"无法加载赏赞码图片: {str(e)}")
  758.             reward_window.destroy()
  759.      
  760.     def on_closing(self):
  761.         """窗口关闭时的处理函数"""
  762.         try:
  763.             # 遍历所有已知的文件路径
  764.             backup_dirs = set()
  765.             for file_info in self.all_files_info:
  766.                 folder = file_info['folder']
  767.                 backup_dir = os.path.join(folder, '.backup')
  768.                 if os.path.exists(backup_dir):
  769.                     backup_dirs.add(backup_dir)

  770.             # 清理所有.backup目录
  771.             for backup_dir in backup_dirs:
  772.                 try:
  773.                     shutil.rmtree(backup_dir)
  774.                     print(f"已清理临时文件目录: {backup_dir}")
  775.                 except Exception as e:
  776.                     print(f"清理临时文件目录失败: {backup_dir}, 错误: {str(e)}")
  777.         except Exception as e:
  778.             print(f"清理临时文件时发生错误: {str(e)}")
  779.         finally:
  780.             self.root.destroy()

  781. # 主函数
  782. if __name__ == "__main__":
  783.     root = tk.Tk()
  784.     app = FileManagementApp(root)
  785.     root.mainloop()
复制代码
文件路径验证工具源码
  1. import os
  2. from datetime import datetime
  3. import tkinter as tk
  4. from tkinter import ttk
  5. from tkinter import filedialog, messagebox
  6. import pandas as pd

  7. class VerifyFilesApp:
  8.     def __init__(self, master):
  9.         self.master = master
  10.         master.title('文件路径验证工具V1.1 by来乐老弟')

  11.         # 文件选择
  12.         # 文件选择
  13.         self.file_frame = ttk.LabelFrame(master, text='文件选择', padding=10)
  14.         self.file_frame.grid(row=0, column=0, columnspan=3, padx=10, pady=5, sticky='ew')

  15.         self.file_label = tk.Label(self.file_frame, text='Excel文件:')
  16.         self.file_label.grid(row=0, column=0, padx=5, pady=5)
  17.         self.file_entry = tk.Entry(self.file_frame, width=40)
  18.         self.file_entry.grid(row=0, column=1, padx=5, pady=5)
  19.         self.file_btn = tk.Button(self.file_frame, text='选择...', command=self.select_file)
  20.         self.file_btn.grid(row=0, column=2, padx=5, pady=5)

  21.         # 列选择
  22.         self.column_label = tk.Label(self.file_frame, text='对比列:')
  23.         self.column_label.grid(row=1, column=0, padx=5, pady=5)
  24.         self.column_combobox = ttk.Combobox(self.file_frame, width=37)
  25.         self.column_combobox.grid(row=1, column=1, padx=5, pady=5, columnspan=2, sticky='ew')

  26.         # 操作按钮
  27.         self.control_frame = ttk.Frame(master, padding=10)
  28.         self.control_frame.grid(row=1, column=0, columnspan=3, padx=10, pady=5, sticky='ew')

  29.         self.verify_btn = tk.Button(self.control_frame, text='开始验证', command=self.start_verification)
  30.         self.verify_btn.pack(side='left', padx=5)
  31.         self.save_btn = tk.Button(self.control_frame, text='保存日志', command=self.save_log)
  32.         self.save_btn.pack(side='left', padx=5)

  33.         # 日志显示
  34.         self.log_frame = ttk.LabelFrame(master, text='验证日志', padding=10)
  35.         self.log_frame.grid(row=2, column=0, columnspan=3, padx=10, pady=5, sticky='nsew')

  36.         self.log_text = tk.Text(self.log_frame, wrap=tk.WORD, font=('Arial', 10))
  37.         self.log_text.pack(fill='both', expand=True)

  38.         master.grid_rowconfigure(2, weight=1)
  39.         master.grid_columnconfigure(0, weight=1)
  40.         self.log_text.tag_configure('valid', foreground='green')
  41.         self.log_text.tag_configure('invalid', foreground='red')

  42.         # 帮助菜单
  43.         self.menu_bar = tk.Menu(master)
  44.         self.help_menu = tk.Menu(self.menu_bar, tearoff=0)
  45.         self.help_menu.add_command(label='使用帮助', command=self.show_help)
  46.         self.menu_bar.add_cascade(label='帮助', menu=self.help_menu)
  47.         master.config(menu=self.menu_bar)

  48.     def select_file(self):
  49.         filename = filedialog.askopenfilename(title='选择Excel文件', filetypes=[('Excel文件', '*.xlsx')])
  50.         if filename:
  51.             self.file_entry.delete(0, tk.END)
  52.             self.file_entry.insert(0, filename)
  53.             try:
  54.                 df = pd.read_excel(filename)
  55.                 self.column_combobox['values'] = df.columns.tolist()
  56.                 self.column_combobox.current(0)
  57.                 self.log_text.delete(1.0, tk.END)
  58.                 self.log_text.insert(tk.END, '文件预览:\n')
  59.                 self.log_text.insert(tk.END, df.to_string(index=False) + '\n\n')
  60.             except Exception as e:
  61.                 messagebox.showerror('错误', f'读取Excel文件失败:{str(e)}')

  62.     def save_log(self):
  63.         filename = filedialog.asksaveasfilename(title='保存日志文件', defaultextension='.txt', filetypes=[('Text Files', '*.txt')])
  64.         if filename:
  65.             try:
  66.                 with open(filename, 'w', encoding='utf-8') as f:
  67.                     f.write(self.log_text.get(1.0, tk.END))
  68.                 messagebox.showinfo('成功', '日志已成功保存')
  69.             except Exception as e:
  70.                 messagebox.showerror('错误', f'保存日志失败:{str(e)}')

  71.     def start_verification(self):
  72.         file_path = self.file_entry.get()
  73.         column_name = self.column_combobox.get()
  74.         if not file_path or not column_name:
  75.             messagebox.showerror('错误', '请选择Excel文件和对比列')
  76.             return

  77.         try:
  78.             df = pd.read_excel(file_path)
  79.             file_names = df[column_name].dropna().tolist()
  80.             file_names = [file_name.strip() for file_name in file_names if isinstance(file_name, str) and file_name.strip()]
  81.             folder_path = os.path.dirname(file_path)

  82.             self.log_text.delete(1.0, tk.END)
  83.             self.log_text.insert(tk.END, '验证时间: ' + datetime.now().strftime('%Y-%m-%d %H:%M:%S') + '\n')

  84.             total_count = len(file_names)
  85.             valid_count = 0

  86.             for idx, file_name in enumerate(file_names, 1):
  87.                 try:
  88.                     exists = os.path.exists(os.path.join(folder_path, file_name))
  89.                     status = '存在' if exists else '不存在'
  90.                     if exists:
  91.                         valid_count += 1
  92.                     self.log_text.insert(tk.END, f'{idx}. 文件名: {file_name} - {status}\n', 'valid' if status == '存在' else 'invalid')
  93.                 except Exception as e:
  94.                     self.log_text.insert(tk.END, f'{idx}. 文件名: {file_name} - 错误: {str(e)}\n')

  95.             self.log_text.insert(tk.END, '\n验证结果:\n')
  96.             self.log_text.insert(tk.END, f'总计:{total_count} 条路径\n')
  97.             self.log_text.insert(tk.END, f'有效路径:{valid_count} 条\n')
  98.             self.log_text.insert(tk.END, f'无效路径:{total_count - valid_count} 条\n')

  99.             messagebox.showinfo('验证结果', f'总计:{total_count} 条路径\n有效路径:{valid_count} 条\n无效路径:{total_count - valid_count} 条')

  100.         except Exception as e:
  101.             messagebox.showerror('错误', f'验证过程中发生错误:{str(e)}')

  102.     def show_help(self):
  103.         help_text = '''使用步骤:
  104. 1. 准备一个Excel表,表里有一列是你要对比文件的完整路径
  105. 2. 选择导入事先准备的Excel表
  106. 3. 选择数据的对比列
  107. 4. 选择开始验证
  108. 5. 查看对比结果或输出txt日志查看'''
  109.         messagebox.showinfo('使用帮助', help_text)

  110. if __name__ == '__main__':
  111.     root = tk.Tk()
  112.     app = VerifyFilesApp(root)
  113.     root.mainloop()
复制代码


wechat_2025-06-06_112715_981.png
转载自吾爱破解,原作者:dl_hou





──── 0人觉得很赞 ────

使用道具 举报

哇塞,这工具从功能到源码都超详细啊,原作者太牛啦!感觉能解决不少文件名处理的麻烦事儿,我先收下备用,说不定哪天就派上用场咯,感谢分享~
源码也是AI写的吗?
明月玩数码 发表于 2025-6-6 14:14
源码也是AI写的吗?

现在AI太猛了 各种程序都能写
您需要登录后才可以回帖 立即登录
高级模式