autowrite_day.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602
  1. """
  2. 日报自动化填写脚本
  3. 依赖安装:
  4. 1. 安装Python依赖包:
  5. pip install playwright
  6. pip install psutil
  7. pip install asyncio
  8. pip install aiohttp # 添加异步HTTP客户端,提高API调用效率
  9. 2. 安装Playwright浏览器:
  10. playwright install chromium
  11. 注意事项:
  12. 1. 确保Chrome浏览器已安装
  13. 2. 脚本使用Chrome用户配置文件,路径为:C:/Users/zhens/AppData/Local/Google/Chrome/User Data
  14. 3. 运行前请确保没有其他Chrome进程在运行
  15. 4. 脚本会自动关闭所有Chrome进程,请确保没有重要工作未保存
  16. 5. 如果修改了Chrome用户配置路径,需要相应修改代码中的user_data_dir变量
  17. 6. 脚本运行时会打开Chrome浏览器窗口,请勿手动操作浏览器
  18. 7. 如果遇到网络问题,可以适当增加等待时间(asyncio.sleep的值)
  19. 8. 如果提交失败,可以检查网络连接或适当增加等待时间
  20. """
  21. import asyncio
  22. import os
  23. import psutil
  24. import time
  25. import tkinter as tk
  26. from tkinter import scrolledtext, messagebox, ttk
  27. from playwright.async_api import async_playwright
  28. import aiohttp # 使用异步HTTP客户端,提高API调用效率
  29. import logging
  30. import threading
  31. import re
  32. from typing import Optional, Tuple
  33. # 配置日志
  34. logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
  35. # 全局变量,用于控制生成状态
  36. is_generating = False
  37. MAX_RETRIES = 3 # API调用最大重试次数,提高可靠性
  38. REQUEST_TIMEOUT = 30 # API请求超时时间(秒),避免长时间等待
  39. def count_chinese_chars(text):
  40. """统计中文字符数量"""
  41. return len(re.findall(r'[\u4e00-\u9fff]', text))
  42. def count_words(text):
  43. """统计总字数(中文字符+其他字符)"""
  44. return count_chinese_chars(text) + len(re.sub(r'[\u4e00-\u9fff]', '', text))
  45. async def generate_daily_report(prompt, ollama_url, model_name):
  46. """使用Ollama生成日报内容"""
  47. try:
  48. logging.info(f"开始生成日报,使用模型: {model_name}")
  49. # 使用异步HTTP客户端,提高API调用效率
  50. async with aiohttp.ClientSession() as session:
  51. for attempt in range(MAX_RETRIES):
  52. try:
  53. # 并行发送两个请求,提高效率
  54. async with session.post(
  55. f"{ollama_url}/api/generate",
  56. json={
  57. "model": model_name,
  58. "prompt": f"你是一个专业的日报生成助手,请根据以下工作内容生成专业的今日工作总结:{prompt}",
  59. "stream": False
  60. },
  61. timeout=aiohttp.ClientTimeout(total=REQUEST_TIMEOUT)
  62. ) as summary_response, \
  63. session.post(
  64. f"{ollama_url}/api/generate",
  65. json={
  66. "model": model_name,
  67. "prompt": f"你是一个专业的日报生成助手,请根据以下工作内容生成合理的明日工作计划:{prompt}",
  68. "stream": False
  69. },
  70. timeout=aiohttp.ClientTimeout(total=REQUEST_TIMEOUT)
  71. ) as plan_response:
  72. summary_data = await summary_response.json()
  73. plan_data = await plan_response.json()
  74. if summary_response.status == 200 and plan_response.status == 200:
  75. return summary_data["response"], plan_data["response"]
  76. else:
  77. error_msg = f"API调用失败: 总结状态码={summary_response.status}, 计划状态码={plan_response.status}"
  78. if summary_response.status != 200:
  79. error_msg += f"\n总结生成失败原因: {await summary_response.text()}"
  80. if plan_response.status != 200:
  81. error_msg += f"\n计划生成失败原因: {await plan_response.text()}"
  82. logging.error(error_msg)
  83. if attempt < MAX_RETRIES - 1:
  84. await asyncio.sleep(1) # 重试前等待
  85. continue
  86. return None, error_msg
  87. except Exception as e:
  88. error_msg = f"生成日报时发生错误: {str(e)}"
  89. logging.error(error_msg)
  90. if attempt < MAX_RETRIES - 1:
  91. await asyncio.sleep(1)
  92. continue
  93. return None, error_msg
  94. except Exception as e:
  95. error_msg = f"生成日报时发生错误: {str(e)}"
  96. logging.error(error_msg)
  97. return None, error_msg
  98. def get_user_input():
  99. """通过GUI窗口获取用户输入的日报内容"""
  100. root = tk.Tk()
  101. root.title("日报填写助手")
  102. root.geometry("1000x800") # 设置更大的初始窗口大小
  103. # 设置窗口样式
  104. root.configure(bg='#f0f0f0')
  105. # 修改字体设置方式
  106. default_font = ('微软雅黑', 10)
  107. root.option_add('*Font', default_font)
  108. # 设置窗口图标(如果有的话)
  109. try:
  110. root.iconbitmap('icon.ico')
  111. except:
  112. pass
  113. # 创建主框架
  114. main_frame = tk.Frame(root, bg='#f0f0f0')
  115. main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
  116. # 创建可滚动内容区(Canvas + Frame)
  117. content_canvas = tk.Canvas(main_frame, bg='#f0f0f0', highlightthickness=0)
  118. scrollbar = tk.Scrollbar(main_frame, orient="vertical", command=content_canvas.yview)
  119. scrollable_frame = tk.Frame(content_canvas, bg='#f0f0f0')
  120. scrollable_frame.bind(
  121. "<Configure>",
  122. lambda e: content_canvas.configure(
  123. scrollregion=content_canvas.bbox("all")
  124. )
  125. )
  126. content_canvas.create_window((0, 0), window=scrollable_frame, anchor="nw")
  127. content_canvas.configure(yscrollcommand=scrollbar.set)
  128. content_canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
  129. scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
  130. # 下面所有内容区控件都放到scrollable_frame里
  131. # 创建标题
  132. title_label = tk.Label(scrollable_frame,
  133. text="日报填写",
  134. font=('微软雅黑', 16, 'bold'),
  135. bg='#f0f0f0',
  136. pady=5)
  137. title_label.pack()
  138. # 创建配置区域框架
  139. config_frame = tk.Frame(scrollable_frame, bg='#f0f0f0')
  140. config_frame.pack(fill=tk.X, pady=5)
  141. # 创建自动生成功能开关
  142. auto_generate_var = tk.BooleanVar(value=False)
  143. auto_generate_check = tk.Checkbutton(config_frame,
  144. text="启用自动生成日报",
  145. variable=auto_generate_var,
  146. font=('微软雅黑', 11),
  147. bg='#f0f0f0',
  148. command=lambda: toggle_auto_generate(auto_generate_var.get()))
  149. auto_generate_check.pack(pady=5)
  150. # 创建Ollama配置区域
  151. ollama_frame = tk.LabelFrame(config_frame,
  152. text="Ollama配置",
  153. font=('微软雅黑', 12),
  154. bg='#f0f0f0',
  155. padx=10,
  156. pady=5)
  157. ollama_frame.pack(fill=tk.X, pady=2)
  158. # Ollama地址输入
  159. url_frame = tk.Frame(ollama_frame, bg='#f0f0f0')
  160. url_frame.pack(fill=tk.X, pady=2)
  161. tk.Label(url_frame,
  162. text="Ollama地址:",
  163. font=('微软雅黑', 11),
  164. bg='#f0f0f0').pack(side=tk.LEFT)
  165. ollama_url = tk.Entry(url_frame,
  166. font=('微软雅黑', 11))
  167. ollama_url.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5)
  168. ollama_url.insert(0, "http://localhost:11434")
  169. # 模型名称输入
  170. model_frame = tk.Frame(ollama_frame, bg='#f0f0f0')
  171. model_frame.pack(fill=tk.X, pady=2)
  172. tk.Label(model_frame,
  173. text="模型名称:",
  174. font=('微软雅黑', 11),
  175. bg='#f0f0f0').pack(side=tk.LEFT)
  176. model_name = tk.Entry(model_frame,
  177. font=('微软雅黑', 11))
  178. model_name.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5)
  179. model_name.insert(0, "llama2")
  180. # 创建提示词输入区域
  181. prompt_frame = tk.LabelFrame(config_frame,
  182. text="工作内容提示词",
  183. font=('微软雅黑', 12),
  184. bg='#f0f0f0',
  185. padx=10,
  186. pady=5)
  187. prompt_frame.pack(fill=tk.X, pady=2)
  188. prompt_text = scrolledtext.ScrolledText(prompt_frame,
  189. height=3,
  190. font=('微软雅黑', 11),
  191. wrap=tk.WORD,
  192. padx=10,
  193. pady=10)
  194. prompt_text.pack(fill=tk.X, expand=True)
  195. # 创建进度条
  196. progress_var = tk.DoubleVar()
  197. progress_bar = ttk.Progressbar(prompt_frame,
  198. variable=progress_var,
  199. maximum=100,
  200. mode='indeterminate')
  201. # 创建自动生成按钮
  202. def auto_generate():
  203. global is_generating
  204. if is_generating:
  205. return
  206. prompt = prompt_text.get("1.0", tk.END).strip()
  207. if not prompt:
  208. messagebox.showwarning("提示", "请输入工作内容提示词")
  209. return
  210. try:
  211. is_generating = True
  212. auto_generate_button.config(state=tk.DISABLED)
  213. progress_bar.pack(fill=tk.X, pady=5)
  214. progress_bar.start()
  215. def generate_thread():
  216. try:
  217. summary, plan = generate_daily_report(
  218. prompt,
  219. ollama_url.get().strip(),
  220. model_name.get().strip()
  221. )
  222. if summary and plan:
  223. root.after(0, lambda: update_texts(summary, plan))
  224. else:
  225. root.after(0, lambda: messagebox.showerror("错误", f"生成日报失败:{plan}"))
  226. except Exception as e:
  227. root.after(0, lambda: messagebox.showerror("错误", f"生成日报时发生错误:{str(e)}"))
  228. finally:
  229. root.after(0, finish_generation)
  230. threading.Thread(target=generate_thread, daemon=True).start()
  231. except Exception as e:
  232. messagebox.showerror("错误", f"生成日报时发生错误:{str(e)}")
  233. finish_generation()
  234. def update_texts(summary, plan):
  235. summary_text.delete("1.0", tk.END)
  236. plan_text.delete("1.0", tk.END)
  237. summary_text.insert("1.0", summary)
  238. plan_text.insert("1.0", plan)
  239. update_counts()
  240. def finish_generation():
  241. global is_generating
  242. is_generating = False
  243. auto_generate_button.config(state=tk.NORMAL)
  244. progress_bar.stop()
  245. progress_bar.pack_forget()
  246. auto_generate_button = tk.Button(prompt_frame,
  247. text="自动生成日报",
  248. command=auto_generate,
  249. font=('微软雅黑', 11),
  250. bg='#2196F3',
  251. fg='white',
  252. relief=tk.FLAT,
  253. cursor='hand2')
  254. auto_generate_button.pack(pady=5)
  255. def toggle_auto_generate(show):
  256. """切换自动生成相关控件的显示状态"""
  257. if show:
  258. ollama_frame.pack(fill=tk.X, pady=2)
  259. prompt_frame.pack(fill=tk.X, pady=2)
  260. else:
  261. ollama_frame.pack_forget()
  262. prompt_frame.pack_forget()
  263. # 初始状态:隐藏自动生成相关控件
  264. toggle_auto_generate(False)
  265. # 创建内容区域框架
  266. content_frame = tk.Frame(scrollable_frame, bg='#f0f0f0')
  267. content_frame.pack(fill=tk.BOTH, expand=True, pady=5)
  268. # 创建今日工作总结输入区域
  269. summary_frame = tk.LabelFrame(content_frame,
  270. text="今日工作总结",
  271. font=('微软雅黑', 12),
  272. bg='#f0f0f0',
  273. padx=10,
  274. pady=5)
  275. summary_frame.pack(fill=tk.BOTH, expand=True, pady=2)
  276. summary_text = scrolledtext.ScrolledText(summary_frame,
  277. height=6, # 减小默认高度
  278. font=('微软雅黑', 11),
  279. wrap=tk.WORD,
  280. padx=10,
  281. pady=10)
  282. summary_text.pack(fill=tk.BOTH, expand=True)
  283. # 添加字数统计
  284. summary_count = tk.Label(summary_frame,
  285. text="字数:0",
  286. font=('微软雅黑', 10),
  287. bg='#f0f0f0',
  288. anchor='e')
  289. summary_count.pack(fill=tk.X, padx=10)
  290. # 创建明日工作计划输入区域
  291. plan_frame = tk.LabelFrame(content_frame,
  292. text="明日工作计划",
  293. font=('微软雅黑', 12),
  294. bg='#f0f0f0',
  295. padx=10,
  296. pady=5)
  297. plan_frame.pack(fill=tk.BOTH, expand=True, pady=2)
  298. plan_text = scrolledtext.ScrolledText(plan_frame,
  299. height=6, # 减小默认高度
  300. font=('微软雅黑', 11),
  301. wrap=tk.WORD,
  302. padx=10,
  303. pady=10)
  304. plan_text.pack(fill=tk.BOTH, expand=True)
  305. # 添加字数统计
  306. plan_count = tk.Label(plan_frame,
  307. text="字数:0",
  308. font=('微软雅黑', 10),
  309. bg='#f0f0f0',
  310. anchor='e')
  311. plan_count.pack(fill=tk.X, padx=10)
  312. # 创建底部控制区域,固定在主窗口底部
  313. bottom_frame = tk.Frame(root, bg='#f0f0f0')
  314. bottom_frame.pack(side=tk.BOTTOM, fill=tk.X, pady=5)
  315. # 创建复选框
  316. keep_original = tk.BooleanVar(value=False)
  317. check_button = tk.Checkbutton(bottom_frame,
  318. text="保留原有内容",
  319. variable=keep_original,
  320. font=('微软雅黑', 11),
  321. bg='#f0f0f0')
  322. check_button.pack(side=tk.LEFT, padx=10)
  323. # 创建提交按钮
  324. result = {"summary": "", "plan": "", "is_submitted": False, "keep_original": False}
  325. def submit():
  326. if not summary_text.get("1.0", tk.END).strip() or not plan_text.get("1.0", tk.END).strip():
  327. messagebox.showwarning("提示", "请填写完整的工作总结和计划")
  328. return
  329. result["summary"] = summary_text.get("1.0", tk.END).strip()
  330. result["plan"] = plan_text.get("1.0", tk.END).strip()
  331. result["keep_original"] = keep_original.get()
  332. result["is_submitted"] = True
  333. root.quit()
  334. root.destroy()
  335. submit_button = tk.Button(bottom_frame,
  336. text="提交",
  337. command=submit,
  338. font=('微软雅黑', 12, 'bold'),
  339. bg='#4CAF50',
  340. fg='white',
  341. width=15,
  342. height=2,
  343. relief=tk.FLAT,
  344. cursor='hand2')
  345. submit_button.pack(side=tk.RIGHT, padx=10)
  346. # 添加字数统计更新函数
  347. def update_counts(event=None):
  348. summary_text_content = summary_text.get("1.0", tk.END).strip()
  349. plan_text_content = plan_text.get("1.0", tk.END).strip()
  350. summary_count.config(text=f"字数:{count_words(summary_text_content)}(中文字符:{count_chinese_chars(summary_text_content)})")
  351. plan_count.config(text=f"字数:{count_words(plan_text_content)}(中文字符:{count_chinese_chars(plan_text_content)})")
  352. # 绑定字数统计更新
  353. summary_text.bind('<KeyRelease>', update_counts)
  354. plan_text.bind('<KeyRelease>', update_counts)
  355. # 初始更新字数统计
  356. update_counts()
  357. # 设置窗口居中
  358. root.update_idletasks()
  359. width = root.winfo_width()
  360. height = root.winfo_height()
  361. x = (root.winfo_screenwidth() // 2) - (width // 2)
  362. y = (root.winfo_screenheight() // 2) - (height // 2)
  363. root.geometry(f'{width}x{height}+{x}+{y}')
  364. # 设置窗口最小尺寸
  365. root.minsize(900, 700)
  366. # 设置窗口关闭确认
  367. def on_closing():
  368. if messagebox.askokcancel("确认", "确定要关闭窗口吗?"):
  369. root.destroy()
  370. root.protocol("WM_DELETE_WINDOW", on_closing)
  371. # 设置默认焦点
  372. prompt_text.focus_set()
  373. root.mainloop()
  374. return result["summary"], result["plan"], result["is_submitted"], result["keep_original"]
  375. # 执行自动化浏览器操作的协程函数
  376. def close_chrome():
  377. """关闭所有Chrome进程"""
  378. for proc in psutil.process_iter(['name']):
  379. try:
  380. if proc.info['name'] == 'chrome.exe':
  381. proc.kill()
  382. except (psutil.NoSuchProcess, psutil.AccessDenied):
  383. pass
  384. # 等待进程完全关闭,减少等待时间
  385. time.sleep(1)
  386. async def run(playwright):
  387. try:
  388. # 确保Chrome已关闭
  389. close_chrome()
  390. # 使用用户实际的Chrome配置
  391. # 请按照以下步骤找到正确的配置文件路径:
  392. # 1. 打开 Chrome 浏览器
  393. # 2. 在地址栏输入 chrome://version
  394. # 3. 查看"个人资料路径"这一行
  395. # 4. 将路径复制到这里
  396. user_data_dir = r'C:\Users\zhens\AppData\Local\Google\Chrome\User Data\Default' # 请替换为你的实际配置文件路径
  397. def text_to_div_html(text):
  398. """将多行文本转换为<div>...</div>格式的HTML"""
  399. lines = [line.strip() for line in text.strip().splitlines() if line.strip()]
  400. return ''.join(f'<div>{line}</div>' for line in lines)
  401. # 获取用户输入的日报内容
  402. summary_text, plan_text, is_submitted, keep_original = get_user_input()
  403. # 如果用户取消输入,直接退出程序
  404. if not is_submitted:
  405. print("用户取消了日报填写,程序退出")
  406. return
  407. summary_html = text_to_div_html(summary_text)
  408. plan_html = text_to_div_html(plan_text)
  409. # 使用已有的Chrome配置文件启动浏览器
  410. context = await playwright.chromium.launch_persistent_context(
  411. user_data_dir,
  412. channel="chrome",
  413. headless=False,
  414. args=[
  415. '--start-maximized',
  416. '--disable-blink-features=AutomationControlled',
  417. '--profile-directory=Default',
  418. '--no-first-run',
  419. '--no-default-browser-check',
  420. '--disable-extensions', # 禁用所有扩展
  421. '--disable-gpu' # 禁用GPU加速
  422. ],
  423. ignore_default_args=['--enable-automation']
  424. )
  425. try:
  426. # 创建新页面
  427. page = await context.new_page()
  428. # 设置实际的User-Agent
  429. await page.set_extra_http_headers({
  430. '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'
  431. })
  432. # 访问页面并等待网络空闲
  433. await page.goto('https://doc.weixin.qq.com/forms/j/AFIADwd9AA4AUcA6AbCADgJXbq19fUR0j_base?page=6',
  434. wait_until='networkidle')
  435. # 等待元素出现并点击
  436. await page.wait_for_selector('div.TitleBarBtn_title__dKAyV:text("填写")', timeout=60000)
  437. await page.click('div.TitleBarBtn_title__dKAyV:text("填写")', delay=100)
  438. # 等待操作完成,确保页面完全加载
  439. await asyncio.sleep(2) # 减少等待时间,提高效率
  440. # 查找所有可编辑的输入框
  441. editors = await page.query_selector_all('.maileditor-editorview[contenteditable="true"]')
  442. print("找到输入框数量:", len(editors))
  443. # 今日工作总结输入
  444. # 1. 先点击输入框,确保获得焦点
  445. #todo 教程:page.locator(...)
  446. # 表示在当前页面上查找符合选择器的元素。
  447. # 'div.question:has-text("今日工作总结") .maileditor-editorview[contenteditable="true"]'
  448. # 这是一个复杂的 CSS 选择器,意思如下:
  449. # div.question:匹配类名为 question 的 div 元素。
  450. # : has-text("今日工作总结"):这个是 Playwright 扩展的选择器语法,表示该 div.question 元素 包含文本"今日工作总结"。
  451. # .maileditor-editorview[contenteditable = "true"]:在该 div 之下,继续查找一个类名为 maileditor-editorview 且有属性 contenteditable = "true" 的元素。
  452. # .click()
  453. # 表示对找到的这个元素执行点击操作。
  454. await page.locator('div.question:has-text("今日工作总结") .maileditor-editorview[contenteditable="true"]').click()
  455. # 2. 如果不保留原有内容,则清空输入框
  456. if not keep_original:
  457. await page.keyboard.press('Control+A')
  458. await page.keyboard.press('Delete')
  459. # 3. 使用键盘输入方式模拟真实输入
  460. await page.keyboard.type(summary_text)
  461. # 4. 等待输入完成,确保内容被正确输入
  462. await asyncio.sleep(1) # 减少等待时间,提高效率
  463. # 明日工作计划输入
  464. # 1. 先点击输入框,确保获得焦点
  465. await page.locator('div.question:has-text("明日工作计划") .maileditor-editorview[contenteditable="true"]').click()
  466. # 2. 如果不保留原有内容,则清空输入框
  467. if not keep_original:
  468. await page.keyboard.press('Control+A')
  469. await page.keyboard.press('Delete')
  470. # 3. 使用键盘输入方式模拟真实输入
  471. await page.keyboard.type(plan_text)
  472. # 4. 等待输入完成
  473. await asyncio.sleep(1) # 减少等待时间,提高效率
  474. # 等待内容保存
  475. # 给系统足够的时间来保存输入的内容
  476. await asyncio.sleep(2) # 减少等待时间,提高效率
  477. # 点击提交按钮
  478. # 1. 等待提交按钮出现
  479. await page.wait_for_selector('button.FillFooter_confirm__0ClPl', timeout=60000)
  480. # 2. 点击提交按钮,添加延迟模拟真实点击
  481. await page.click('button.FillFooter_confirm__0ClPl', delay=100)
  482. # 等待提交完成
  483. # 给系统足够的时间来处理提交操作
  484. await asyncio.sleep(2) # 减少等待时间,提高效率
  485. # 显示提交成功提示
  486. print("日报提交成功!")
  487. except Exception as e:
  488. print(f"提交过程中发生错误: {str(e)}")
  489. raise
  490. finally:
  491. # 确保浏览器正常关闭
  492. await context.close()
  493. except Exception as e:
  494. print(f"程序运行过程中发生错误: {str(e)}")
  495. raise
  496. # 主函数,用于启动 playwright 并调用 run 函数
  497. async def main():
  498. async with async_playwright() as playwright:
  499. await run(playwright)
  500. # 判断当前环境是否已经有事件循环在运行
  501. if __name__ == "__main__":
  502. try:
  503. # 尝试获取正在运行的事件循环(某些 IDE/Jupyter 会预先启动)
  504. loop = asyncio.get_running_loop()
  505. except RuntimeError:
  506. loop = None
  507. # 如果事件循环存在且正在运行(比如在 Jupyter Notebook 中)
  508. if loop and loop.is_running():
  509. print("检测到事件循环正在运行,使用 create_task 启动协程")
  510. asyncio.create_task(main()) # 使用 create_task 异步运行
  511. else:
  512. # 否则,正常使用 asyncio.run 启动主协程
  513. asyncio.run(main())