fix: prevent browser process leaks and improve robustness

- fix(browser): wrap context lifecycle in try/finally to ensure browser
  closes on exceptions and KeyboardInterrupt (login, sniffer, generator)
- fix(browser): replace time.sleep with Playwright native waits
  (wait_for, wait_for_timeout) for more reliable element interaction
- fix(browser): use parameterized page.evaluate instead of f-string
  JS injection in generator polling
- fix(api): add retry logic in wait_for_completion to survive transient
  network errors
- fix(config): add prepare_profile_dir to copy profile to temp dir,
  preventing Chromium SingletonLock conflicts when tools run concurrently
- fix(sniffer): stream API logs to tempfile instead of unbounded memory
  list to avoid OOM
- fix(api): specify encoding='utf-8' when loading cookies from file

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Claude
2026-05-24 23:08:46 +08:00
parent b2f549af01
commit 734d53dafb
6 changed files with 255 additions and 180 deletions
+94 -95
View File
@@ -47,105 +47,104 @@ def main():
headless = not args.no_headless
# 使用持久化上下文,cookie 和登录状态会保存到 PROFILE_DIR
context = launch_persistent_context(PROFILE_DIR, headless=headless)
page = context.new_page()
context = None
try:
context = launch_persistent_context(PROFILE_DIR, headless=headless)
page = context.new_page()
print("正在打开腾讯混元3D...")
page.goto("https://3d.hunyuan.tencent.com/")
time.sleep(2)
print("正在打开腾讯混元3D...")
page.goto("https://3d.hunyuan.tencent.com/", timeout=60000)
page.wait_for_timeout(2000)
# 检查是否已经是登录状态(没有登录按钮说明已登录)
login_btn = page.locator("button").filter(has_text="登录").first
if login_btn.count() == 0:
print("检测到已有登录状态,无需重新登录。")
print(f"当前页面: {page.url}")
_extract_and_save_cookies(context)
if headless:
context.close()
return
else:
# 未登录,走登录流程
login_btn.click()
time.sleep(1.5)
# 切换到邮箱登录
email_tab = page.locator("text=邮箱").first
if email_tab.count() > 0:
email_tab.click()
time.sleep(1.5)
else:
print("未找到邮箱登录选项")
context.close()
return
# 定位元素
email_input = page.locator('input[placeholder="请输入邮箱地址"]')
code_input = page.locator('input[type="number"][placeholder="请输入邮箱验证码"]')
send_code_btn = page.locator("a.hyc-email-login__send-code")
login_submit_btn = page.locator("button.hyc-email-login__btn")
checkbox = page.locator(".t-checkbox__former")
# 第一次交互:输入邮箱并发送验证码
print("\n============================================")
email = input("请输入邮箱地址,按回车发送验证码: ").strip()
if not email:
print("邮箱不能为空,退出")
context.close()
return
email_input.fill(email)
time.sleep(0.5)
# 勾选协议(如果未勾选)
if checkbox.count() > 0:
is_checked = checkbox.evaluate("el => el.checked")
if not is_checked:
checkbox.evaluate("el => el.click()")
time.sleep(0.3)
if send_code_btn.count() > 0:
send_code_btn.click()
print("已点击发送验证码,请查收邮件...")
else:
print("未找到发送验证码按钮")
context.close()
return
# 第二次交互:输入验证码并登录
print("\n============================================")
code = input("请输入邮箱验证码,按回车登录: ").strip()
if not code:
print("验证码不能为空,退出")
context.close()
return
code_input.fill(code)
time.sleep(0.5)
# 点击登录
if login_submit_btn.count() > 0:
login_submit_btn.click()
print("已点击登录按钮,等待跳转...")
else:
print("未找到登录提交按钮")
context.close()
return
# 等待登录成功跳转
try:
page.wait_for_url(lambda url: "login" not in url, timeout=30000)
print(f"\n登录成功!当前页面: {page.url}")
print(f"登录状态已保存到: {os.path.abspath(PROFILE_DIR)}")
# 检查是否已经是登录状态(没有登录按钮说明已登录)
login_btn = page.locator("button").filter(has_text="登录").first
if login_btn.count() == 0:
print("检测到已有登录状态,无需重新登录。")
print(f"当前页面: {page.url}")
_extract_and_save_cookies(context)
except Exception:
print("\n登录可能仍在处理中,或出现错误。请检查浏览器状态。")
# 保持浏览器打开(仅非无头模式)
if not headless:
print("\n按 Enter 键关闭浏览器并退出...")
input()
context.close()
if headless:
return
else:
# 未登录,走登录流程
login_btn.click()
page.locator("text=邮箱").wait_for(state="visible", timeout=10000)
# 切换到邮箱登录
email_tab = page.locator("text=邮箱").first
if email_tab.count() > 0:
email_tab.click()
email_input.wait_for(state="visible", timeout=10000)
else:
print("未找到邮箱登录选项")
return
# 定位元素
email_input = page.locator('input[placeholder="请输入邮箱地址"]')
code_input = page.locator('input[type="number"][placeholder="请输入邮箱验证码"]')
send_code_btn = page.locator("a.hyc-email-login__send-code")
login_submit_btn = page.locator("button.hyc-email-login__btn")
checkbox = page.locator(".t-checkbox__former")
# 第一次交互:输入邮箱并发送验证码
print("\n============================================")
email = input("请输入邮箱地址,按回车发送验证码: ").strip()
if not email:
print("邮箱不能为空,退出")
return
email_input.fill(email)
# 勾选协议(如果未勾选)
if checkbox.count() > 0:
is_checked = checkbox.evaluate("el => el.checked")
if not is_checked:
checkbox.evaluate("el => el.click()")
page.wait_for_timeout(300)
if send_code_btn.count() > 0:
send_code_btn.click()
print("已点击发送验证码,请查收邮件...")
else:
print("未找到发送验证码按钮")
return
# 第二次交互:输入验证码并登录
print("\n============================================")
code = input("请输入邮箱验证码,按回车登录: ").strip()
if not code:
print("验证码不能为空,退出")
return
code_input.fill(code)
# 点击登录
if login_submit_btn.count() > 0:
login_submit_btn.click()
print("已点击登录按钮,等待跳转...")
else:
print("未找到登录提交按钮")
return
# 等待登录成功跳转
try:
page.wait_for_url(lambda url: "login" not in url, timeout=30000)
print(f"\n登录成功!当前页面: {page.url}")
print(f"登录状态已保存到: {os.path.abspath(PROFILE_DIR)}")
_extract_and_save_cookies(context)
except Exception:
print("\n登录可能仍在处理中,或出现错误。请检查浏览器状态。")
# 保持浏览器打开(仅非无头模式)
if not headless:
print("\n按 Enter 键关闭浏览器并退出...")
input()
finally:
if context is not None:
try:
context.close()
except Exception:
pass
if __name__ == "__main__":