文章目录
- 前言
- 一、批量html文件转化为pdf
- 1. 问题描述
- 2.代码
- 3.程序下载链接
- 总结
前言
办公过程中,总是会遇到一些大量重复做的事情,通过生成一些小程序,提高办公效率。
一、批量html文件转化为pdf
1. 问题描述
我需要将若干的html文件转换为pdf,要求:
- 自主选择源文件和输出文件。
- 新文件命名方式,要么根据文件名重新生成学号+姓名,要么保持原文件名,至于更复杂的命名方式后续碰到了再增加。
2.代码
一共是两个版本,版本1是加了谷歌浏览器进去,可能有700MB;版本2是用的Edge浏览器,不到50MB。代码也贴出来,可以直接拿来运行,同时也生成了.exe文件,下载后双击直接运行即可。
版本1,使用谷歌,运行过程中差的包通过pip下载即可。
importosimportreimportasynciofrompathlibimportPathfromplaywright.async_apiimportasync_playwright# GUI 相关importtkinterastkfromtkinterimportfiledialog,messagebox# 3. 文件命名规则:10位学号 + 姓名# - 支持前面有任意前缀,例如:PJDS2022443948-张三.html,ZRDS2023444591-李四.html# - 如果你想要“学号-姓名.pdf”,把下面 new_filename 那一行改一下即可# 支持前缀的正则:前面可以有任意非数字字符,然后 10 位数字,再“-姓名.html”HTML_NAME_PATTERN=re.compile(r"\D*(\d{10})-([^.]+)\.html$",re.IGNORECASE)asyncdefconvert_all(source_folder:str,output_root:str,filename_mode:str="student"):""" filename_mode: - "student": 使用 10 位学号+姓名,格式:学号_姓名.pdf - "original": 保留原 html 文件名(去掉扩展名) """ifnotos.path.exists(source_folder):print(f"源路径不存在,请检查:{source_folder}")return# 确保输出根目录存在Path(output_root).mkdir(parents=True,exist_ok=True)total_html=0# 找到的 html 总数matched_html=0# 文件名匹配“学号+姓名”的 html 数converted=0# 实际成功转换的数量unmatched_samples=[]# 未匹配样例,最多展示 5 个,方便你检查命名asyncwithasync_playwright()asp:browser=awaitp.chromium.launch(headless=True)page=awaitbrowser.new_page()src_root=Path(source_folder).resolve()forroot,_,filesinos.walk(source_folder):forfileinfiles:ifnotfile.lower().endswith(".html"):continuetotal_html+=1iffilename_mode=="original":matched_html+=1new_filename=f"{Path(file).stem}.pdf"else:m=HTML_NAME_PATTERN.search(file)ifnotm:# 记录少量未匹配样例,方便你看命名有什么特例iflen(unmatched_samples)<5:unmatched_samples.append(Path(root,file).name)continuematched_html+=1sid,name=m.group(1),m.group(2)# 如需连字符:new_filename = f"{sid}-{name}.pdf"new_filename=f"{sid}_{name}.pdf"html_path=Path(root,file).resolve()input_uri=html_path.as_uri()# 计算相对“平时作业”的目录结构,在 output_root 中镜像创建rel_dir=Path(root).resolve().relative_to(src_root)target_dir=Path(output_root,rel_dir)target_dir.mkdir(parents=True,exist_ok=True)output_path=target_dir/new_filenameprint(f"处理:{html_path}->{output_path}")try:awaitpage.goto(input_uri)awaitpage.pdf(path=str(output_path.resolve()))converted+=1exceptExceptionase:print(f"转换失败:{file}, 异常:{e}")awaitbrowser.close()print("\n====== 处理完成 ======")print(f"源目录:{src_root}")print(f"扫描到 html 总数:{total_html}")print(f"匹配“10位学号-姓名”规则的 html 数:{matched_html}")ifunmatched_samples:print("未匹配到学号+姓名规则的示例(最多5个):")forsinunmatched_samples:print(" ",s)print(f"成功转换 pdf 数量:{converted}")print(f"输出根目录:{Path(output_root).resolve()}")defrun_gui():"""简单图形界面:选择源文件夹和输出文件夹,然后开始转换"""root=tk.Tk()root.title("批量 HTML 转 PDF")root.geometry("600x320")root.resizable(False,False)# 居中窗口root.update_idletasks()w=600h=320ws=root.winfo_screenwidth()hs=root.winfo_screenheight()x=int((ws/2)-(w/2))y=int((hs/2)-(h/2))root.geometry(f"{w}x{h}+{x}+{y}")source_var=tk.StringVar()output_var=tk.StringVar(value="归档完成_PDF版")filename_mode_var=tk.StringVar(value="student")status_var=tk.StringVar(value="请选择源/输出文件夹,选择文件命名方式,然后点击“开始转换”")defchoose_source():path=filedialog.askdirectory(title="选择包含 HTML 的“平时作业”总目录")ifpath:source_var.set(path)defchoose_output():path=filedialog.askdirectory(title="选择 PDF 输出根目录(可新建空文件夹)")ifpath:output_var.set(path)defstart_convert():source=source_var.get().strip()output=output_var.get().strip()ifnotsource:messagebox.showwarning("提示","请先选择源文件夹")returnifnotos.path.exists(source):messagebox.showerror("错误",f"源文件夹不存在:\n{source}")returnifnotoutput:messagebox.showwarning("提示","请先选择或填写输出文件夹")return# 如果输出目录不存在,可以提前创建Path(output).mkdir(parents=True,exist_ok=True)btn_convert.config(state=tk.DISABLED)status_var.set("正在转换,请稍候……(控制台也会显示详细进度)")root.update_idletasks()try:# 直接在按钮回调中运行异步任务,期间窗口会短暂停止响应,这是正常的asyncio.run(convert_all(source,output,filename_mode_var.get()))messagebox.showinfo("完成","全部转换完成!\n详细信息请查看控制台输出。")exceptExceptionase:messagebox.showerror("错误",f"转换过程中出现异常:\n{e}")finally:btn_convert.config(state=tk.NORMAL)status_var.set("转换结束,可以重新选择文件夹再次运行。")# ===== 布局 =====padding_x=10padding_y=8# 源文件夹tk.Label(root,text="源文件夹(包含 HTML 的总目录):").grid(row=0,column=0,sticky="w",padx=padding_x,pady=padding_y)tk.Entry(root,textvariable=source_var,width=55).grid(row=1,column=0,padx=padding_x,pady=0,sticky="w")tk.Button(root,text="浏览...",command=choose_source,width=10).grid(row=1,column=1,padx=padding_x,pady=0)# 输出文件夹tk.Label(root,text="输出根目录(将按原结构保存 PDF):").grid(row=2,column=0,sticky="w",padx=padding_x,pady=padding_y)tk.Entry(root,textvariable=output_var,width=55).grid(row=3,column=0,padx=padding_x,pady=0,sticky="w")tk.Button(root,text="浏览...",command=choose_output,width=10).grid(row=3,column=1,padx=padding_x,pady=0)# 文件命名方式tk.Label(root,text="文件命名方式:").grid(row=4,column=0,sticky="w",padx=padding_x,pady=padding_y)radio_frame=tk.Frame(root)radio_frame.grid(row=5,column=0,columnspan=2,sticky="w",padx=padding_x,pady=0)tk.Radiobutton(radio_frame,text="学号_姓名.pdf(需文件名匹配 10位学号-姓名.html)",variable=filename_mode_var,value="student",).pack(anchor="w")tk.Radiobutton(radio_frame,text="保持原文件名(去除 .html)",variable=filename_mode_var,value="original",).pack(anchor="w")# 状态栏tk.Label(root,textvariable=status_var,fg="blue").grid(row=6,column=0,columnspan=2,sticky="w",padx=padding_x,pady=padding_y)# 开始按钮btn_convert=tk.Button(root,text="开始转换",command=start_convert,width=12)btn_convert.grid(row=7,column=0,columnspan=2,pady=padding_y)root.mainloop()if__name__=="__main__":# 直接运行脚本时,启动 GUIrun_gui()版本2,使用edge。
importosimportreimportsysimportasyncioimportthreadingfrompathlibimportPathfromplaywright.async_apiimportasync_playwright# GUI 相关importtkinterastkfromtkinterimportfiledialog,messagebox,ttk# 为脚本/打包后的 exe 固定 Playwright 浏览器存放路径(程序所在目录下的 playwright-browsers)ifgetattr(sys,"frozen",False):# 打包后的 exeBASE_DIR=Path(sys.executable).parentelse:# 直接用脚本运行BASE_DIR=Path(__file__).parent PLAYWRIGHT_BROWSER_DIR=BASE_DIR/"playwright-browsers"os.environ.setdefault("PLAYWRIGHT_BROWSERS_PATH",str(PLAYWRIGHT_BROWSER_DIR))# 3. 文件命名规则:10位学号 + 姓名# - 支持前面有任意前缀,例如:PJDS2022443948-张三.html,ZRDS2023444591-李四.html# - 如果你想要“学号-姓名.pdf”,把下面 new_filename 那一行改一下即可# 支持前缀的正则:前面可以有任意非数字字符,然后 10 位数字,再“-姓名.html”HTML_NAME_PATTERN=re.compile(r"\D*(\d{10})-([^.]+)\.html$",re.IGNORECASE)asyncdefconvert_all(source_folder:str,output_root:str,filename_mode:str="student",progress_callback=None):""" filename_mode: - "student": 使用 10 位学号+姓名,格式:学号_姓名.pdf - "original": 保留原 html 文件名(去掉扩展名) progress_callback: 进度回调函数,接受 (current, total, message) 参数 """ifnotos.path.exists(source_folder):error_msg=f"源路径不存在,请检查:{source_folder}"print(error_msg)ifprogress_callback:progress_callback(0,0,error_msg)return# 确保输出根目录存在Path(output_root).mkdir(parents=True,exist_ok=True)total_html=0# 找到的 html 总数matched_html=0# 文件名匹配"学号+姓名"的 html 数converted=0# 实际成功转换的数量unmatched_samples=[]# 未匹配样例,最多展示 5 个,方便你检查命名ifprogress_callback:progress_callback(0,0,"正在启动浏览器,请稍候...")asyncwithasync_playwright()asp:# 直接使用本机已安装的 Microsoft Edge(Windows 默认自带),避免打包内置浏览器体积过大browser=awaitp.chromium.launch(headless=True,channel="msedge")page=awaitbrowser.new_page()ifprogress_callback:progress_callback(0,0,"浏览器已启动,正在扫描文件...")src_root=Path(source_folder).resolve()# 先扫描一遍,统计总数html_files=[]forroot,_,filesinos.walk(source_folder):forfileinfiles:ifnotfile.lower().endswith(".html"):continuetotal_html+=1html_files.append((root,file))iftotal_html==0:ifprogress_callback:progress_callback(0,0,"未找到任何 HTML 文件")returnifprogress_callback:progress_callback(0,total_html,f"找到{total_html}个 HTML 文件,开始转换...")# 开始转换foridx,(root,file)inenumerate(html_files,1):iffilename_mode=="original":matched_html+=1new_filename=f"{Path(file).stem}.pdf"else:m=HTML_NAME_PATTERN.search(file)ifnotm:# 记录少量未匹配样例,方便你看命名有什么特例iflen(unmatched_samples)<5:unmatched_samples.append(Path(root,file).name)continuematched_html+=1sid,name=m.group(1),m.group(2)# 如需连字符:new_filename = f"{sid}-{name}.pdf"new_filename=f"{sid}_{name}.pdf"html_path=Path(root,file).resolve()input_uri=html_path.as_uri()# 计算相对"平时作业"的目录结构,在 output_root 中镜像创建rel_dir=Path(root).resolve().relative_to(src_root)target_dir=Path(output_root,rel_dir)target_dir.mkdir(parents=True,exist_ok=True)output_path=target_dir/new_filenameifprogress_callback:progress_callback(idx,total_html,f"正在转换:{file}({idx}/{total_html})")print(f"处理:{html_path}->{output_path}")try:awaitpage.goto(input_uri)awaitpage.pdf(path=str(output_path.resolve()))converted+=1exceptExceptionase:print(f"转换失败:{file}, 异常:{e}")awaitbrowser.close()print("\n====== 处理完成 ======")print(f"源目录:{src_root}")print(f"扫描到 html 总数:{total_html}")print(f"匹配“10位学号-姓名”规则的 html 数:{matched_html}")ifunmatched_samples:print("未匹配到学号+姓名规则的示例(最多5个):")forsinunmatched_samples:print(" ",s)print(f"成功转换 pdf 数量:{converted}")print(f"输出根目录:{Path(output_root).resolve()}")defrun_gui():"""简单图形界面:选择源文件夹和输出文件夹,然后开始转换"""root=tk.Tk()root.title("批量 HTML 转 PDF")root.geometry("600x380")root.resizable(False,False)# 居中窗口root.update_idletasks()w=600h=380ws=root.winfo_screenwidth()hs=root.winfo_screenheight()x=int((ws/2)-(w/2))y=int((hs/2)-(h/2))root.geometry(f"{w}x{h}+{x}+{y}")source_var=tk.StringVar()output_var=tk.StringVar(value="归档完成_PDF版")filename_mode_var=tk.StringVar(value="student")# 修复这里的引号嵌套问题:外单引号,内部使用双引号status_var=tk.StringVar(value='请选择源/输出文件夹,选择文件命名方式,然后点击"开始转换"')# 进度条相关progress_var=tk.DoubleVar()progress_label_var=tk.StringVar(value="")defchoose_source():path=filedialog.askdirectory(title="选择包含 HTML 的“平时作业”总目录")ifpath:source_var.set(path)defchoose_output():path=filedialog.askdirectory(title="选择 PDF 输出根目录(可新建空文件夹)")ifpath:output_var.set(path)defupdate_progress(current,total,message):"""更新进度条和状态(在主线程中调用)"""iftotal>0:progress_var.set((current/total)*100)progress_label_var.set(f"{current}/{total}")status_var.set(message)root.update_idletasks()defrun_convert_thread():"""在后台线程中运行转换任务"""source=source_var.get().strip()output=output_var.get().strip()ifnotsource:root.after(0,lambda:messagebox.showwarning("提示","请先选择源文件夹"))returnifnotos.path.exists(source):root.after(0,lambda:messagebox.showerror("错误",f"源文件夹不存在:\n{source}"))returnifnotoutput:root.after(0,lambda:messagebox.showwarning("提示","请先选择或填写输出文件夹"))return# 如果输出目录不存在,可以提前创建Path(output).mkdir(parents=True,exist_ok=True)# 禁用按钮root.after(0,lambda:btn_convert.config(state=tk.DISABLED))root.after(0,lambda:update_progress(0,0,"准备开始转换..."))defprogress_callback(current,total,msg):"""进度回调,在主线程中更新UI"""root.after(0,lambda:update_progress(current,total,msg))try:# 在新的事件循环中运行异步任务loop=asyncio.new_event_loop()asyncio.set_event_loop(loop)loop.run_until_complete(convert_all(source,output,filename_mode_var.get(),progress_callback))loop.close()root.after(0,lambda:messagebox.showinfo("完成","全部转换完成!"))exceptExceptionase:root.after(0,lambda:messagebox.showerror("错误",f"转换过程中出现异常:\n{e}"))finally:root.after(0,lambda:btn_convert.config(state=tk.NORMAL))root.after(0,lambda:status_var.set("转换结束,可以重新选择文件夹再次运行。"))root.after(0,lambda:progress_var.set(0))root.after(0,lambda:progress_label_var.set(""))defstart_convert():"""启动转换(在后台线程中运行)"""# 在新线程中运行,避免阻塞GUIthread=threading.Thread(target=run_convert_thread,daemon=True)thread.start()# ===== 布局 =====padding_x=10padding_y=8# 源文件夹tk.Label(root,text="源文件夹(包含 HTML 的总目录):").grid(row=0,column=0,sticky="w",padx=padding_x,pady=padding_y)tk.Entry(root,textvariable=source_var,width=55).grid(row=1,column=0,padx=padding_x,pady=0,sticky="w")tk.Button(root,text="浏览...",command=choose_source,width=10).grid(row=1,column=1,padx=padding_x,pady=0)# 输出文件夹tk.Label(root,text="输出根目录(将按原结构保存 PDF):").grid(row=2,column=0,sticky="w",padx=padding_x,pady=padding_y)tk.Entry(root,textvariable=output_var,width=55).grid(row=3,column=0,padx=padding_x,pady=0,sticky="w")tk.Button(root,text="浏览...",command=choose_output,width=10).grid(row=3,column=1,padx=padding_x,pady=0)# 文件命名方式tk.Label(root,text="文件命名方式:").grid(row=4,column=0,sticky="w",padx=padding_x,pady=padding_y)radio_frame=tk.Frame(root)radio_frame.grid(row=5,column=0,columnspan=2,sticky="w",padx=padding_x,pady=0)tk.Radiobutton(radio_frame,text="学号_姓名.pdf(需文件名匹配 10位学号-姓名.html)",variable=filename_mode_var,value="student",).pack(anchor="w")tk.Radiobutton(radio_frame,text="保持原文件名(去除 .html)",variable=filename_mode_var,value="original",).pack(anchor="w")# 进度条progress_frame=tk.Frame(root)progress_frame.grid(row=6,column=0,columnspan=2,sticky="ew",padx=padding_x,pady=padding_y)progress_frame.columnconfigure(0,weight=1)tk.Label(progress_frame,textvariable=progress_label_var,font=("Arial",9)).grid(row=0,column=0,sticky="w")progress_bar=ttk.Progressbar(progress_frame,variable=progress_var,maximum=100,length=580)progress_bar.grid(row=1,column=0,sticky="ew",pady=(5,0))# 状态栏tk.Label(root,textvariable=status_var,fg="blue",wraplength=580,justify="left").grid(row=7,column=0,columnspan=2,sticky="w",padx=padding_x,pady=padding_y)# 开始按钮btn_convert=tk.Button(root,text="开始转换",command=start_convert,width=12)btn_convert.grid(row=8,column=0,columnspan=2,pady=padding_y)root.mainloop()if__name__=="__main__":# 直接运行脚本时,启动 GUIrun_gui()3.程序下载链接
百度网盘, 提取码: 1234
总结
1.增加html批量转pdf的程序。2025-12-16