CoolFace
Apppublic

zhiyinya/AIStudioBuildWS

sourceHugging Faceupdated 10mo agoView on Hugging Face
0likes
instance.py217 linesDownload Raw Back to browser
1import os2from playwright.sync_api import TimeoutError, Error as PlaywrightError3from utils.logger import setup_logging4from utils.cookie_manager import CookieManager5from browser.navigation import handle_successful_navigation6from camoufox.sync_api import Camoufox7from utils.paths import logs_dir8from utils.common import parse_headless_mode, ensure_dir9from utils.url_helper import extract_url_path10 11 12def run_browser_instance(config):13    """14    根据最终合并的配置,启动并管理一个单独的 Camoufox 浏览器实例。15    使用CookieManager统一管理cookie加载,避免重复的扫描逻辑。16    """17    cookie_source = config.get('cookie_source')18    if not cookie_source:19        # 使用默认logger进行错误报告20        logger = setup_logging(os.path.join(logs_dir(), 'app.log'))21        logger.error("错误: 配置中缺少cookie_source对象")22        return23 24    instance_label = cookie_source.display_name25    logger = setup_logging(26        os.path.join(logs_dir(), 'app.log'), prefix=instance_label27    )28    diagnostic_tag = instance_label.replace(os.sep, "_")29 30    expected_url = config.get('url')31    proxy = config.get('proxy')32    headless_setting = config.get('headless', 'virtual')33 34    # 使用CookieManager加载cookie35    cookie_manager = CookieManager(logger)36    all_cookies = []37 38    try:39        # 直接使用CookieSource对象加载cookie40        cookies = cookie_manager.load_cookies(cookie_source)41        all_cookies.extend(cookies)42 43    except Exception as e:44        logger.error(f"从cookie来源加载时出错: {e}")45        return46 47    # 3. 检查是否有任何cookie可用48    if not all_cookies:49        logger.error("错误: 没有可用的cookie(既没有有效的JSON文件,也没有环境变量)")50        return51 52    cookies = all_cookies53 54    headless_mode = parse_headless_mode(headless_setting)55    launch_options = {"headless": headless_mode}56    if proxy:57        logger.info(f"使用代理: {proxy} 访问")58        launch_options["proxy"] = {"server": proxy, "bypass": "localhost, 127.0.0.1"}59    # 无需禁用图片加载, 因为图片很少, 禁用还可能导致风控增加60    # launch_options["block_images"] = True61    62    screenshot_dir = logs_dir()63    ensure_dir(screenshot_dir)64 65    try:66        with Camoufox(**launch_options) as browser:67            context = browser.new_context()68            context.add_cookies(cookies)69            page = context.new_page()70            71            # ####################################################################72            # ############ 增强的 page.goto() 错误处理和日志记录 ###############73            # ####################################################################74            75            response = None76            try:77                logger.info(f"正在导航到: {expected_url} (超时设置为 120 秒)")78                # page.goto() 会返回一个 response 对象,我们可以用它来获取状态码等信息79                response = page.goto(expected_url, wait_until='domcontentloaded', timeout=120000)80                81                # 检查HTTP响应状态码82                if response:83                    logger.info(f"导航初步成功,服务器响应状态码: {response.status} {response.status_text}")84                    if not response.ok: # response.ok 检查状态码是否在 200-299 范围内85                        logger.warning(f"警告:页面加载成功,但HTTP状态码表示错误: {response.status}")86                        # 即使状态码错误,也保存快照以供分析87                        page.screenshot(path=os.path.join(screenshot_dir, f"WARN_http_status_{response.status}_{diagnostic_tag}.png"))88                else:89                    # 对于非http/https的导航(如 about:blank),response可能为None90                    logger.warning("page.goto 未返回响应对象,可能是一个非HTTP导航。")91 92            except TimeoutError:93                # 这是最常见的错误:超时94                logger.error(f"导航到 {expected_url} 超时 (超过120秒)。")95                logger.error("可能原因:网络连接缓慢、目标网站服务器无响应、代理问题、或页面资源被阻塞。")96                # 尝试保存诊断信息97                try:98                    # 截图对于看到页面卡在什么状态非常有帮助(例如,空白页、加载中、Chrome错误页)99                    screenshot_path = os.path.join(screenshot_dir, f"FAIL_timeout_{diagnostic_tag}.png")100                    page.screenshot(path=screenshot_path, full_page=True)101                    logger.info(f"已截取超时时的屏幕快照: {screenshot_path}")102                    103                    # 保存HTML可以帮助分析DOM结构,即使在无头模式下也很有用104                    html_path = os.path.join(screenshot_dir, f"FAIL_timeout_{diagnostic_tag}.html")105                    with open(html_path, 'w', encoding='utf-8') as f:106                        f.write(page.content())107                    logger.info(f"已保存超时时的页面HTML: {html_path}")108                except Exception as diag_e:109                    logger.error(f"在尝试进行超时诊断(截图/保存HTML)时发生额外错误: {diag_e}")110                return # 超时后,后续操作无意义,直接终止111 112            except PlaywrightError as e:113                # 捕获其他Playwright相关的网络错误,例如DNS解析失败、连接被拒绝等114                error_message = str(e)115                logger.error(f"导航到 {expected_url} 时发生 Playwright 网络错误。")116                logger.error(f"错误详情: {error_message}")117                118                # Playwright的错误信息通常很具体,例如 "net::ERR_CONNECTION_REFUSED"119                if "net::ERR_NAME_NOT_RESOLVED" in error_message:120                    logger.error("排查建议:检查DNS设置或域名是否正确。")121                elif "net::ERR_CONNECTION_REFUSED" in error_message:122                    logger.error("排查建议:目标服务器可能已关闭,或代理/防火墙阻止了连接。")123                elif "net::ERR_INTERNET_DISCONNECTED" in error_message:124                    logger.error("排查建议:检查本机的网络连接。")125                126                # 同样,尝试截图,尽管此时页面可能完全无法访问127                try:128                    screenshot_path = os.path.join(screenshot_dir, f"FAIL_network_error_{diagnostic_tag}.png")129                    page.screenshot(path=screenshot_path)130                    logger.info(f"已截取网络错误时的屏幕快照: {screenshot_path}")131                except Exception as diag_e:132                    logger.error(f"在尝试进行网络错误诊断(截图)时发生额外错误: {diag_e}")133                return # 网络错误,终止134 135            # --- 如果导航没有抛出异常,继续执行后续逻辑 ---136            137            logger.info("页面初步加载完成,正在检查并处理初始弹窗...")138            page.wait_for_timeout(2000)139            140            final_url = page.url141            logger.info(f"导航完成。最终URL为: {final_url}")142 143            # ... 你原有的URL检查逻辑保持不变 ...144            if "accounts.google.com/v3/signin/identifier" in final_url:145                logger.error("检测到Google登录页面(需要输入邮箱)。Cookie已完全失效。")146                page.screenshot(path=os.path.join(screenshot_dir, f"FAIL_identifier_page_{diagnostic_tag}.png"))147                return148 149            # 提取路径部分进行匹配(允许域名重定向)150            expected_path = extract_url_path(expected_url).split('?')[0]151            final_path = extract_url_path(final_url)152 153            if expected_path and expected_path in final_path:154                logger.info(f"URL验证通过。预期路径: {expected_path}, 最终URL: {final_url}")155 156                # --- NEW ROBUST STRATEGY: Wait for the loading spinner to disappear ---157                # This is the key to solving the race condition. The error message or158                # content will only appear AFTER the initial loading is done.159                spinner_locator = page.locator('mat-spinner')160                try:161                    logger.info("正在等待加载指示器 (spinner) 消失... (最长等待30秒)")162                    # We wait for the spinner to be 'hidden' or not present in the DOM.163                    spinner_locator.wait_for(state='hidden', timeout=30000)164                    logger.info("加载指示器已消失。页面已完成异步加载。")165                except TimeoutError:166                    logger.error("页面加载指示器在30秒内未消失。页面可能已卡住。")167                    page.screenshot(path=os.path.join(screenshot_dir, f"FAIL_spinner_stuck_{diagnostic_tag}.png"))168                    return # Exit if the page is stuck loading169 170                # --- NOW, we can safely check for the error message ---171                # We use the most specific text possible to avoid false positives.172                auth_error_text = "authentication error"173                auth_error_locator = page.get_by_text(auth_error_text, exact=False)174 175                # We only need a very short timeout here because the page should be stable.176                if auth_error_locator.is_visible(timeout=2000):177                    logger.error(f"检测到认证失败的错误横幅: '{auth_error_text}'. Cookie已过期或无效。")178                    screenshot_path = os.path.join(screenshot_dir, f"FAIL_auth_error_banner_{diagnostic_tag}.png")179                    page.screenshot(path=screenshot_path)180                    181                    # html_path = os.path.join(screenshot_dir, f"FAIL_auth_error_banner_{diagnostic_tag}.html")182                    # with open(html_path, 'w', encoding='utf-8') as f:183                    #     f.write(page.content())184                    # logger.info(f"已保存包含错误信息的页面HTML: {html_path}")185                    return # Definitive failure, so we exit.186 187                # --- If no error, proceed to final confirmation (as a fallback) ---188                logger.info("未检测到认证错误横幅。进行最终确认。")189                login_button_cn = page.get_by_role('button', name='登录')190                login_button_en = page.get_by_role('button', name='Login')191                192                if login_button_cn.is_visible(timeout=1000) or login_button_en.is_visible(timeout=1000):193                    logger.error("页面上仍显示'登录'按钮。Cookie无效。")194                    page.screenshot(path=os.path.join(screenshot_dir, f"FAIL_login_button_visible_{diagnostic_tag}.png"))195                    return196 197                # --- If all checks pass, we assume success ---198                logger.info("所有验证通过,确认已成功登录。")199                handle_successful_navigation(page, logger, diagnostic_tag)200            elif "accounts.google.com/v3/signin/accountchooser" in final_url:201                logger.warning("检测到Google账户选择页面。登录失败或Cookie已过期。")202                page.screenshot(path=os.path.join(screenshot_dir, f"FAIL_chooser_click_failed_{diagnostic_tag}.png"))203                return204            else:205                logger.error(f"导航到了意外的URL。")206                logger.error(f"  预期路径: {expected_path}")207                logger.error(f"  最终URL: {final_url}")208                logger.error(f"  最终路径: {final_path}")209                page.screenshot(path=os.path.join(screenshot_dir, f"FAIL_unexpected_url_{diagnostic_tag}.png"))210                return211 212    except KeyboardInterrupt:213        logger.info(f"用户中断,正在关闭...")214    except Exception as e:215        # 这是一个最终的捕获,用于捕获所有未预料到的错误216        logger.exception(f"运行 Camoufox 实例时发生未预料的严重错误: {e}")217