diff --git a/hh_browser.py b/hh_browser.py index e4a82dd..bc35494 100644 --- a/hh_browser.py +++ b/hh_browser.py @@ -42,6 +42,7 @@ SUBMIT_BUTTONS = [ # Доступны ТОЛЬКО из браузерного потока. _browsers = {} _pages = {} +_playwrights = {} # ---------- Браузерный поток ---------- @@ -111,9 +112,8 @@ def get_proxy_config(): # ---------- Внутренние функции (только из браузерного потока) ---------- -def _launch_context(account_dir): +def _launch_context_with(p, account_dir): """Запускает браузер с профилем аккаунта и подставляет cookies сессии.""" - p = sync_playwright().start() kwargs = dict( user_data_dir=os.path.join(account_dir, "profile"), headless=is_headless(), @@ -127,6 +127,15 @@ def _launch_context(account_dir): return context +def _context_alive(ctx): + """Проверяет, жив ли контекст (лёгкая операция, не создаёт страниц).""" + try: + ctx.pages + return True + except Exception: + return False + + def _inject_session_cookies(context, account_dir): """Подставляет cookies из session.txt — вход на hh.ru без ручного логина.""" path = os.path.join(account_dir, "session.txt") @@ -149,10 +158,33 @@ def _inject_session_cookies(context, account_dir): def _get_context(account_dir, user, account): + """Возвращает живой контекст; если браузер умер — перезапускает.""" key = (user, account) - if key not in _browsers: - _browsers[key] = _launch_context(account_dir) - return _browsers[key] + ctx = _browsers.get(key) + if ctx is None or not _context_alive(ctx): + if ctx is not None: + print(f" ⚠ Браузер {key} закрыт — перезапускаю") + _close_context(key) + p = sync_playwright().start() + try: + ctx = _launch_context_with(p, account_dir) + except Exception: + p.stop() + raise + _browsers[key] = ctx + _playwrights[key] = p + return ctx + + +def _new_page(account_dir, user, account): + """Создаёт страницу; если контекст умер между проверкой и созданием — + перезапускает браузер и пробует ещё раз.""" + try: + return _get_context(account_dir, user, account).new_page() + except Exception: + print(f" ⚠ Не удалось создать страницу для {(user, account)} — перезапускаю браузер") + _close_context((user, account)) + return _get_context(account_dir, user, account).new_page() def _save_screenshot(page, screenshot_dir, vid): @@ -181,8 +213,7 @@ def open_response_page(account_dir, user, account, vacancy, message, except Exception: pass - context = _get_context(account_dir, user, account) - page = context.new_page() + page = _new_page(account_dir, user, account) _pages[key] = page page.goto(vacancy["url"], timeout=45000) page.wait_for_timeout(2500) @@ -276,15 +307,14 @@ def confirm_send(user, account, vid): def open_login_page(account_dir, user, account): """Открывает hh.ru в браузере (для ручного входа, локальный режим с экраном).""" key = (user, account) - context = _get_context(account_dir, user, account) - page = context.new_page() + page = _new_page(account_dir, user, account) _pages[key] = page page.goto("https://hh.ru", timeout=45000) return True, "Страница hh.ru открыта" -def close_context(user, account): - key = (user, account) +def _close_context(key): + """Закрывает контекст и останавливает playwright-инстанс (из браузерного потока).""" if key in _browsers: try: _browsers[key].close() @@ -292,6 +322,16 @@ def close_context(user, account): pass _browsers.pop(key, None) _pages.pop(key, None) + p = _playwrights.pop(key, None) + if p: + try: + p.stop() + except Exception: + pass + + +def close_context(user, account): + _close_context((user, account)) # ---------- Публичные обёртки (можно вызывать из любого потока) ----------