""" 日报自动化填写脚本 依赖安装: 1. 安装Python依赖包: pip install playwright pip install psutil pip install asyncio 2. 安装Playwright浏览器: playwright install chromium 注意事项: 1. 确保Chrome浏览器已安装 2. 脚本使用Chrome用户配置文件,路径为:C:/Users/zhens/AppData/Local/Google/Chrome/User Data 3. 运行前请确保没有其他Chrome进程在运行 4. 脚本会自动关闭所有Chrome进程,请确保没有重要工作未保存 5. 如果修改了Chrome用户配置路径,需要相应修改代码中的user_data_dir变量 6. 脚本运行时会打开Chrome浏览器窗口,请勿手动操作浏览器 7. 如果遇到网络问题,可以适当增加等待时间(asyncio.sleep的值) 8. 如果提交失败,可以检查网络连接或适当增加等待时间 """ import asyncio import os import psutil import time import tkinter as tk from tkinter import scrolledtext, messagebox, ttk from playwright.async_api import async_playwright import requests import logging import threading import re # 配置日志 logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') # 全局变量,用于控制生成状态 is_generating = False def count_chinese_chars(text): """统计中文字符数量""" return len(re.findall(r'[\u4e00-\u9fff]', text)) def count_words(text): """统计总字数(中文字符+其他字符)""" return count_chinese_chars(text) + len(re.sub(r'[\u4e00-\u9fff]', '', text)) def generate_daily_report(prompt, ollama_url, model_name): """使用Ollama生成日报内容""" try: logging.info(f"开始生成日报,使用模型: {model_name}") # 生成今日工作总结 summary_response = requests.post( f"{ollama_url}/api/generate", json={ "model": model_name, "prompt": f"你是一个专业的日报生成助手,请根据以下工作内容生成专业的今日工作总结:{prompt}", "stream": False } ) logging.info(f"今日工作总结生成状态码: {summary_response.status_code}") # 生成明日工作计划 plan_response = requests.post( f"{ollama_url}/api/generate", json={ "model": model_name, "prompt": f"你是一个专业的日报生成助手,请根据以下工作内容生成合理的明日工作计划:{prompt}", "stream": False } ) logging.info(f"明日工作计划生成状态码: {plan_response.status_code}") if summary_response.status_code == 200 and plan_response.status_code == 200: return summary_response.json()["response"], plan_response.json()["response"] else: error_msg = f"API调用失败: 总结状态码={summary_response.status_code}, 计划状态码={plan_response.status_code}" if summary_response.status_code != 200: error_msg += f"\n总结生成失败原因: {summary_response.text}" if plan_response.status_code != 200: error_msg += f"\n计划生成失败原因: {plan_response.text}" logging.error(error_msg) return None, error_msg except Exception as e: error_msg = f"生成日报时发生错误: {str(e)}" logging.error(error_msg) return None, error_msg def get_user_input(): """通过GUI窗口获取用户输入的日报内容""" root = tk.Tk() root.title("日报填写助手") root.geometry("1000x800") # 设置更大的初始窗口大小 # 设置窗口样式 root.configure(bg='#f0f0f0') # 修改字体设置方式 default_font = ('微软雅黑', 10) root.option_add('*Font', default_font) # 设置窗口图标(如果有的话) try: root.iconbitmap('icon.ico') except: pass # 创建主框架 main_frame = tk.Frame(root, bg='#f0f0f0') main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) # 创建标题 title_label = tk.Label(main_frame, text="日报填写", font=('微软雅黑', 16, 'bold'), bg='#f0f0f0', pady=5) title_label.pack() # 创建配置区域框架 config_frame = tk.Frame(main_frame, bg='#f0f0f0') config_frame.pack(fill=tk.X, pady=5) # 创建Ollama配置区域 ollama_frame = tk.LabelFrame(config_frame, text="Ollama配置", font=('微软雅黑', 12), bg='#f0f0f0', padx=10, pady=5) ollama_frame.pack(fill=tk.X, pady=2) # Ollama地址输入 url_frame = tk.Frame(ollama_frame, bg='#f0f0f0') url_frame.pack(fill=tk.X, pady=2) tk.Label(url_frame, text="Ollama地址:", font=('微软雅黑', 11), bg='#f0f0f0').pack(side=tk.LEFT) ollama_url = tk.Entry(url_frame, font=('微软雅黑', 11)) ollama_url.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5) ollama_url.insert(0, "http://localhost:11434") # 模型名称输入 model_frame = tk.Frame(ollama_frame, bg='#f0f0f0') model_frame.pack(fill=tk.X, pady=2) tk.Label(model_frame, text="模型名称:", font=('微软雅黑', 11), bg='#f0f0f0').pack(side=tk.LEFT) model_name = tk.Entry(model_frame, font=('微软雅黑', 11)) model_name.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5) model_name.insert(0, "llama2") # 创建提示词输入区域 prompt_frame = tk.LabelFrame(config_frame, text="工作内容提示词", font=('微软雅黑', 12), bg='#f0f0f0', padx=10, pady=5) prompt_frame.pack(fill=tk.X, pady=2) prompt_text = scrolledtext.ScrolledText(prompt_frame, height=3, font=('微软雅黑', 11), wrap=tk.WORD, padx=10, pady=10) prompt_text.pack(fill=tk.X, expand=True) # 创建进度条 progress_var = tk.DoubleVar() progress_bar = ttk.Progressbar(prompt_frame, variable=progress_var, maximum=100, mode='indeterminate') # 创建自动生成按钮 def auto_generate(): global is_generating if is_generating: return prompt = prompt_text.get("1.0", tk.END).strip() if not prompt: messagebox.showwarning("提示", "请输入工作内容提示词") return try: is_generating = True auto_generate_button.config(state=tk.DISABLED) progress_bar.pack(fill=tk.X, pady=5) progress_bar.start() def generate_thread(): try: summary, plan = generate_daily_report( prompt, ollama_url.get().strip(), model_name.get().strip() ) if summary and plan: root.after(0, lambda: update_texts(summary, plan)) else: root.after(0, lambda: messagebox.showerror("错误", f"生成日报失败:{plan}")) except Exception as e: root.after(0, lambda: messagebox.showerror("错误", f"生成日报时发生错误:{str(e)}")) finally: root.after(0, finish_generation) threading.Thread(target=generate_thread, daemon=True).start() except Exception as e: messagebox.showerror("错误", f"生成日报时发生错误:{str(e)}") finish_generation() def update_texts(summary, plan): summary_text.delete("1.0", tk.END) plan_text.delete("1.0", tk.END) summary_text.insert("1.0", summary) plan_text.insert("1.0", plan) update_counts() def finish_generation(): global is_generating is_generating = False auto_generate_button.config(state=tk.NORMAL) progress_bar.stop() progress_bar.pack_forget() auto_generate_button = tk.Button(prompt_frame, text="自动生成日报", command=auto_generate, font=('微软雅黑', 11), bg='#2196F3', fg='white', relief=tk.FLAT, cursor='hand2') auto_generate_button.pack(pady=5) # 创建内容区域框架 content_frame = tk.Frame(main_frame, bg='#f0f0f0') content_frame.pack(fill=tk.BOTH, expand=True, pady=5) # 创建今日工作总结输入区域 summary_frame = tk.LabelFrame(content_frame, text="今日工作总结", font=('微软雅黑', 12), bg='#f0f0f0', padx=10, pady=5) summary_frame.pack(fill=tk.BOTH, expand=True, pady=2) summary_text = scrolledtext.ScrolledText(summary_frame, height=8, font=('微软雅黑', 11), wrap=tk.WORD, padx=10, pady=10) summary_text.pack(fill=tk.BOTH, expand=True) # 添加字数统计 summary_count = tk.Label(summary_frame, text="字数:0", font=('微软雅黑', 10), bg='#f0f0f0', anchor='e') summary_count.pack(fill=tk.X, padx=10) # 创建明日工作计划输入区域 plan_frame = tk.LabelFrame(content_frame, text="明日工作计划", font=('微软雅黑', 12), bg='#f0f0f0', padx=10, pady=5) plan_frame.pack(fill=tk.BOTH, expand=True, pady=2) plan_text = scrolledtext.ScrolledText(plan_frame, height=8, font=('微软雅黑', 11), wrap=tk.WORD, padx=10, pady=10) plan_text.pack(fill=tk.BOTH, expand=True) # 添加字数统计 plan_count = tk.Label(plan_frame, text="字数:0", font=('微软雅黑', 10), bg='#f0f0f0', anchor='e') plan_count.pack(fill=tk.X, padx=10) # 创建底部控制区域 bottom_frame = tk.Frame(main_frame, bg='#f0f0f0') bottom_frame.pack(fill=tk.X, pady=5) # 创建复选框 keep_original = tk.BooleanVar(value=False) check_button = tk.Checkbutton(bottom_frame, text="保留原有内容", variable=keep_original, font=('微软雅黑', 11), bg='#f0f0f0') check_button.pack(side=tk.LEFT, padx=10) # 创建提交按钮 result = {"summary": "", "plan": "", "is_submitted": False, "keep_original": False} def submit(): if not summary_text.get("1.0", tk.END).strip() or not plan_text.get("1.0", tk.END).strip(): messagebox.showwarning("提示", "请填写完整的工作总结和计划") return result["summary"] = summary_text.get("1.0", tk.END).strip() result["plan"] = plan_text.get("1.0", tk.END).strip() result["keep_original"] = keep_original.get() result["is_submitted"] = True root.quit() root.destroy() submit_button = tk.Button(bottom_frame, text="提交", command=submit, font=('微软雅黑', 12, 'bold'), bg='#4CAF50', fg='white', width=15, height=2, relief=tk.FLAT, cursor='hand2') submit_button.pack(side=tk.RIGHT, padx=10) # 添加字数统计更新函数 def update_counts(event=None): summary_text_content = summary_text.get("1.0", tk.END).strip() plan_text_content = plan_text.get("1.0", tk.END).strip() summary_count.config(text=f"字数:{count_words(summary_text_content)}(中文字符:{count_chinese_chars(summary_text_content)})") plan_count.config(text=f"字数:{count_words(plan_text_content)}(中文字符:{count_chinese_chars(plan_text_content)})") # 绑定字数统计更新 summary_text.bind('', update_counts) plan_text.bind('', update_counts) # 初始更新字数统计 update_counts() # 设置窗口居中 root.update_idletasks() width = root.winfo_width() height = root.winfo_height() x = (root.winfo_screenwidth() // 2) - (width // 2) y = (root.winfo_screenheight() // 2) - (height // 2) root.geometry(f'{width}x{height}+{x}+{y}') # 设置窗口最小尺寸 root.minsize(900, 700) # 设置窗口关闭确认 def on_closing(): if messagebox.askokcancel("确认", "确定要关闭窗口吗?"): root.destroy() root.protocol("WM_DELETE_WINDOW", on_closing) # 设置默认焦点 prompt_text.focus_set() root.mainloop() return result["summary"], result["plan"], result["is_submitted"], result["keep_original"] # 执行自动化浏览器操作的协程函数 def close_chrome(): """关闭所有Chrome进程""" for proc in psutil.process_iter(['name']): try: if proc.info['name'] == 'chrome.exe': proc.kill() except (psutil.NoSuchProcess, psutil.AccessDenied): pass # 等待进程完全关闭 time.sleep(2) async def run(playwright): try: # 确保Chrome已关闭 close_chrome() # 使用用户实际的Chrome配置,配置查询chrome://version user_data_dir = r'C:/Users/zhens/AppData/Local/Google/Chrome/User Data' def text_to_div_html(text): """将多行文本转换为
...
格式的HTML""" lines = [line.strip() for line in text.strip().splitlines() if line.strip()] return ''.join(f'
{line}
' for line in lines) # 获取用户输入的日报内容 summary_text, plan_text, is_submitted, keep_original = get_user_input() # 如果用户取消输入,直接退出程序 if not is_submitted: print("用户取消了日报填写,程序退出") return summary_html = text_to_div_html(summary_text) plan_html = text_to_div_html(plan_text) # 使用已有的Chrome配置文件启动浏览器 context = await playwright.chromium.launch_persistent_context( user_data_dir, channel="chrome", headless=False, args=[ '--start-maximized', '--disable-blink-features=AutomationControlled', '--profile-directory=Default', '--no-first-run', '--no-default-browser-check' ], ignore_default_args=['--enable-automation'] ) try: # 创建新页面 page = await context.new_page() # 设置实际的User-Agent await page.set_extra_http_headers({ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36' }) # 访问页面并等待网络空闲 await page.goto('https://doc.weixin.qq.com/forms/j/AFIADwd9AA4AUcA6AbCADgJXbq19fUR0j_base?page=6', wait_until='networkidle') # 等待元素出现并点击 await page.wait_for_selector('div.TitleBarBtn_title__dKAyV:text("填写")', timeout=60000) await page.click('div.TitleBarBtn_title__dKAyV:text("填写")', delay=100) # 等待操作完成,确保页面完全加载 await asyncio.sleep(5) # 查找所有可编辑的输入框 editors = await page.query_selector_all('.maileditor-editorview[contenteditable="true"]') print("找到输入框数量:", len(editors)) # 今日工作总结输入 # 1. 先点击输入框,确保获得焦点 await page.locator('div.question:has-text("今日工作总结") .maileditor-editorview[contenteditable="true"]').click() # 2. 如果不保留原有内容,则清空输入框 if not keep_original: await page.keyboard.press('Control+A') await page.keyboard.press('Delete') # 3. 使用键盘输入方式模拟真实输入 await page.keyboard.type(summary_text) # 4. 等待输入完成,确保内容被正确输入 await asyncio.sleep(2) # 明日工作计划输入 # 1. 先点击输入框,确保获得焦点 await page.locator('div.question:has-text("明日工作计划") .maileditor-editorview[contenteditable="true"]').click() # 2. 如果不保留原有内容,则清空输入框 if not keep_original: await page.keyboard.press('Control+A') await page.keyboard.press('Delete') # 3. 使用键盘输入方式模拟真实输入 await page.keyboard.type(plan_text) # 4. 等待输入完成 await asyncio.sleep(2) # 等待内容保存 # 给系统足够的时间来保存输入的内容 await asyncio.sleep(5) # 点击提交按钮 # 1. 等待提交按钮出现 await page.wait_for_selector('button.FillFooter_confirm__0ClPl', timeout=60000) # 2. 点击提交按钮,添加延迟模拟真实点击 await page.click('button.FillFooter_confirm__0ClPl', delay=100) # 等待提交完成 # 给系统足够的时间来处理提交操作 await asyncio.sleep(5) # 显示提交成功提示 print("日报提交成功!") except Exception as e: print(f"提交过程中发生错误: {str(e)}") raise finally: # 确保浏览器正常关闭 await context.close() except Exception as e: print(f"程序运行过程中发生错误: {str(e)}") raise # 主函数,用于启动 playwright 并调用 run 函数 async def main(): async with async_playwright() as playwright: await run(playwright) # 判断当前环境是否已经有事件循环在运行 if __name__ == "__main__": try: # 尝试获取正在运行的事件循环(某些 IDE/Jupyter 会预先启动) loop = asyncio.get_running_loop() except RuntimeError: loop = None # 如果事件循环存在且正在运行(比如在 Jupyter Notebook 中) if loop and loop.is_running(): print("检测到事件循环正在运行,使用 create_task 启动协程") asyncio.create_task(main()) # 使用 create_task 异步运行 else: # 否则,正常使用 asyncio.run 启动主协程 asyncio.run(main())