fix: авто-перезапуск браузера при закрытом контексте (BrowserContext.new_page: has been closed)

This commit is contained in:
apuc committed 2026-09-16 13:20:19 +03:00
1 parent 4dcaffd4c4
commit e4cd06a23d
1 file changed
+51 -11
+51 -11
View File
@@ -42,6 +42,7 @@ SUBMIT_BUTTONS = [
# Доступны ТОЛЬКО из браузерного потока. # Доступны ТОЛЬКО из браузерного потока.
_browsers = {} _browsers = {}
_pages = {} _pages = {}
_playwrights = {}
# ---------- Браузерный поток ---------- # ---------- Браузерный поток ----------
@@ -111,9 +112,8 @@ def get_proxy_config():
# ---------- Внутренние функции (только из браузерного потока) ---------- # ---------- Внутренние функции (только из браузерного потока) ----------
def _launch_context(account_dir): def _launch_context_with(p, account_dir):
"""Запускает браузер с профилем аккаунта и подставляет cookies сессии.""" """Запускает браузер с профилем аккаунта и подставляет cookies сессии."""
p = sync_playwright().start()
kwargs = dict( kwargs = dict(
user_data_dir=os.path.join(account_dir, "profile"), user_data_dir=os.path.join(account_dir, "profile"),
headless=is_headless(), headless=is_headless(),
@@ -127,6 +127,15 @@ def _launch_context(account_dir):
return context return context
def _context_alive(ctx):
"""Проверяет, жив ли контекст (лёгкая операция, не создаёт страниц)."""
try:
ctx.pages
return True
except Exception:
return False
def _inject_session_cookies(context, account_dir): def _inject_session_cookies(context, account_dir):
"""Подставляет cookies из session.txt — вход на hh.ru без ручного логина.""" """Подставляет cookies из session.txt — вход на hh.ru без ручного логина."""
path = os.path.join(account_dir, "session.txt") 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): def _get_context(account_dir, user, account):
"""Возвращает живой контекст; если браузер умер — перезапускает."""
key = (user, account) key = (user, account)
if key not in _browsers: ctx = _browsers.get(key)
_browsers[key] = _launch_context(account_dir) if ctx is None or not _context_alive(ctx):
return _browsers[key] 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): def _save_screenshot(page, screenshot_dir, vid):
@@ -181,8 +213,7 @@ def open_response_page(account_dir, user, account, vacancy, message,
except Exception: except Exception:
pass pass
context = _get_context(account_dir, user, account) page = _new_page(account_dir, user, account)
page = context.new_page()
_pages[key] = page _pages[key] = page
page.goto(vacancy["url"], timeout=45000) page.goto(vacancy["url"], timeout=45000)
page.wait_for_timeout(2500) page.wait_for_timeout(2500)
@@ -276,15 +307,14 @@ def confirm_send(user, account, vid):
def open_login_page(account_dir, user, account): def open_login_page(account_dir, user, account):
"""Открывает hh.ru в браузере (для ручного входа, локальный режим с экраном).""" """Открывает hh.ru в браузере (для ручного входа, локальный режим с экраном)."""
key = (user, account) key = (user, account)
context = _get_context(account_dir, user, account) page = _new_page(account_dir, user, account)
page = context.new_page()
_pages[key] = page _pages[key] = page
page.goto("https://hh.ru", timeout=45000) page.goto("https://hh.ru", timeout=45000)
return True, "Страница hh.ru открыта" return True, "Страница hh.ru открыта"
def close_context(user, account): def _close_context(key):
key = (user, account) """Закрывает контекст и останавливает playwright-инстанс (из браузерного потока)."""
if key in _browsers: if key in _browsers:
try: try:
_browsers[key].close() _browsers[key].close()
@@ -292,6 +322,16 @@ def close_context(user, account):
pass pass
_browsers.pop(key, None) _browsers.pop(key, None)
_pages.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))
# ---------- Публичные обёртки (можно вызывать из любого потока) ---------- # ---------- Публичные обёртки (можно вызывать из любого потока) ----------