Всем привет! Если вы хоть раз искали работу в IT за последний год, то знаете, что рынок не любит новичков без опыта. Вы откликаетесь на сотни вакансий, получаете отказы от скриптов-автоответчиков, а чтобы пробиться через фильтры HR, нужно под каждую вакансию писать уникальное сопроводительное письмо.
На просторах интернета можно найти сервисы-автоотклики, которые просят за свои услуги от 3 000 до 5 000 рублей в месяц. Зачем платить за то, что можно написать самому?
В этой статье я сделаю полный разбор того, как я написал собственного автономного ИИ-агента, который ищет вакансии, фильтрует мусор с помощью локальной нейросети, пишет персонализированные сопроводительные письма и отчитывается мне в Telegram, пока я спокойно занимаюсь своими делами. Ни копейки затрат, 100% контроль над кодом. Основная задача в том, чтобы скрипт был бесплатным, автономным и не требовал танцев с бубном вокруг платных API.
Стек:
— Python (asyncio) — как основной движок. Асинхронность критична, чтобы бот не зависал на ожидании сети.
— Playwright — для парсинга и взаимодействия с браузером. Почему не requests? HH.ru активно защищается от ботов (Cloudflare, динамический JS), а Playwright ведет себя как реальный пользователь.
— Ollama (Llama 3) — локальная нейросеть. Полностью бесплатна, работает на моей видеокарте бесплатно.
— Aiogram — для связи с Telegram (алерты и логи).
— SQLite — легковесная БД, чтобы не откликаться на одни и те же вакансии дважды.
Архитектура состоит из 4 модулей: Настройки (config.py), Бот (tg_bot.py), ИИ-анализатор (ai_analyzer.py) и Браузерный движок (hh_client.py).
Шаг 1: Playwright и обход авторизации
Главная проблема автоматизации HH.ru — авторизация. Сервис просит код из SMS или пуша. Делать это при каждом запуске скрипта глупо.
Решение: при первом запуске мы поднимаем Playwright в режиме headless=False (с видимым окном). Я логинюсь ручками, а скрипт сохраняет куки и сессию в файлик state.json.
python
# Из hh_client.pyheadless = os.path.exists(STATE_FILE)self.browser = await self.playwright.chromium.launch(headless=headless)if os.path.exists(STATE_FILE):self.context = await self.browser.new_context(storage_state=STATE_FILE, user_agent=user_agent)
Всё! Теперь при следующих запусках браузер стартует в невидимом (headless) режиме, подхватывает сессию и работает как залогиненный юзер.
Сам поиск устроен хитро: я запускаю двойной проход по запросам (например, «Python backend»). Сначала скрипт парсит вакансии по Питеру (офис + удаленка), а затем по всей России (строго schedule=remote).
Шаг 2: Двойной фильтр и борьба с добротой ии
Алгоритмы поиска HH.ru далеки от идеала. Вбиваешь «C++ разработчик», а тебе выдают вакансии «Педагог автоквантума» или «Коммуникационный дизайнер» просто потому, что эти буквы случайно попали в теги.
Чтобы не тратить ресурсы нейросети на откровенный мусор, я реализовал двойную линию обороны:
1. Жесткий хард-фильтр (Python)
На этапе сбора ссылок я проверяю заголовки вакансий по списку стоп-слов. Если в названии есть «senior», «lead», «стажер», «менеджер», «дизайнер», «hr», «1с», «риелтор» — скрипт делает continue еще до вызова ИИ.
2. Строгий промпт для Llama 3
Оставшиеся вакансии отправляются в Ollama. Поначалу моя Llama 3 была «слишком доброй» — одобряла вакансию не по теме. Пришлось вправить ей мозги жестким промптом:
prompt = f"""Твоя задача — оценить, подходит ли вакансия под мои критерии поиска.Мои требования и профиль: {MY_RESUME_SUMMARY}
Также мне СТРОГО НЕ подходят (отклоняй сразу, отвечая NO):
Вакансии из других сфер: менеджеры, HR, маркетологи, риелторы...
Любые вакансии, которые НЕ связаны напрямую с написанием кода.
Если вакансия не про программирование — сразу пиши NO."""
Теперь ИИ отвечает строго одним словом YES или NO, и делает это безошибочно.
Шаг 3: Генерация сопроводительных писем
Если нейросеть сказала YES, мы переходим к генерации письма. Мой скрипт передает в Llama 3 текст вакансии и мое резюме (с указанием пет-проектов и GitHub). Нейросеть генерирует уникальный текст, подсвечивая именно те навыки, которые нужны работодателю.
Проблема галлюцинаций:
Локальные LLM любят поболтать. Они могут выдать: «Here is your cover letter: Здравствуйте…». Чтобы письмо не выглядело кринжово, я прописал в промпте критические правила:
ПИСАТЬ СТРОГО ТОЛЬКО НА РУССКОМ ЯЗЫКЕ! Никакого английского.ВЫВОДИ ТОЛЬКО ТЕКСТ ПИСЬМА. Строго запрещены любые вводные фразы.
И на всякий случай добавил жесткую очистку на питоне: text.split("\n\n", 1)[-1].
Иногда иишка все равно начинает писать на английском, чтож издержки автоматизации.
Шаг 4: Умный кликер и обработка UI Headhunter’а
Итак, вот письмо подготовили, нужно откликнуться. И тут начинается борьба с фронтендом HH.ru.
Иногда кнопка «Откликнуться» ведет сразу на форму, иногда открывает модальное окно. Иногда поле для письма скрыто за кнопкой «Написать сопроводительное», а иногда оно сразу на экране.
Playwright решает это элегантно:
# Кликаем "Откликнуться"
await apply_btn.click()
# Ищем выпадающий список резюме, если у нас их несколько, и выбираем нужное:
resume_dropdown = page.locator('[data-qa*="resume-select"]').first
# ... кликаем на TARGET_RESUME_NAME ("Backend-разработчик")
#Ищем поле для письма, если оно спрятано — открываем
add_letter_btn = page.locator('button[data-qa="vacancy-response-letter-toggle"]').first
if await add_letter_btn.is_visible():
await add_letter_btn.click()
# Печатаем письмо в textarea, как живой человек
letter_input = page.locator('textarea[name="text"]') # или placeholder="Напишите сопроводительное..."
await letter_input.fill(cover_letter)
Жмем "Отправить"
await submit_btn.click()
Всё это происходит в фоне за пару секунд!
Шаг 5: Интеграция с Telegram
Скрипт крутится в бесконечном цикле while True, засыпая на 30 минут между итерациями. Чтобы понимать, что происходит, я прикрутил Telegram-бота на aiogram.
Бот присылает мне уведомления:✅ Успешный отклик: Backend-разработчик (Middle) и сразу прикрепляет сгенерированное сопроводительное письмо, чтобы я мог оценить, что там нафантазировала Llama и покекать.
Более того, мой ИИ-комбайн читает чаты на HH.ru! Раз в полчаса он проверяет вкладку сообщений, и если HR мне ответил (пригласил на собес или прислал тестовое), скрипт парсит текст и пересылает мне в Тгшку.
Итого, мы имеем:
1. Бесплатный автоотклик, который заменяет сервисы за 5к в месяц.
2. ИИ пишет сопроводительные, чтобы отклик не был пустым
3. Скрипт сам выбирает нужное резюме, присылает все отклики прямо в мессенджер.
4. Мой ПК работает на меня, пока я занимаюсь своими делами или играю в игры.
Какие минусы?
Локальная LLM ест видеопамять. Если вы хотите запустить тяжелую игрушку параллельно с ботом, начнутся просадки FPS. Решается это легко — переездом с локальной Ollama на бесплатный API облачных провайдеров (например, Groq), что потребует изменения ровно двух строчек в коде, и тогда скрипт станет вообще невесомым.
Рынок труда — это борьба с ии фильтрами. Так зачем играть по правилам и тратить кучу времени на то, что железка с большой вероятностью откинет?
Самое важное: Мы не обманываем работодателя насчет опыта работы или стека. Мы лишь автоматизируем поиск, для того чтобы получить заветный собес.
Пишите код, автоматизируйте рутину и получайте лучшие офферы. Всем удачи!
ссылка на оригинал статьи https://habr.com/ru/articles/1055530/