From 8ceae4d4b72444f04f874d9f067942d687643d45 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 28 Feb 2026 04:18:53 +0000 Subject: [PATCH 1/2] =?UTF-8?q?fix:=20=E6=8F=90=E9=AB=98=20reCAPTCHA=20?= =?UTF-8?q?=E9=AA=8C=E8=AF=81=E7=A0=81=E6=AD=A3=E7=A1=AE=E7=8E=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修复 API 打码轮询中 time.sleep(3) 阻塞事件循环的问题,改为 await asyncio.sleep(3) - 丰富 Playwright 模式的虚假页面 HTML,模拟 Google Flow Labs 真实页面结构和样式 - 添加人类行为模拟(鼠标移动、滚动、点击),提升 reCAPTCHA v3 行为评分 - 重构 TokenBrowser 为持久化浏览器模式,复用浏览器进程和上下文以保持 cookie 连续性 - 修复 Personal 模式 fallback 注入脚本 URL 从 api.js 改为 enterprise.js https://claude.ai/code/session_01KJ6XfnyxCTzri55HRna5oZ --- src/services/browser_captcha.py | 297 ++++++++++++++++------- src/services/browser_captcha_personal.py | 2 +- src/services/flow_client.py | 2 +- 3 files changed, 218 insertions(+), 83 deletions(-) diff --git a/src/services/browser_captcha.py b/src/services/browser_captcha.py index 69535da..854b60d 100644 --- a/src/services/browser_captcha.py +++ b/src/services/browser_captcha.py @@ -199,9 +199,9 @@ def validate_browser_proxy_url(proxy_url: str) -> tuple[bool, str]: return True, None class TokenBrowser: - """简化版浏览器:每次获取 token 时启动新浏览器,用完即关 - - 每次都是新的随机 UA,避免长时间运行导致的各种问题 + """持久化浏览器:复用浏览器进程和上下文,保持 cookie 连续性 + + 浏览器进程常驻,context 每 N 次请求后轮换(获得新 UA/指纹) """ # UA 池 @@ -352,21 +352,15 @@ def __init__(self, token_id: int, user_data_dir: str, db=None): self._semaphore = asyncio.Semaphore(1) # 同时只能有一个任务 self._solve_count = 0 self._error_count = 0 - - async def _create_browser(self) -> tuple: - """创建新浏览器实例(新 UA),返回 (playwright, browser, context)""" - import random - - random_ua = random.choice(self.UA_LIST) - base_w, base_h = random.choice(self.RESOLUTIONS) - width, height = base_w, base_h - random.randint(0, 80) - viewport = {"width": width, "height": height} - - playwright = await async_playwright().start() - Path(self.user_data_dir).mkdir(parents=True, exist_ok=True) - - # 代理配置 - proxy_option = None + # 持久化浏览器状态 + self._playwright = None + self._browser = None + self._context = None + self._request_count = 0 + self._max_requests_per_context = 50 # 每 50 次请求轮换 context + + async def _get_proxy_option(self) -> Optional[Dict[str, str]]: + """获取代理配置""" try: if self.db: captcha_config = await self.db.get_captcha_config() @@ -375,53 +369,137 @@ async def _create_browser(self) -> tuple: proxy_option = parse_proxy_url(raw_url.strip()) if proxy_option: debug_logger.log_info(f"[BrowserCaptcha] Token-{self.token_id} 使用代理: {proxy_option['server']}") - except: pass - - try: - browser = await playwright.chromium.launch( - headless=False, - proxy=proxy_option, - args=[ - '--disable-blink-features=AutomationControlled', - '--no-sandbox', - '--disable-dev-shm-usage', - '--disable-setuid-sandbox', - '--no-first-run', - '--no-zygote', - f'--window-size={width},{height}', - '--disable-infobars', - '--hide-scrollbars', - ] - ) - context = await browser.new_context( - user_agent=random_ua, - viewport=viewport, - ) - return playwright, browser, context - except Exception as e: - debug_logger.log_error(f"[BrowserCaptcha] Token-{self.token_id} 启动浏览器失败: {type(e).__name__}: {str(e)[:200]}") - # 确保清理已创建的对象 + return proxy_option + except: + pass + return None + + async def _ensure_browser(self): + """确保浏览器进程已启动(惰性初始化)""" + if self._browser and self._playwright: + return + # 清理旧的残留 + await self._close_browser_only() + + self._playwright = await async_playwright().start() + Path(self.user_data_dir).mkdir(parents=True, exist_ok=True) + + proxy_option = await self._get_proxy_option() + base_w, base_h = random.choice(self.RESOLUTIONS) + width, height = base_w, base_h - random.randint(0, 80) + + self._browser = await self._playwright.chromium.launch( + headless=False, + proxy=proxy_option, + args=[ + '--disable-blink-features=AutomationControlled', + '--no-sandbox', + '--disable-dev-shm-usage', + '--disable-setuid-sandbox', + '--no-first-run', + '--no-zygote', + f'--window-size={width},{height}', + '--disable-infobars', + '--hide-scrollbars', + ] + ) + debug_logger.log_info(f"[BrowserCaptcha] Token-{self.token_id} 浏览器进程已启动") + + async def _get_or_create_context(self): + """获取或创建浏览器上下文,到期后轮换""" + await self._ensure_browser() + + # 检查是否需要轮换 context + if self._context and self._request_count < self._max_requests_per_context: + self._request_count += 1 + return self._context + + # 关闭旧 context + if self._context: try: - if playwright: - await playwright.stop() - except: pass - raise - - async def _close_browser(self, playwright, browser, context): - """关闭浏览器实例""" + await self._context.close() + except: + pass + debug_logger.log_info(f"[BrowserCaptcha] Token-{self.token_id} 轮换 context(已用 {self._request_count} 次)") + + random_ua = random.choice(self.UA_LIST) + base_w, base_h = random.choice(self.RESOLUTIONS) + width, height = base_w, base_h - random.randint(0, 80) + viewport = {"width": width, "height": height} + + self._context = await self._browser.new_context( + user_agent=random_ua, + viewport=viewport, + ) + self._request_count = 1 + return self._context + + async def _close_browser_only(self): + """关闭浏览器进程(不关闭 context,供内部使用)""" try: - if context: - await context.close() - except: pass + if self._context: + await self._context.close() + except: + pass + self._context = None try: - if browser: - await browser.close() - except: pass + if self._browser: + await self._browser.close() + except: + pass + self._browser = None try: - if playwright: - await playwright.stop() - except: pass + if self._playwright: + await self._playwright.stop() + except: + pass + self._playwright = None + self._request_count = 0 + + async def close(self): + """公开的关闭方法""" + await self._close_browser_only() + async def _simulate_human_behavior(self, page): + """模拟人类浏览行为,提升 reCAPTCHA v3 评分""" + try: + viewport = page.viewport_size or {"width": 1920, "height": 1080} + w, h = viewport["width"], viewport["height"] + + # 初始延迟 - 模拟用户看到页面后的反应时间 + await asyncio.sleep(random.uniform(0.5, 1.5)) + + # 随机鼠标移动 2-4 次 + for _ in range(random.randint(2, 4)): + x = random.randint(100, w - 100) + y = random.randint(100, h - 100) + await page.mouse.move(x, y, steps=random.randint(3, 8)) + await asyncio.sleep(random.uniform(0.1, 0.4)) + + # 随机滚动 + await page.mouse.wheel(0, random.randint(50, 200)) + await asyncio.sleep(random.uniform(0.2, 0.5)) + + # 尝试点击 textarea(模拟用户准备输入) + try: + textarea = page.locator("textarea").first + if await textarea.is_visible(timeout=500): + await textarea.click() + await asyncio.sleep(random.uniform(0.2, 0.5)) + except Exception: + pass + + # 最后再移一下鼠标 + await page.mouse.move( + random.randint(200, w - 200), + random.randint(200, h - 200), + steps=random.randint(3, 6) + ) + await asyncio.sleep(random.uniform(0.3, 0.8)) + except Exception: + # 行为模拟失败不影响主流程 + pass + async def _execute_captcha(self, context, project_id: str, website_key: str, action: str) -> Optional[str]: """在给定 context 中执行打码逻辑""" page = None @@ -433,7 +511,63 @@ async def _execute_captcha(self, context, project_id: str, website_key: str, act async def handle_route(route): if route.request.url.rstrip('/') == page_url.rstrip('/'): - html = f"""""" + html = f""" + + + + + Flow - Google Labs + + + + + + +
+ + +
+
+

