Полуавтоматический отклик на hh.ru: поиск, ИИ-генерация, веб-интерфейс

This commit is contained in:
apuc committed 2026-09-12 13:16:23 +03:00
commit 0ca55f3818
11 files changed
+1740

No files matched your search

+93
View File
@@ -0,0 +1,93 @@
#!/usr/bin/env python3
"""Работа с браузером: открытие вакансии, клик «Откликнуться», вставка текста."""
import os
from playwright.sync_api import sync_playwright
# Селекторы hh.ru (с запасными вариантами)
RESPONSE_BUTTONS = [
'[data-qa="vacancy-response-link-top"]',
'[data-qa="vacancy-response-link"]',
'button:has-text("Откликнуться")',
]
MESSAGE_FIELDS = [
'textarea[data-qa="vacancy-response-letter"]',
'textarea[data-qa="vacancy-response-popup-form"] textarea',
'textarea',
]
# Живые браузерные контексты по аккаунтам: {account_name: context}
_browsers = {}
def launch_context(account_dir):
"""Запускает браузер с профилем аккаунта (видимое окно)."""
p = sync_playwright().start()
context = p.chromium.launch_persistent_context(
user_data_dir=os.path.join(account_dir, "profile"),
headless=False,
viewport={"width": 1400, "height": 900},
)
return context
def get_context(account_dir, account_name):
"""Возвращает живой контекст аккаунта, создавая при необходимости."""
if account_name not in _browsers:
_browsers[account_name] = launch_context(account_dir)
return _browsers[account_name]
def close_context(account_name):
if account_name in _browsers:
try:
_browsers[account_name].close()
except Exception:
pass
_browsers.pop(account_name, None)
def open_response_page(context, vacancy, message, resume_id=None):
"""Открывает вакансию, жмёт «Откликнуться», выбирает резюме и вставляет текст.
Возвращает (ok, note): ok=True если текст вставлен автоматически."""
page = context.new_page()
page.goto(vacancy["url"], timeout=45000)
page.wait_for_timeout(2500)
# Кнопка «Откликнуться»
clicked = False
for sel in RESPONSE_BUTTONS:
try:
btn = page.locator(sel).first
if btn.is_visible(timeout=3000):
btn.click()
clicked = True
break
except Exception:
continue
if not clicked:
return False, "Не нашёл кнопку «Откликнуться» — сделайте это вручную"
# Ждём модалку
page.wait_for_timeout(2000)
# Выбор резюме (радио-кнопка name="resumeId" value="{hash}")
if resume_id:
try:
radio = page.locator(f'input[name="resumeId"][value="{resume_id}"]').first
if radio.is_visible(timeout=3000):
radio.check(force=True)
print(f" ✓ Резюме {resume_id} выбрано")
except Exception:
print(" ⚠ Не нашёл радио-кнопку резюме — выберите вручную")
# Вставляем текст
for sel in MESSAGE_FIELDS:
try:
field = page.locator(sel).first
if field.is_visible(timeout=3000):
field.fill(message)
return True, "Текст вставлен. Проверьте и нажмите «Отправить»"
except Exception:
continue
return False, "Не нашёл поле сообщения — вставьте текст вручную"