Excel拆分和合并优化版本
代码总览
import pandas as pd
import os
import tkinter as tk
from tkinter import filedialog, messagebox
from tkinter import ttk
import math
from collections import defaultdict
import logging
from openpyxl import load_workbook, Workbook
from openpyxl.utils import get_column_letter
import copy
import time# --- 1. 配置日志 ---
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')# --- 2. UI 文本常量 (提高可读性) ---
UI_TEXTS = {'APP_TITLE': "Excel工具:拆分与合并 (V4 可读性重构)",'TAB_SPLIT': "文件拆分",'TAB_COMBINE': "智能合并",'SELECT_FILE_LABEL': "选择Excel文件:",'BROWSE_BUTTON': "浏览",'ROWS_PER_FILE_LABEL': "每份文件行数:",'ROWS_UNIT': "行",'START_SPLIT_BUTTON': "开始拆分",'START_COMBINE_BUTTON': "开始智能合并",'PRESERVE_FORMAT_LABEL': "保留视觉格式 (字体/颜色/列宽,速度很慢)",'SPEED_FIRST_INFO': "取消勾选 = 速度最快,仅保留文本数据 (推荐, 可保留 '00123' 和身份证号)",'COMBINE_BASE_LABEL': "选择基准Excel文件:",'COMBINE_INFO': "系统会自动查找与选定文件相似的其他文件进行合并\n(基于文件名模式识别,如:文件1.xlsx, 文件2.xlsx)",'FILE_INFO_DEFAULT': "请选择Excel文件",'FILE_INFO_SELECTED': "已选择: {filename}",'FILE_INFO_ROWS': "文件总行数: {rows} 行 (不含表头)",'WARN_TITLE': "警告",'WARN_NO_FILE': "请先选择有效的Excel文件!",'WARN_INVALID_ROWS': "请输入有效的行数(大于0)!",'WARN_NAN': "请输入有效的数字!",'WARN_EMPTY_FILE': "文件为空,没有数据可拆分。",'ERROR_TITLE': "错误",'ERROR_CANT_READ': "无法读取文件信息",'ERROR_NO_FILES_FOUND': "目录中没有找到 Excel 文件!",'ERROR_NO_SIMILAR_FILES': "没有找到与 '{filename}' 相似的其他文件",'ERROR_NO_DATA_READ': "无法读取任何文件的数据。",'ERROR_DURING_SPLIT': "拆分过程中出现错误:{error}",'ERROR_DURING_COMBINE': "合并过程中出现错误:{error}",'SUCCESS_TITLE': "成功",'SUCCESS_SPLIT_FORMAT': ("文件拆分完成!(保留格式)\n""共拆分成 {num_files} 个文件\n""总耗时: {time:.2f}秒"),'SUCCESS_SPLIT_FAST': ("文件拆分完成!(速度优先)\n""共拆分成 {num_files} 个文件\n""总耗时: {time:.2f}秒\n""(注意:仅保留文本数据,视觉格式已丢失)"),'SUCCESS_COMBINE_FORMAT': ("数据合并完成!(保留格式)\n""合并了 {num_files} 个文件\n""保存位置:{path}\n""总行数:{rows} (不含表头)\n""总耗时: {time:.2f}秒"),'SUCCESS_COMBINE_FAST': ("数据合并完成!(速度优先)\n""合并了 {num_files} 个文件\n""保存位置:{path}\n""总行数:{rows}\n""总耗时: {time:.2f}秒\n""(注意:仅保留文本数据,视觉格式已丢失)"),
}# --- 3. 逻辑类:文件名处理 ---
class ExcelFileUtils:"""只负责文件名匹配和模式识别"""@staticmethoddef extract_common_pattern(filename):name_without_ext = os.path.splitext(filename)[0]patterns = []if any(char.isdigit() for char in name_without_ext):non_digit_parts = []current_part = ""for char in name_without_ext:if char.isdigit():if current_part:non_digit_parts.append(current_part)current_part = ""else:current_part += charif current_part:non_digit_parts.append(current_part)if non_digit_parts:patterns.append(''.join(non_digit_parts).strip(' _-'))for sep in ['_', '-', ' ']:if sep in name_without_ext:parts = name_without_ext.split(sep)if len(parts) > 1:patterns.append(sep.join(parts[:-1]))if not patterns:patterns.append(name_without_ext)return patterns@staticmethoddef find_similar_files(all_files, patterns, base_file):similar_files = [base_file]for pattern in patterns:if pattern:for filename in all_files:if filename == base_file:continuename_without_ext = os.path.splitext(filename)[0]if (pattern in filename or pattern in name_without_ext orfilename.startswith(pattern) orname_without_ext.startswith(pattern)):similar_files.append(filename)return list(dict.fromkeys(similar_files))@staticmethoddef generate_output_name(patterns, base_file):if patterns:longest_pattern = max(patterns, key=len)if len(longest_pattern) > 3:return longest_patternreturn os.path.splitext(base_file)[0]# --- 4. 逻辑类:Pandas 快速处理 ---
class PandasProcessor:"""只负责速度优先的 Pandas 逻辑 (dtype=str)"""@staticmethoddef split_excel_pandas(file_path, rows_per_file):try:start_time = time.time()logging.info(f"[Pandas-STR] 开始拆分文件: {file_path}")df = pd.read_excel(file_path, dtype=str, engine='openpyxl') total_data_rows = len(df)if total_data_rows == 0:messagebox.showerror(UI_TEXTS['ERROR_TITLE'], UI_TEXTS['WARN_EMPTY_FILE'])returnnum_files = math.ceil(total_data_rows / rows_per_file)file_dir = os.path.dirname(file_path)file_name = os.path.splitext(os.path.basename(file_path))[0]for i in range(num_files):start_row = i * rows_per_fileend_row = min((i + 1) * rows_per_file, total_data_rows)chunk_df = df.iloc[start_row:end_row]output_filename = f"{file_name}_拆分_{i+1}.xlsx"output_path = os.path.join(file_dir, output_filename)chunk_df.to_excel(output_path, index=False, engine='openpyxl')logging.info(f"[Pandas-STR] 保存拆分文件: {output_path}")total_time = time.time() - start_timemessagebox.showinfo(UI_TEXTS['SUCCESS_TITLE'], UI_TEXTS['SUCCESS_SPLIT_FAST'].format(num_files=num_files, time=total_time))except Exception as e:messagebox.showerror(UI_TEXTS['ERROR_TITLE'], UI_TEXTS['ERROR_DURING_SPLIT'].format(error=e))logging.error(f"[Pandas-STR] 拆分过程中出现错误:{e}")@staticmethoddef combine_excel_pandas(selected_file):try:start_time = time.time()logging.info(f"[Pandas-STR] 开始合并文件,以 {selected_file} 为基准")file_dir = os.path.dirname(selected_file)file_name = os.path.basename(selected_file)listdir = os.listdir(file_dir)excel_files = [f for f in listdir if f.endswith(('.xlsx', '.xls'))]if not excel_files:messagebox.showerror(UI_TEXTS['ERROR_TITLE'], UI_TEXTS['ERROR_NO_FILES_FOUND'])returnbase_name = file_namecommon_patterns = ExcelFileUtils.extract_common_pattern(base_name)similar_files = ExcelFileUtils.find_similar_files(excel_files, common_patterns, base_name)if len(similar_files) <= 1:messagebox.showinfo(UI_TEXTS['SUCCESS_TITLE'], UI_TEXTS['ERROR_NO_SIMILAR_FILES'].format(filename=file_name))returnfile_list = "\n".join(similar_files)confirm = messagebox.askyesno(f"{UI_TEXTS['SUCCESS_TITLE']} ({UI_TEXTS['TAB_SPLIT']})", f"将合并以下文件 (仅保留文本数据):\n\n{file_list}\n\n共 {len(similar_files)} 个文件")if not confirm:logging.info("[Pandas-STR] 用户取消合并")returnall_dfs = []for filename in similar_files:file_path = os.path.join(file_dir, filename)try:df = pd.read_excel(file_path, dtype=str, engine='openpyxl')all_dfs.append(df)except Exception as e:logging.warning(f"[Pandas-STR] 无法读取文件 {filename}: {e}")if not all_dfs:messagebox.showerror(UI_TEXTS['ERROR_TITLE'], UI_TEXTS['ERROR_NO_DATA_READ'])returnmerged_df = pd.concat(all_dfs, ignore_index=True)total_rows = len(merged_df)output_name = ExcelFileUtils.generate_output_name(common_patterns, base_name)output_path = os.path.join(file_dir, f"{output_name}_合并结果_Pandas.xlsx")counter = 1while os.path.exists(output_path):output_path = os.path.join(file_dir, f"{output_name}_合并结果_Pandas({counter}).xlsx")counter += 1merged_df.to_excel(output_path, index=False, engine='openpyxl')total_time = time.time() - start_timelogging.info(f"[Pandas-STR] 保存合并结果: {output_path} (总耗时: {total_time:.2f}s)")messagebox.showinfo(UI_TEXTS['SUCCESS_TITLE'], UI_TEXTS['SUCCESS_COMBINE_FAST'].format(num_files=len(similar_files),path=output_path,rows=total_rows,time=total_time))except Exception as e:messagebox.showerror(UI_TEXTS['ERROR_TITLE'], UI_TEXTS['ERROR_DURING_COMBINE'].format(error=e))logging.error(f"[Pandas-STR] 合并过程中出现错误:{e}")# --- 5. 逻辑类:OpenPyXL 格式处理 ---
class OpenPyXLProcessor:"""只负责保留格式的 OpenPyXL 逻辑 (慢)"""@staticmethoddef _cache_styles(ws):"""辅助函数:缓存标题行和列宽"""header_format = []for row in ws.iter_rows(min_row=1, max_row=1):header_format.append([(cell.value, cell.number_format, cell.font, cell.fill, cell.border, cell.alignment) for cell in row])column_widths = {}for col_idx in range(1, ws.max_column + 1):col_letter = get_column_letter(col_idx)column_dim = ws.column_dimensions[col_letter]if column_dim and hasattr(column_dim, 'width') and column_dim.width:column_widths[col_letter] = column_dim.widthreturn header_format, column_widths@staticmethoddef _apply_header_styles(ws, header_format):"""辅助函数:应用缓存的标题行格式"""for col_idx, (value, num_format, font, fill, border, alignment) in enumerate(header_format[0], 1):cell = ws.cell(row=1, column=col_idx, value=value)cell.number_format = num_formatif font: cell.font = copy.copy(font)if fill: cell.fill = copy.copy(fill)if border: cell.border = copy.copy(border)if alignment: cell.alignment = copy.copy(alignment)return ws.max_column@staticmethoddef _apply_column_widths(ws, column_widths):"""辅助函数:应用缓存的列宽"""for col_letter, width in column_widths.items():ws.column_dimensions[col_letter].width = width@staticmethoddef split_excel_preserve_format(file_path, rows_per_file, total_data_rows):try:start_time = time.time()logging.info(f"[PreserveFormat] 开始拆分文件: {file_path}")wb = load_workbook(file_path)ws = wb.activeif total_data_rows == 0:messagebox.showerror(UI_TEXTS['ERROR_TITLE'], UI_TEXTS['WARN_EMPTY_FILE'])returnnum_files = math.ceil(total_data_rows / rows_per_file)file_dir = os.path.dirname(file_path)file_name = os.path.splitext(os.path.basename(file_path))[0]header_format, column_widths = OpenPyXLProcessor._cache_styles(ws)max_col = ws.max_columnfor i in range(num_files):file_start_time = time.time()start_row = i * rows_per_file + 2 end_row = min((i + 1) * rows_per_file + 1, total_data_rows + 1)new_wb = Workbook()new_ws = new_wb.activeOpenPyXLProcessor._apply_header_styles(new_ws, header_format)# 核心:逐个复制数据和格式data_row_idx = 2for row in range(start_row, end_row + 1):for col in range(1, max_col + 1):source_cell = ws.cell(row=row, column=col)target_cell = new_ws.cell(row=data_row_idx, column=col, value=source_cell.value)target_cell.number_format = source_cell.number_formatif source_cell.font:target_cell.font = copy.copy(source_cell.font)if source_cell.fill:target_cell.fill = copy.copy(source_cell.fill)if source_cell.border:target_cell.border = copy.copy(source_cell.border)if source_cell.alignment:target_cell.alignment = copy.copy(source_cell.alignment)data_row_idx += 1OpenPyXLProcessor._apply_column_widths(new_ws, column_widths)output_filename = f"{file_name}_拆分_{i+1}.xlsx"output_path = os.path.join(file_dir, output_filename)new_wb.save(output_path)file_time = time.time() - file_start_timelogging.info(f"[PreserveFormat] 保存拆分文件: {output_path} (耗时: {file_time:.2f}s)")total_time = time.time() - start_timemessagebox.showinfo(UI_TEXTS['SUCCESS_TITLE'], UI_TEXTS['SUCCESS_SPLIT_FORMAT'].format(num_files=num_files,time=total_time))except Exception as e:messagebox.showerror(UI_TEXTS['ERROR_TITLE'], UI_TEXTS['ERROR_DURING_SPLIT'].format(error=e))logging.error(f"[PreserveFormat] 拆分过程中出现错误:{e}")@staticmethoddef combine_excel_preserve_format(selected_file):try:start_time = time.time()logging.info(f"[PreserveFormat] 开始合并文件,以 {selected_file} 为基准")file_dir = os.path.dirname(selected_file)file_name = os.path.basename(selected_file)listdir = os.listdir(file_dir)excel_files = [f for f in listdir if f.endswith(('.xlsx', '.xls'))]if not excel_files:messagebox.showerror(UI_TEXTS['ERROR_TITLE'], UI_TEXTS['ERROR_NO_FILES_FOUND'])returnbase_name = file_namecommon_patterns = ExcelFileUtils.extract_common_pattern(base_name)similar_files = ExcelFileUtils.find_similar_files(excel_files, common_patterns, base_name)if len(similar_files) <= 1:messagebox.showinfo(UI_TEXTS['SUCCESS_TITLE'], UI_TEXTS['ERROR_NO_SIMILAR_FILES'].format(filename=file_name))returnfile_list = "\n".join(similar_files)confirm = messagebox.askyesno(f"{UI_TEXTS['SUCCESS_TITLE']} ({UI_TEXTS['TAB_COMBINE']})", f"将合并以下文件 (此操作可能较慢):\n\n{file_list}\n\n共 {len(similar_files)} 个文件")if not confirm:logging.info("[PreserveFormat] 用户取消合并")returntemplate_file = os.path.join(file_dir, similar_files[0])template_wb = load_workbook(template_file)template_ws = template_wb.activemerged_wb = Workbook()merged_ws = merged_wb.activeheader_format, column_widths = OpenPyXLProcessor._cache_styles(template_ws)max_col_count = OpenPyXLProcessor._apply_header_styles(merged_ws, header_format)current_row = 2total_rows = 0for filename in similar_files:file_path = os.path.join(file_dir, filename)wb = load_workbook(file_path)ws = wb.activefor row_idx, row in enumerate(ws.iter_rows(min_row=2), 2):for col_idx, cell in enumerate(row, 1):if col_idx <= max_col_count:target_cell = merged_ws.cell(row=current_row, column=col_idx, value=cell.value)target_cell.number_format = cell.number_formatif cell.font:target_cell.font = copy.copy(cell.font)if cell.fill:target_cell.fill = copy.copy(cell.fill)if cell.border:target_cell.border = copy.copy(cell.border)if cell.alignment:target_cell.alignment = copy.copy(cell.alignment)current_row += 1total_rows += 1logging.info(f"[PreserveFormat] 合并文件: {filename}, 添加了 {ws.max_row - 1} 行数据")wb.close()OpenPyXLProcessor._apply_column_widths(merged_ws, column_widths)output_name = ExcelFileUtils.generate_output_name(common_patterns, base_name)output_path = os.path.join(file_dir, f"{output_name}_合并结果.xlsx")counter = 1while os.path.exists(output_path):output_path = os.path.join(file_dir, f"{output_name}_合并结果({counter}).xlsx")counter += 1merged_wb.save(output_path)total_time = time.time() - start_timelogging.info(f"[PreserveFormat] 保存合并结果: {output_path} (总耗时: {total_time:.2f}s)")messagebox.showinfo(UI_TEXTS['SUCCESS_TITLE'], UI_TEXTS['SUCCESS_COMBINE_FORMAT'].format(num_files=len(similar_files),path=output_path,rows=total_rows,time=total_time))except Exception as e:messagebox.showerror(UI_TEXTS['ERROR_TITLE'], UI_TEXTS['ERROR_DURING_COMBINE'].format(error=e))logging.error(f"[PreserveFormat] 合并过程中出现错误:{e}")# --- 6. UI 类:拆分标签页 ---
class SplitterTab(ttk.Frame):"""文件拆分标签页的UI和逻辑 (已重构)"""def __init__(self, parent):super().__init__(parent, padding="15")self.total_data_rows = 0self.split_file_var = tk.StringVar()self.rows_var = tk.StringVar(value="10000")self.split_info_var = tk.StringVar(value=UI_TEXTS['FILE_INFO_DEFAULT'])self.preserve_format_var = tk.BooleanVar(value=True) # 默认保留格式self.create_widgets()def create_widgets(self):# --- 使用 UI_TEXTS 常量 ---ttk.Label(self, text=UI_TEXTS['SELECT_FILE_LABEL']).grid(row=0, column=0, sticky=tk.W, pady=5)ttk.Entry(self, textvariable=self.split_file_var, width=40).grid(row=0, column=1, padx=5, pady=5)ttk.Button(self, text=UI_TEXTS['BROWSE_BUTTON'], command=self.select_file).grid(row=0, column=2, padx=5, pady=5)ttk.Label(self, textvariable=self.split_info_var, foreground="blue").grid(row=1, column=1, sticky=tk.W, pady=2)ttk.Label(self, text=UI_TEXTS['ROWS_PER_FILE_LABEL']).grid(row=2, column=0, sticky=tk.W, pady=10)ttk.Entry(self, textvariable=self.rows_var, width=15).grid(row=2, column=1, sticky=tk.W, padx=5, pady=10)ttk.Label(self, text=UI_TEXTS['ROWS_UNIT']).grid(row=2, column=1, sticky=tk.W, padx=100, pady=10)ttk.Checkbutton(self, text=UI_TEXTS['PRESERVE_FORMAT_LABEL'], variable=self.preserve_format_var).grid(row=3, column=1, sticky=tk.W, pady=(10, 0))ttk.Label(self, text=UI_TEXTS['SPEED_FIRST_INFO'],foreground="gray", font=("Arial", 9)).grid(row=4, column=1, sticky=tk.W, padx=5, pady=(0, 10))ttk.Button(self, text=UI_TEXTS['START_SPLIT_BUTTON'], command=self.start_split).grid(row=5, column=1, pady=10)self.columnconfigure(1, weight=1)def select_file(self):file_path = filedialog.askopenfilename(title=UI_TEXTS['SELECT_FILE_LABEL'],filetypes=[("Excel files", "*.xlsx *.xls"), ("All files", "*.*")])if file_path:self.split_file_var.set(file_path)if self._read_row_count(file_path): # 自动读取行数self.split_info_var.set(UI_TEXTS['FILE_INFO_ROWS'].format(rows=self.total_data_rows))else:self.split_info_var.set(UI_TEXTS['ERROR_CANT_READ'])def _read_row_count(self, file_path):"""辅助函数:读取行数"""try:df = pd.read_excel(file_path, engine='openpyxl')self.total_data_rows = len(df)logging.info(f"选中拆分文件: {file_path}, 总行数: {self.total_data_rows}")return Trueexcept Exception as e:self.total_data_rows = 0logging.error(f"无法读取文件信息: {e}")return Falsedef _validate_input(self):"""辅助函数:集中处理所有输入验证"""file_path = self.split_file_var.get()if not file_path or not os.path.exists(file_path):messagebox.showwarning(UI_TEXTS['WARN_TITLE'], UI_TEXTS['WARN_NO_FILE'])return None, None# 如果选择文件时读取失败,在此重试if self.total_data_rows == 0 and UI_TEXTS['ERROR_CANT_READ'] in self.split_info_var.get():if not self._read_row_count(file_path):messagebox.showerror(UI_TEXTS['ERROR_TITLE'], UI_TEXTS['ERROR_CANT_READ'])return None, Noneif self.total_data_rows == 0:messagebox.showwarning(UI_TEXTS['WARN_TITLE'], UI_TEXTS['WARN_EMPTY_FILE'])return None, Nonetry:rows_per_file = int(self.rows_var.get())if rows_per_file <= 0:messagebox.showwarning(UI_TEXTS['WARN_TITLE'], UI_TEXTS['WARN_INVALID_ROWS'])return None, Noneexcept ValueError:messagebox.showwarning(UI_TEXTS['WARN_TITLE'], UI_TEXTS['WARN_NAN'])return None, Nonereturn file_path, rows_per_filedef start_split(self):"""开始拆分(逻辑更清晰)"""file_path, rows_per_file = self._validate_input()if not file_path:return # 验证失败if self.preserve_format_var.get():# 方案1:保留格式 (慢)OpenPyXLProcessor.split_excel_preserve_format(file_path, rows_per_file, self.total_data_rows)else:# 方案2:速度优先 (快)PandasProcessor.split_excel_pandas(file_path, rows_per_file)# --- 7. UI 类:合并标签页 ---
class CombinerTab(ttk.Frame):"""智能合并标签页的UI和逻辑 (已重构)"""def __init__(self, parent):super().__init__(parent, padding="15")self.combine_file_var = tk.StringVar()self.combine_info_var = tk.StringVar(value=UI_TEXTS['FILE_INFO_DEFAULT'])self.preserve_format_var = tk.BooleanVar(value=True)self.create_widgets()def create_widgets(self):# --- 使用 UI_TEXTS 常量 ---ttk.Label(self, text=UI_TEXTS['COMBINE_BASE_LABEL']).grid(row=0, column=0, sticky=tk.W, pady=5)ttk.Entry(self, textvariable=self.combine_file_var, width=50).grid(row=0, column=1, padx=5, pady=5)ttk.Button(self, text=UI_TEXTS['BROWSE_BUTTON'], command=self.select_file).grid(row=0, column=2, padx=5, pady=5)ttk.Label(self, textvariable=self.combine_info_var, foreground="blue").grid(row=1, column=1, sticky=tk.W, pady=5)ttk.Label(self, text=UI_TEXTS['COMBINE_INFO'], foreground="gray", font=("Arial", 9)).grid(row=2, column=1, sticky=tk.W, pady=5)ttk.Checkbutton(self, text=UI_TEXTS['PRESERVE_FORMAT_LABEL'], variable=self.preserve_format_var).grid(row=3, column=1, sticky=tk.W, pady=(10, 0))ttk.Label(self, text=UI_TEXTS['SPEED_FIRST_INFO'],foreground="gray", font=("Arial", 9)).grid(row=4, column=1, sticky=tk.W, padx=5, pady=(0, 10))ttk.Button(self, text=UI_TEXTS['START_COMBINE_BUTTON'], command=self.start_combine).grid(row=5, column=1, pady=15)self.columnconfigure(1, weight=1)def select_file(self):file_path = filedialog.askopenfilename(title=UI_TEXTS['COMBINE_BASE_LABEL'],filetypes=[("Excel files", "*.xlsx *.xls"), ("All files", "*.*")])if file_path:self.combine_file_var.set(file_path)file_name = os.path.basename(file_path)self.combine_info_var.set(UI_TEXTS['FILE_INFO_SELECTED'].format(filename=file_name))logging.info(f"选中合并基准文件: {file_path}")def start_combine(self):"""开始合并"""file_path = self.combine_file_var.get()if not file_path or not os.path.exists(file_path):messagebox.showwarning(UI_TEXTS['WARN_TITLE'], UI_TEXTS['WARN_NO_FILE'])returnif self.preserve_format_var.get():OpenPyXLProcessor.combine_excel_preserve_format(file_path)else:PandasProcessor.combine_excel_pandas(file_path)# --- 8. 主应用 ---
class ExcelToolApp:"""主应用程序类"""def __init__(self, root):self.root = rootself.root.title(UI_TEXTS['APP_TITLE'])self.root.geometry("600x380")self.create_widgets()def create_widgets(self):notebook = ttk.Notebook(self.root)notebook.pack(fill='both', expand=True, padx=10, pady=10)split_frame = SplitterTab(notebook)notebook.add(split_frame, text=UI_TEXTS['TAB_SPLIT'])combine_frame = CombinerTab(notebook)notebook.add(combine_frame, text=UI_TEXTS['TAB_COMBINE'])def run(self):self.root.mainloop()# --- 9. 启动入口 ---
if __name__ == "__main__":root = tk.Tk()app = ExcelToolApp(root)app.run()
打包步骤
创建一个虚拟环境
python -m venv venv_pack
激活虚拟环境
.\venv_pack\Scripts\activate
安装最小依赖: 在这个干净的环境中,只安装您的程序真正需要的库。
pip install pyinstaller pandas openpyxl
运行 PyInstaller 打包命令
选项 A:打包成一个文件夹(推荐,启动快)
这会创建一个 dist\Excel工具 文件夹,里面包含您的 Excel工具.exe 和所有依赖。启动速度比“单文件”快得多。
pyinstaller --noconsole --name="Excel工具" --upx-dir="." "excel_tool.py"
选项 B:打包成一个单独的 EXE 文件(整洁,但启动慢)
这会创建一个 dist\Excel工具.exe 单文件。
pyinstaller --noconsole --onefile --name="Excel工具" --upx-dir="." "excel_tool.py"
找到您的 EXE
打包完成后,PyInstaller 会创建几个文件夹:
-
build:(临时文件,可以删除)
-
dist:您的程序在这里!
-
Excel工具.spec:(打包配置文件,可以保留)
您只需要进入 dist 文件夹。
如果使用选项 A,您需要将整个 Excel工具 文件夹 复制给其他人。
如果使用选项 B,您只需要将 Excel工具.exe 文件复制给其他人。