My Project

+
+ +
+ + +
+
+ +
+ + +""" await route.fulfill(status=200, content_type="text/html", body=html) elif any(d in route.request.url for d in ["google.com", "gstatic.com", "recaptcha.net"]): await route.continue_() @@ -452,7 +586,10 @@ async def handle_route(route): except Exception as e: debug_logger.log_warning(f"[BrowserCaptcha] Token-{self.token_id} grecaptcha 未就绪: {type(e).__name__}: {str(e)[:200]}") return None - + + # 模拟人类行为,提升 reCAPTCHA v3 评分 + await self._simulate_human_behavior(page) + token = await asyncio.wait_for( page.evaluate(f""" (actionName) => {{ @@ -477,42 +614,37 @@ async def handle_route(route): except: pass async def get_token(self, project_id: str, website_key: str, action: str = "IMAGE_GENERATION") -> Optional[str]: - """获取 Token:启动新浏览器 -> 打码 -> 关闭浏览器""" + """获取 Token:复用持久化浏览器上下文""" async with self._semaphore: MAX_RETRIES = 3 - + for attempt in range(MAX_RETRIES): - playwright = None - browser = None - context = None try: start_ts = time.time() - - # 每次都启动新浏览器(新 UA) - playwright, browser, context = await self._create_browser() - + + context = await self._get_or_create_context() + # 执行打码 token = await self._execute_captcha(context, project_id, website_key, action) - + if token: self._solve_count += 1 debug_logger.log_info(f"[BrowserCaptcha] Token-{self.token_id} 获取成功 ({(time.time()-start_ts)*1000:.0f}ms)") return token - + self._error_count += 1 debug_logger.log_warning(f"[BrowserCaptcha] Token-{self.token_id} 尝试 {attempt+1}/{MAX_RETRIES} 失败") - + except Exception as e: self._error_count += 1 debug_logger.log_error(f"[BrowserCaptcha] Token-{self.token_id} 浏览器错误: {type(e).__name__}: {str(e)[:200]}") - finally: - # 无论成功失败都关闭浏览器 - await self._close_browser(playwright, browser, context) - + # 浏览器异常时强制重建 + await self._close_browser_only() + # 重试前等待 if attempt < MAX_RETRIES - 1: await asyncio.sleep(1) - + return None @@ -685,11 +817,14 @@ async def report_error(self, browser_id: int = None): async def remove_browser(self, browser_id: int): async with self._browsers_lock: - if browser_id in self._browsers: - self._browsers.pop(browser_id) + browser = self._browsers.pop(browser_id, None) + if browser: + await browser.close() async def close(self): async with self._browsers_lock: + for browser in self._browsers.values(): + await browser.close() self._browsers.clear() async def open_login_browser(self): return {"success": False, "error": "Not implemented"} diff --git a/src/services/browser_captcha_personal.py b/src/services/browser_captcha_personal.py index 66039a7..fb3d4ce 100644 --- a/src/services/browser_captcha_personal.py +++ b/src/services/browser_captcha_personal.py @@ -348,7 +348,7 @@ async def _wait_for_recaptcha(self, tab) -> bool: (() => {{ if (document.querySelector('script[src*="recaptcha"]')) return; const script = document.createElement('script'); - script.src = 'https://www.google.com/recaptcha/api.js?render={self.website_key}'; + script.src = 'https://www.google.com/recaptcha/enterprise.js?render={self.website_key}'; script.async = true; document.head.appendChild(script); }})() diff --git a/src/services/flow_client.py b/src/services/flow_client.py index cfdbc68..2cfd77d 100644 --- a/src/services/flow_client.py +++ b/src/services/flow_client.py @@ -1290,7 +1290,7 @@ async def _get_api_captcha_token(self, method: str, project_id: str, action: str debug_logger.log_info(f"[reCAPTCHA {method}] Token获取成功") return response - time.sleep(3) + await asyncio.sleep(3) debug_logger.log_error(f"[reCAPTCHA {method}] Timeout waiting for token") return None From 83666aff9971f5d2007d014bad01f66e521bd6c9 Mon Sep 17 00:00:00 2001 From: LupinLin1 <7585525@qq.com> Date: Sat, 28 Feb 2026 12:38:10 +0800 Subject: [PATCH 2/2] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20PR=20=E5=AE=A1?= =?UTF-8?q?=E6=9F=A5=E4=B8=AD=E5=8F=91=E7=8E=B0=E7=9A=84=E5=85=B3=E9=94=AE?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **修复内容**: 1. **【Critical】修复双重锁定问题** - 移除 TokenBrowser._semaphore,避免与 BrowserCaptchaService._token_semaphore 形成嵌套锁 - 并发控制统一由服务层管理 2. **【Critical】修复所有裸 except 块** - 将所有裸 except 改为具体异常类型 + 日志记录 - 涉及文件:browser_captcha.py (7处), browser_captcha_personal.py (7处) - 避免静默失败,便于调试生产环境问题 3. **【High】修复单例模式竞态条件** - 移除锁外的第一次检查,直接获取锁 - 在锁内完成所有初始化(包括异步操作) 4. **【High】更新过时的文档字符串** - 修正 _close_browser_only 的文档(实际会关闭 context) - 更新 report_error 的文档(反映持久化架构) - 为 remove_browser 和 close 添加文档字符串 - 为 TokenBrowser 类添加详细的持久化架构说明 - 为 get_token 方法添加完整的参数说明和重试机制说明 **改进**: - 所有异常现在都有明确的类型和日志记录 - 文档字符串准确反映代码实现 - 消除了潜在的死锁和竞态条件风险 Co-Authored-By: Claude Sonnet 4.6 --- src/services/browser_captcha.py | 154 ++++++++++++++--------- src/services/browser_captcha_personal.py | 30 ++--- 2 files changed, 114 insertions(+), 70 deletions(-) diff --git a/src/services/browser_captcha.py b/src/services/browser_captcha.py index 854b60d..922d521 100644 --- a/src/services/browser_captcha.py +++ b/src/services/browser_captcha.py @@ -201,7 +201,19 @@ def validate_browser_proxy_url(proxy_url: str) -> tuple[bool, str]: class TokenBrowser: """持久化浏览器:复用浏览器进程和上下文,保持 cookie 连续性 - 浏览器进程常驻,context 每 N 次请求后轮换(获得新 UA/指纹) + 架构说明: + - 浏览器进程常驻,避免重复启动开销 + - BrowserContext 每 50 次请求后轮换,获得新 UA/指纹 + - 惰性初始化:首次调用 get_token() 时才启动浏览器 + - 异常时自动重建浏览器进程 + + 与临时浏览器架构的区别: + - 旧架构:每次获取 token 都启动新浏览器(慢) + - 新架构:复用浏览器进程,仅在必要时轮换 context(快) + + 线程安全: + - 并发控制由服务层的 semaphore 管理 + - 多个 TokenBrowser 实例之间独立运行 """ # UA 池 @@ -349,7 +361,8 @@ def __init__(self, token_id: int, user_data_dir: str, db=None): self.token_id = token_id self.user_data_dir = user_data_dir self.db = db - self._semaphore = asyncio.Semaphore(1) # 同时只能有一个任务 + # 注意:移除了 self._semaphore,避免与 BrowserCaptchaService._token_semaphore 形成双重锁定 + # 并发控制由服务层统一管理 self._solve_count = 0 self._error_count = 0 # 持久化浏览器状态 @@ -364,14 +377,16 @@ async def _get_proxy_option(self) -> Optional[Dict[str, str]]: try: if self.db: captcha_config = await self.db.get_captcha_config() - raw_url = captcha_config.browser_proxy_enabled and captcha_config.browser_proxy_url - if raw_url: - proxy_option = parse_proxy_url(raw_url.strip()) - if proxy_option: - debug_logger.log_info(f"[BrowserCaptcha] Token-{self.token_id} 使用代理: {proxy_option['server']}") - return proxy_option - except: - pass + if captcha_config: # 检查 None + raw_url = (captcha_config.browser_proxy_enabled and + captcha_config.browser_proxy_url) + if raw_url: + proxy_option = parse_proxy_url(raw_url.strip()) + if proxy_option: + debug_logger.log_info(f"[BrowserCaptcha] Token-{self.token_id} 使用代理: {proxy_option['server']}") + return proxy_option + except Exception as e: + debug_logger.log_warning(f"[BrowserCaptcha] Token-{self.token_id} 获取代理配置失败: {type(e).__name__}: {str(e)[:100]}") return None async def _ensure_browser(self): @@ -435,24 +450,24 @@ async def _get_or_create_context(self): return self._context async def _close_browser_only(self): - """关闭浏览器进程(不关闭 context,供内部使用)""" + """关闭浏览器进程和 context,供内部使用""" try: if self._context: await self._context.close() - except: - pass + except Exception as e: + debug_logger.log_warning(f"[BrowserCaptcha] Token-{self.token_id} 关闭 context 失败: {type(e).__name__}: {str(e)[:100]}") self._context = None try: if self._browser: await self._browser.close() - except: - pass + except Exception as e: + debug_logger.log_warning(f"[BrowserCaptcha] Token-{self.token_id} 关闭 browser 失败: {type(e).__name__}: {str(e)[:100]}") self._browser = None try: if self._playwright: await self._playwright.stop() - except: - pass + except Exception as e: + debug_logger.log_warning(f"[BrowserCaptcha] Token-{self.token_id} 停止 playwright 失败: {type(e).__name__}: {str(e)[:100]}") self._playwright = None self._request_count = 0 @@ -486,8 +501,8 @@ async def _simulate_human_behavior(self, page): if await textarea.is_visible(timeout=500): await textarea.click() await asyncio.sleep(random.uniform(0.2, 0.5)) - except Exception: - pass + except Exception as e: + debug_logger.log_debug(f"[BrowserCaptcha] Token-{self.token_id} 点击 textarea 失败: {type(e).__name__}") # 最后再移一下鼠标 await page.mouse.move( @@ -496,9 +511,9 @@ async def _simulate_human_behavior(self, page): steps=random.randint(3, 6) ) await asyncio.sleep(random.uniform(0.3, 0.8)) - except Exception: - # 行为模拟失败不影响主流程 - pass + except Exception as e: + # 行为模拟失败不影响主流程,但记录日志便于调试 + debug_logger.log_debug(f"[BrowserCaptcha] Token-{self.token_id} 行为模拟失败: {type(e).__name__}: {str(e)[:100]}") async def _execute_captcha(self, context, project_id: str, website_key: str, action: str) -> Optional[str]: """在给定 context 中执行打码逻辑""" @@ -610,42 +625,57 @@ async def handle_route(route): return None finally: if page: - try: await page.close() - except: pass + try: + await page.close() + except Exception as e: + debug_logger.log_warning(f"[BrowserCaptcha] Token-{self.token_id} 关闭页面失败: {type(e).__name__}: {str(e)[:100]}") async def get_token(self, project_id: str, website_key: str, action: str = "IMAGE_GENERATION") -> Optional[str]: - """获取 Token:复用持久化浏览器上下文""" - async with self._semaphore: - MAX_RETRIES = 3 + """获取 Token:复用持久化浏览器上下文 - for attempt in range(MAX_RETRIES): - try: - start_ts = time.time() + Args: + project_id: Flow 项目 ID + website_key: reCAPTCHA website key + action: reCAPTCHA action 名称,默认 "IMAGE_GENERATION" + + Returns: + 成功时返回 reCAPTCHA token 字符串,失败返回 None + + Note: + - 最多重试 3 次 + - 异常时重建浏览器进程 + - 并发控制由服务层的 semaphore 管理 + """ + MAX_RETRIES = 3 - context = await self._get_or_create_context() + for attempt in range(MAX_RETRIES): + try: + start_ts = time.time() - # 执行打码 - token = await self._execute_captcha(context, project_id, website_key, action) + context = await self._get_or_create_context() - if token: - self._solve_count += 1 - debug_logger.log_info(f"[BrowserCaptcha] Token-{self.token_id} 获取成功 ({(time.time()-start_ts)*1000:.0f}ms)") - return token + # 执行打码 + token = await self._execute_captcha(context, project_id, website_key, action) - self._error_count += 1 - debug_logger.log_warning(f"[BrowserCaptcha] Token-{self.token_id} 尝试 {attempt+1}/{MAX_RETRIES} 失败") + if token: + self._solve_count += 1 + debug_logger.log_info(f"[BrowserCaptcha] Token-{self.token_id} 获取成功 ({(time.time()-start_ts)*1000:.0f}ms)") + return token - except Exception as e: - self._error_count += 1 - debug_logger.log_error(f"[BrowserCaptcha] Token-{self.token_id} 浏览器错误: {type(e).__name__}: {str(e)[:200]}") - # 浏览器异常时强制重建 - await self._close_browser_only() + self._error_count += 1 + debug_logger.log_warning(f"[BrowserCaptcha] Token-{self.token_id} 尝试 {attempt+1}/{MAX_RETRIES} 失败") - # 重试前等待 - if attempt < MAX_RETRIES - 1: - await asyncio.sleep(1) + except Exception as e: + self._error_count += 1 + debug_logger.log_error(f"[BrowserCaptcha] Token-{self.token_id} 浏览器错误: {type(e).__name__}: {str(e)[:200]}") + # 浏览器异常时强制重建 + await self._close_browser_only() - return None + # 重试前等待 + if attempt < MAX_RETRIES - 1: + await asyncio.sleep(1) + + return None class BrowserCaptchaService: @@ -681,12 +711,12 @@ def __init__(self, db=None): @classmethod async def get_instance(cls, db=None) -> 'BrowserCaptchaService': - if cls._instance is None: - async with cls._lock: - if cls._instance is None: - cls._instance = cls(db) - # 从数据库加载 browser_count 配置 - await cls._instance._load_browser_count() + # 直接获取锁,避免竞态条件 + async with cls._lock: + if cls._instance is None: + cls._instance = cls(db) + # 在锁内完成初始化(包括异步操作) + await cls._instance._load_browser_count() return cls._instance def _check_available(self): @@ -806,9 +836,12 @@ async def get_token(self, project_id: str, action: str = "IMAGE_GENERATION", tok async def report_error(self, browser_id: int = None): """上层举报:Token 无效(统计用) - + Args: - browser_id: 浏览器 ID(当前架构下每次都是新浏览器,此参数仅用于日志) + browser_id: 浏览器 ID(用于日志记录和错误追踪) + + Note: + 当前使用持久化浏览器架构,browser_id 用于标识哪个浏览器实例产生了无效 token """ async with self._browsers_lock: self._stats["api_403"] += 1 @@ -816,12 +849,21 @@ async def report_error(self, browser_id: int = None): debug_logger.log_info(f"[BrowserCaptcha] 浏览器 {browser_id} 的 token 验证失败") async def remove_browser(self, browser_id: int): + """移除指定浏览器实例并关闭其资源 + + Args: + browser_id: 要移除的浏览器 ID + + Note: + 此方法会调用 browser.close() 清理资源 + """ async with self._browsers_lock: browser = self._browsers.pop(browser_id, None) if browser: await browser.close() async def close(self): + """关闭所有浏览器实例并清理资源""" async with self._browsers_lock: for browser in self._browsers.values(): await browser.close() diff --git a/src/services/browser_captcha_personal.py b/src/services/browser_captcha_personal.py index fb3d4ce..69102b7 100644 --- a/src/services/browser_captcha_personal.py +++ b/src/services/browser_captcha_personal.py @@ -27,8 +27,10 @@ def _has_display_environment() -> bool: if result.returncode == 0: # xvfb-run 可用,说明在 Xvfb 环境中 return True - except: - pass + except (subprocess.TimeoutExpired, FileNotFoundError) as e: + debug_logger.log_debug(f"[BrowserCaptcha] 检测 xvfb-run 失败: {type(e).__name__}") + except Exception as e: + debug_logger.log_debug(f"[BrowserCaptcha] 检测 xvfb-run 异常: {type(e).__name__}") # 有 DISPLAY 环境变量即认为有显示环境 return True @@ -305,8 +307,8 @@ async def stop_resident_mode(self, project_id: Optional[str] = None): if resident_info and resident_info.tab: try: await resident_info.tab.close() - except Exception: - pass + except Exception as e: + debug_logger.log_warning(f"[BrowserCaptcha] 关闭标签页失败: {type(e).__name__}") debug_logger.log_info(f"[BrowserCaptcha] 已关闭所有常驻标签页 (共 {len(project_ids)} 个)") # 向后兼容:清理旧属性 @@ -317,8 +319,8 @@ async def stop_resident_mode(self, project_id: Optional[str] = None): if self.resident_tab: try: await self.resident_tab.close() - except Exception: - pass + except Exception as e: + debug_logger.log_warning(f"[BrowserCaptcha] 关闭常驻标签页失败: {type(e).__name__}") self.resident_tab = None self.resident_project_id = None @@ -425,8 +427,8 @@ async def _execute_recaptcha_on_tab(self, tab, action: str = "IMAGE_GENERATION") # 清理临时变量 try: await tab.evaluate(f"delete window.{token_var}; delete window.{error_var};") - except: - pass + except Exception as e: + debug_logger.log_debug(f"[BrowserCaptcha] 清理临时变量失败: {type(e).__name__}") return token @@ -533,8 +535,8 @@ async def _create_resident_tab(self, project_id: str) -> Optional[ResidentTabInf debug_logger.log_error(f"[BrowserCaptcha] 页面加载超时 (project: {project_id})") try: await tab.close() - except: - pass + except Exception as e: + debug_logger.log_warning(f"[BrowserCaptcha] 关闭超时的标签页失败: {type(e).__name__}") return None # 等待 reCAPTCHA 加载 @@ -544,8 +546,8 @@ async def _create_resident_tab(self, project_id: str) -> Optional[ResidentTabInf debug_logger.log_error(f"[BrowserCaptcha] reCAPTCHA 加载失败 (project: {project_id})") try: await tab.close() - except: - pass + except Exception as e: + debug_logger.log_warning(f"[BrowserCaptcha] 关闭失败的标签页失败: {type(e).__name__}") return None # 创建常驻信息对象 @@ -636,8 +638,8 @@ async def _get_token_legacy(self, project_id: str, action: str = "IMAGE_GENERATI if tab: try: await tab.close() - except Exception: - pass + except Exception as e: + debug_logger.log_warning(f"[BrowserCaptcha] 关闭标签页失败: {type(e).__name__}") async def close(self): """关闭浏览器"""