autowrite_day_v0.0.1.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. """
  2. 日报自动化填写脚本
  3. 依赖安装:
  4. 1. 安装Python依赖包:
  5. pip install playwright
  6. pip install psutil
  7. pip install asyncio
  8. 2. 安装Playwright浏览器:
  9. playwright install chromium
  10. 注意事项:
  11. 1. 确保Chrome浏览器已安装
  12. 2. 脚本使用Chrome用户配置文件,路径为:C:/Users/zhens/AppData/Local/Google/Chrome/User Data
  13. 3. 运行前请确保没有其他Chrome进程在运行
  14. 4. 脚本会自动关闭所有Chrome进程,请确保没有重要工作未保存
  15. 5. 如果修改了Chrome用户配置路径,需要相应修改代码中的user_data_dir变量
  16. 6. 脚本运行时会打开Chrome浏览器窗口,请勿手动操作浏览器
  17. 7. 如果遇到网络问题,可以适当增加等待时间(asyncio.sleep的值)
  18. 8. 如果提交失败,可以检查网络连接或适当增加等待时间
  19. """
  20. import asyncio
  21. import os
  22. import psutil
  23. import time
  24. import tkinter as tk
  25. from tkinter import scrolledtext
  26. from playwright.async_api import async_playwright
  27. def get_user_input():
  28. """通过GUI窗口获取用户输入的日报内容"""
  29. root = tk.Tk()
  30. root.title("日报填写助手")
  31. root.geometry("900x700")
  32. # 设置窗口样式
  33. root.configure(bg='#f0f0f0')
  34. root.option_add('*Font', '微软雅黑 10')
  35. # 创建主框架
  36. main_frame = tk.Frame(root, bg='#f0f0f0', padx=20, pady=20)
  37. main_frame.pack(fill=tk.BOTH, expand=True)
  38. # 创建标题
  39. title_label = tk.Label(main_frame,
  40. text="日报填写",
  41. font=("微软雅黑", 16, "bold"),
  42. bg='#f0f0f0',
  43. pady=10)
  44. title_label.pack()
  45. # 创建今日工作总结输入区域
  46. summary_frame = tk.LabelFrame(main_frame,
  47. text="今日工作总结",
  48. font=("微软雅黑", 12),
  49. bg='#f0f0f0',
  50. padx=10,
  51. pady=5)
  52. summary_frame.pack(fill=tk.BOTH, expand=True, pady=10)
  53. summary_text = scrolledtext.ScrolledText(summary_frame,
  54. width=80,
  55. height=8,
  56. font=("微软雅黑", 11),
  57. wrap=tk.WORD,
  58. padx=10,
  59. pady=10)
  60. summary_text.pack(fill=tk.BOTH, expand=True)
  61. # 创建明日工作计划输入区域
  62. plan_frame = tk.LabelFrame(main_frame,
  63. text="明日工作计划",
  64. font=("微软雅黑", 12),
  65. bg='#f0f0f0',
  66. padx=10,
  67. pady=5)
  68. plan_frame.pack(fill=tk.BOTH, expand=True, pady=10)
  69. plan_text = scrolledtext.ScrolledText(plan_frame,
  70. width=80,
  71. height=8,
  72. font=("微软雅黑", 11),
  73. wrap=tk.WORD,
  74. padx=10,
  75. pady=10)
  76. plan_text.pack(fill=tk.BOTH, expand=True)
  77. # 创建底部控制区域
  78. bottom_frame = tk.Frame(main_frame, bg='#f0f0f0', pady=10)
  79. bottom_frame.pack(fill=tk.X)
  80. # 创建复选框
  81. keep_original = tk.BooleanVar(value=False)
  82. check_button = tk.Checkbutton(bottom_frame,
  83. text="保留原有内容",
  84. variable=keep_original,
  85. font=("微软雅黑", 11),
  86. bg='#f0f0f0')
  87. check_button.pack(side=tk.LEFT, padx=10)
  88. # 创建提交按钮
  89. result = {"summary": "", "plan": "", "is_submitted": False, "keep_original": False}
  90. def submit():
  91. result["summary"] = summary_text.get("1.0", tk.END).strip()
  92. result["plan"] = plan_text.get("1.0", tk.END).strip()
  93. result["keep_original"] = keep_original.get()
  94. result["is_submitted"] = True
  95. root.quit()
  96. root.destroy()
  97. submit_button = tk.Button(bottom_frame,
  98. text="提交",
  99. command=submit,
  100. font=("微软雅黑", 12, "bold"),
  101. bg='#4CAF50',
  102. fg='white',
  103. width=15,
  104. height=2,
  105. relief=tk.FLAT)
  106. submit_button.pack(side=tk.RIGHT, padx=10)
  107. # 设置窗口居中
  108. root.update_idletasks()
  109. width = root.winfo_width()
  110. height = root.winfo_height()
  111. x = (root.winfo_screenwidth() // 2) - (width // 2)
  112. y = (root.winfo_screenheight() // 2) - (height // 2)
  113. root.geometry(f'{width}x{height}+{x}+{y}')
  114. # 设置窗口图标(如果有的话)
  115. try:
  116. root.iconbitmap('icon.ico') # 如果有图标文件的话
  117. except:
  118. pass
  119. root.mainloop()
  120. return result["summary"], result["plan"], result["is_submitted"], result["keep_original"]
  121. # 执行自动化浏览器操作的协程函数
  122. def close_chrome():
  123. """关闭所有Chrome进程"""
  124. for proc in psutil.process_iter(['name']):
  125. try:
  126. if proc.info['name'] == 'chrome.exe':
  127. proc.kill()
  128. except (psutil.NoSuchProcess, psutil.AccessDenied):
  129. pass
  130. # 等待进程完全关闭
  131. time.sleep(2)
  132. async def run(playwright):
  133. # 确保Chrome已关闭
  134. close_chrome()
  135. # 使用用户实际的Chrome配置,配置查询chrome://version
  136. user_data_dir = r'C:/Users/zhens/AppData/Local/Google/Chrome/User Data'
  137. def text_to_div_html(text):
  138. """将多行文本转换为<div>...</div>格式的HTML"""
  139. lines = [line.strip() for line in text.strip().splitlines() if line.strip()]
  140. return ''.join(f'<div>{line}</div>' for line in lines)
  141. # 获取用户输入的日报内容
  142. summary_text, plan_text, is_submitted, keep_original = get_user_input()
  143. # 如果用户取消输入,直接退出程序
  144. if not is_submitted:
  145. print("用户取消了日报填写,程序退出")
  146. return
  147. summary_html = text_to_div_html(summary_text)
  148. plan_html = text_to_div_html(plan_text)
  149. try:
  150. # 使用已有的Chrome配置文件启动浏览器
  151. context = await playwright.chromium.launch_persistent_context(
  152. user_data_dir,
  153. channel="chrome",
  154. headless=False,
  155. args=[
  156. '--start-maximized',
  157. '--disable-blink-features=AutomationControlled',
  158. '--profile-directory=Default',
  159. '--no-first-run',
  160. '--no-default-browser-check'
  161. ],
  162. ignore_default_args=['--enable-automation']
  163. )
  164. try:
  165. # 创建新页面
  166. page = await context.new_page()
  167. # 设置实际的User-Agent
  168. await page.set_extra_http_headers({
  169. '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'
  170. })
  171. # 访问页面并等待网络空闲
  172. await page.goto('https://doc.weixin.qq.com/forms/j/AFIADwd9AA4AUcA6AbCADgJXbq19fUR0j_base?page=6',
  173. wait_until='networkidle')
  174. # 等待元素出现并点击
  175. await page.wait_for_selector('div.TitleBarBtn_title__dKAyV:text("填写")', timeout=60000)
  176. await page.click('div.TitleBarBtn_title__dKAyV:text("填写")', delay=100)
  177. # 等待操作完成,确保页面完全加载
  178. await asyncio.sleep(5)
  179. # 查找所有可编辑的输入框
  180. editors = await page.query_selector_all('.maileditor-editorview[contenteditable="true"]')
  181. print("找到输入框数量:", len(editors))
  182. # 打印每个输入框的父级HTML,帮助确认 data-qid,每次打开data-qid都会不同所以不准确
  183. # for i, editor in enumerate(editors):
  184. # parent = await editor.evaluate_handle('el => el.closest("div.question")')
  185. # parent_html = await parent.evaluate('el => el.outerHTML')
  186. # print(f"输入框{i+1}父级HTML:\n{parent_html}\n")
  187. # if len(editors) >= 2:
  188. # await editors[0].evaluate(f'el => el.innerHTML = `{summary_html}`')
  189. # await editors[1].evaluate(f'el => el.innerHTML = `{plan_html}`')
  190. # 今日工作总结输入
  191. # 1. 先点击输入框,确保获得焦点
  192. await page.locator('div.question:has-text("今日工作总结") .maileditor-editorview[contenteditable="true"]').click()
  193. # 2. 如果不保留原有内容,则清空输入框
  194. if not keep_original:
  195. await page.keyboard.press('Control+A')
  196. await page.keyboard.press('Delete')
  197. # 3. 使用键盘输入方式模拟真实输入
  198. await page.keyboard.type(summary_text)
  199. # 4. 等待输入完成,确保内容被正确输入
  200. await asyncio.sleep(2)
  201. # 明日工作计划输入
  202. # 1. 先点击输入框,确保获得焦点
  203. await page.locator('div.question:has-text("明日工作计划") .maileditor-editorview[contenteditable="true"]').click()
  204. # 2. 如果不保留原有内容,则清空输入框
  205. if not keep_original:
  206. await page.keyboard.press('Control+A')
  207. await page.keyboard.press('Delete')
  208. # 3. 使用键盘输入方式模拟真实输入
  209. await page.keyboard.type(plan_text)
  210. # 4. 等待输入完成
  211. await asyncio.sleep(2)
  212. # 等待内容保存
  213. # 给系统足够的时间来保存输入的内容
  214. await asyncio.sleep(5)
  215. # 点击提交按钮
  216. # 1. 等待提交按钮出现
  217. await page.wait_for_selector('button.FillFooter_confirm__0ClPl', timeout=60000)
  218. # # 2. 点击提交按钮,添加延迟模拟真实点击
  219. await page.click('button.FillFooter_confirm__0ClPl', delay=100)
  220. # 等待提交完成
  221. # 给系统足够的时间来处理提交操作
  222. await asyncio.sleep(5)
  223. finally:
  224. # 确保浏览器正常关闭
  225. await context.close()
  226. except Exception as e:
  227. print(f"发生错误: {str(e)}")
  228. raise
  229. # 主函数,用于启动 playwright 并调用 run 函数
  230. async def main():
  231. async with async_playwright() as playwright:
  232. await run(playwright)
  233. # 判断当前环境是否已经有事件循环在运行
  234. if __name__ == "__main__":
  235. try:
  236. # 尝试获取正在运行的事件循环(某些 IDE/Jupyter 会预先启动)
  237. loop = asyncio.get_running_loop()
  238. except RuntimeError:
  239. loop = None
  240. # 如果事件循环存在且正在运行(比如在 Jupyter Notebook 中)
  241. if loop and loop.is_running():
  242. print("检测到事件循环正在运行,使用 create_task 启动协程")
  243. asyncio.create_task(main()) # 使用 create_task 异步运行
  244. else:
  245. # 否则,正常使用 asyncio.run 启动主协程
  246. asyncio.run(main())