[Python] 纯文本查看 复制代码
import tkinter as tk
from tkinter import filedialog, messagebox, ttk
import os
import tempfile
import shutil
import zipfile
from datetime import datetime
from lxml import etree
from openpyxl import load_workbook
from PyPDF2 import PdfReader, PdfWriter
class FileMetadataCleaner:
def __init__(self, master):
self.master = master
master.title("文件元数据清理工具 v4.0")
master.geometry("800x600")
# 初始化变量
self.selected_files = []
self.output_dir = ""
self.error_log = []
# 创建界面组件
self.create_widgets()
def create_widgets(self):
# 主容器
main_frame = tk.Frame(self.master, padx=20, pady=20)
main_frame.pack(fill=tk.BOTH, expand=True)
# 文件选择区域
file_frame = tk.LabelFrame(main_frame, text="文件操作")
file_frame.pack(fill=tk.X, pady=5)
self.btn_select = tk.Button(
file_frame,
text="选择文件",
command=self.select_files,
width=15
)
self.btn_select.pack(side=tk.LEFT, padx=5)
self.btn_dir = tk.Button(
file_frame,
text="选择保存目录",
command=self.select_directory,
width=15
)
self.btn_dir.pack(side=tk.LEFT, padx=5)
self.btn_convert = tk.Button(
file_frame,
text="开始清理",
command=self.start_cleaning,
state=tk.DISABLED,
width=15
)
self.btn_convert.pack(side=tk.LEFT, padx=5)
# 文件列表
list_frame = tk.LabelFrame(main_frame, text="待处理文件")
list_frame.pack(fill=tk.BOTH, expand=True, pady=5)
self.listbox = tk.Listbox(
list_frame,
width=100,
height=15,
selectmode=tk.EXTENDED
)
self.listbox.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
# 进度条
self.progress = ttk.Progressbar(
main_frame,
orient=tk.HORIZONTAL,
mode='determinate'
)
self.progress.pack(fill=tk.X, pady=5)
# 状态栏
self.status = tk.Label(
main_frame,
text="就绪",
bd=1,
relief=tk.SUNKEN,
anchor=tk.W
)
self.status.pack(fill=tk.X)
def select_files(self):
filetypes = [
("所有文档", "*.doc;*.docx;*.xls;*.xlsx;*.pdf"),
("Word 文档", "*.doc;*.docx"),
("Excel 文档", "*.xls;*.xlsx"),
("PDF 文档", "*.pdf")
]
files = filedialog.askopenfilenames(filetypes=filetypes)
if files:
self.selected_files = list(files)
self.listbox.delete(0, tk.END)
for f in self.selected_files:
self.listbox.insert(tk.END, f)
self.check_ready_state()
self.update_status(f"已选择 {len(files)} 个文件")
def select_directory(self):
self.output_dir = filedialog.askdirectory()
if self.output_dir:
self.check_ready_state()
self.update_status(f"保存目录:{self.output_dir}")
def check_ready_state(self):
if self.selected_files and self.output_dir:
self.btn_convert.config(state=tk.NORMAL)
else:
self.btn_convert.config(state=tk.DISABLED)
def start_cleaning(self):
if not self.selected_files or not self.output_dir:
return
total_files = len(self.selected_files)
success_count = 0
self.error_log = []
try:
for index, file_path in enumerate(self.selected_files, 1):
filename = os.path.basename(file_path)
file_ext = os.path.splitext(file_path)[1].lower()
self.update_status(f"正在处理 {filename} ({index}/{total_files})")
self.progress['value'] = (index / total_files) * 100
self.master.update_idletasks()
try:
output_path = os.path.join(self.output_dir, filename)
if file_ext in ('.doc', '.docx'):
self.process_word(file_path, output_path)
elif file_ext in ('.xls', '.xlsx'):
self.process_excel(file_path, output_path)
elif file_ext == '.pdf':
self.process_pdf(file_path, output_path)
else:
raise ValueError("不支持的文件类型")
success_count += 1
except Exception as e:
self.error_log.append((filename, str(e)))
continue
result_msg = [
f"处理完成!成功:{success_count}/{total_files}",
f"失败:{len(self.error_log)} 个文件" if self.error_log else ""
]
self.update_status(" ".join(result_msg))
messagebox.showinfo("完成", "\n".join(result_msg))
if self.error_log:
self.save_error_log()
except Exception as e:
messagebox.showerror("系统错误", f"处理过程中断:{str(e)}")
finally:
self.progress['value'] = 0
def process_word(self, input_path, output_path):
"""处理Word文档(增强版)"""
try:
if not self.is_valid_docx(input_path):
raise ValueError("无效的Word文档格式")
temp_dir = tempfile.mkdtemp()
try:
# 解压原始文档
with zipfile.ZipFile(input_path) as zip_ref:
zip_ref.extractall(temp_dir)
# 清理核心元数据
core_xml_path = os.path.join(temp_dir, 'docProps', 'core.xml')
if os.path.exists(core_xml_path):
self.clean_core_xml(core_xml_path)
# 重新打包
with zipfile.ZipFile(output_path, 'w', zipfile.ZIP_DEFLATED) as new_zip:
for root, dirs, files in os.walk(temp_dir):
for file in files:
file_path = os.path.join(root, file)
arcname = os.path.relpath(file_path, temp_dir)
new_zip.write(file_path, arcname)
finally:
shutil.rmtree(temp_dir)
except Exception as e:
raise RuntimeError(f"Word处理失败: {str(e)}")
def is_valid_docx(self, file_path):
"""验证docx文件有效性"""
try:
with zipfile.ZipFile(file_path) as z:
return 'word/document.xml' in z.namelist()
except:
return False
def clean_core_xml(self, xml_path):
"""清理核心元数据XML"""
try:
parser = etree.XMLParser(remove_blank_text=True)
tree = etree.parse(xml_path, parser)
root = tree.getroot()
namespaces = {
'cp': "http://schemas.openxmlformats.org/package/2006/metadata/core-properties",
'dc': "http://purl.org/dc/elements/1.1/",
'dcterms': "http://purl.org/dc/terms/"
}
# 清理文本字段
elements_to_clear = [
('cp:lastModifiedBy', ''),
('dc:creator', ''),
('cp:revision', '1'),
('dc:title', ''),
('dc:subject', ''),
('cp:keywords', ''),
('dc:description', ''),
('cp:category', '')
]
for xpath, value in elements_to_clear:
for elem in root.xpath(xpath, namespaces=namespaces):
elem.text = value
# 设置基准时间
base_time = datetime(2000, 1, 1).isoformat()
for elem in root.xpath('//dcterms:created | //dcterms:modified', namespaces=namespaces):
elem.text = base_time
tree.write(xml_path, encoding='UTF-8', xml_declaration=True)
except Exception as e:
raise RuntimeError(f"XML清理失败: {str(e)}")
def process_excel(self, input_path, output_path):
"""处理Excel文档"""
try:
wb = load_workbook(input_path)
props = wb.properties
# 清除元数据
props.creator = props.lastModifiedBy = ""
props.title = props.subject = ""
props.description = props.keywords = ""
props.created = props.modified = datetime(2000, 1, 1)
wb.save(output_path)
except Exception as e:
raise RuntimeError(f"Excel处理失败: {str(e)}")
def process_pdf(self, input_path, output_path):
"""处理PDF文档"""
try:
reader = PdfReader(input_path)
writer = PdfWriter()
for page in reader.pages:
writer.add_page(page)
# 清除元数据
metadata = {
'/Author': '',
'/Title': '',
'/Subject': '',
'/Keywords': '',
'/CreationDate': datetime(2000, 1, 1).strftime('D:%Y%m%d%H%M%S'),
'/ModDate': datetime(2000, 1, 1).strftime('D:%Y%m%d%H%M%S')
}
writer.add_metadata(metadata)
with open(output_path, 'wb') as f:
writer.write(f)
except Exception as e:
raise RuntimeError(f"PDF处理失败: {str(e)}")
def save_error_log(self):
"""保存错误日志"""
log_path = os.path.join(self.output_dir, "processing_errors.log")
try:
with open(log_path, 'w', encoding='utf-8') as f:
f.write("文件处理错误日志\n")
f.write(f"生成时间:{datetime.now()}\n\n")
for filename, error in self.error_log:
f.write(f"文件:{filename}\n错误:{error}\n\n")
messagebox.showinfo("错误日志", f"错误日志已保存到:\n{log_path}")
except Exception as e:
messagebox.showerror("日志错误", f"无法保存错误日志:{str(e)}")
def update_status(self, message):
self.status.config(text=message)
self.master.update_idletasks()
if __name__ == "__main__":
root = tk.Tk()
app = FileMetadataCleaner(root)
root.mainloop()