Бесплатный ИИ-агент для HH.ru: руководство с кодом | AiManual
AiManual Logo Ai / Manual.
04 Июл 2026 Гайд

Бесплатный ИИ-агент для HH.ru: как заставить нейросеть искать работу за тебя

Пошаговое руководство по созданию бесплатного ИИ-агента для автоматического отклика на вакансии HH.ru. Playwright, Ollama, Aiogram, Python. Полный код.

Ты всё ещё кликаешь по вакансиям пальцем? Серьёзно?

Проснулся, кофе, 20 вакансий, однотипное сопроводительное письмо, отклик, день прошёл, тишина. Знакомо? Поиск работы — это адская бюрократия, где выигрывает не самый умный, а самый быстрый. Или тот, кто умеет автоматизировать.

В этой статье я покажу, как собрать бесплатного ИИ-агента, который будет делать всё за тебя: мониторить новые вакансии, генерировать персонализированные сопроводительные письма под каждую компанию и откликаться без твоего участия. Всё на локальном железе, без платных API и риска утечки данных. Разберём код до последней запятой на реальном стеке: asyncio + Playwright + Ollama + Aiogram.

Предупреждение: HH.ru не любит ботов. Использовать этот агент стоит с умом — ставить задержки, рандомизировать поведение и не долбить сервер сотнями запросов в минуту. Один аккаунт живёт дольше, если вести себя как человек.

Почему агент, а не простой скрипт?

Можно взять requests, накидать парсер, захардкодить письмо. Но HH.ru за последние пару лет научился отлавливать таких. Они смотрят на поведение: как быстро ты заполняешь форму, как двигаешь мышь, какие заголовки шлёшь. Им нужен эмулятор человека.

Playwright (не путать с Puppeteer) — это браузер без головы, который может двигать мышкой с человеческой скоростью, скроллить, ждать случайное время. А Ollama с моделью Llama 4 Instruct (или, если железа мало, Qwen 2.5 7B) даёт осмысленный текст, который не палится одинаковыми фразами. Добавим сюда Telegram-бота на Aiogram 3 — управлять агентом можно будет с телефона: запустить поиск, остановить, посмотреть статистику.

И да, всё это бесплатно. Ни копейки за API, только электричество и желание разобраться.

Что будем строить: архитектура за 30 секунд

Компонент Назначение
Telegram-бот (Aiogram) Получает команды от пользователя, отдаёт статус, управляет циклом
Brain-модуль (Ollama) Генерирует сопроводительное письмо под каждую вакансию на основе резюме
Hunter-модуль (Playwright) Авторизуется на HH.ru, парсит страницу поиска, заполняет форму отклика
Orchestrator (asyncio) Координирует всё, ставит задачи в очередь, логирует ошибки

Настройка окружения: с нуля за 10 минут

Предполагается, что у тебя Python 3.13+ и pip. Если нет — качаем с офсайта. Всё остальное ставится одной командой.

1 Установка зависимостей

pip install playwright aiogram ollama python-dotenv loguru
playwright install chromium
💡
Почему playwright install chromium? Без этого не будет браузера. Playwright сам скажет, если забудешь. В 2026 году Chromium — самый стабильный вариант для stealth-режимов.

2 Установка Ollama и модели

Качаем Ollama с официального сайта (Windows, macOS, Linux — всё работает). После установки:

ollama pull llama4:7b-instruct-q4_K_M

Почему Llama 4 7B? Она достаточно легкая для домашнего ПК (4 ГБ VRAM или 8 RAM), но при этом даёт связный русский текст. Если есть 12+ ГБ — бери llama4:70b-instruct. Если совсем туго — qwen2.5:7b или gemma3:7b. Про локальные модели мы писали в статье «Российский локальный AI-агент: сборка с нуля без облака, VPN и подписок» — там подробно про выбор и оптимизацию.

Важно: Ollama должна быть запущена как сервис. По умолчанию после установки она уже висит на localhost:11434. Проверь: curl http://localhost:11434/api/tags

Пишем код: ядро агента

Создаём файл config.py — всё чувствительное (логин, пароль HH, токен бота) храним в .env. Никаких хардкодов.

3 Конфиг и .env

# config.py
from dotenv import load_dotenv
import os

load_dotenv()

HH_LOGIN = os.getenv('HH_LOGIN')
HH_PASSWORD = os.getenv('HH_PASSWORD')
BOT_TOKEN = os.getenv('BOT_TOKEN')
OLLAMA_URL = os.getenv('OLLAMA_URL', 'http://localhost:11434')
RESUME_TEXT = os.getenv('RESUME_TEXT', 'Я DevOps-инженер с 5-летним опытом...')  # твоё резюме одной строкой
SEARCH_QUERY = os.getenv('SEARCH_QUERY', 'DevOps')

.env файл:

HH_LOGIN=your_email@example.com
HH_PASSWORD=your_secret_password
BOT_TOKEN=123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11
RESUME_TEXT=Опыт работы 5 лет в DevOps, CI/CD, Docker, Kubernetes...
SEARCH_QUERY=DevOps

4 Модуль генерации писем (brain.py)

Тут мы дёргаем Ollama через клиентскую библиотеку. Промпт — ключевой момент. Если дать плохой промпт — получишь кашу. Об этом мы писали в «Agent Skills: как заставить ИИ-агента не тупить и помнить все инструкции». Наш промпт строится так: контекст (твоё резюме и текст вакансии), инструкция (написать письмо на 2 абзаца, выделить подходящие навыки), формат (только текст письма).

# brain.py
from ollama import Client

class ResumeGenerator:
    def __init__(self, ollama_url: str):
        self.client = Client(host=ollama_url)
        self.model = 'llama4:7b-instruct-q4_K_M'

    async def generate_cover_letter(self, vacancy_text: str, resume: str) -> str:
        prompt = f"""Ты — профессиональный карьерный консультант. Напиши персонализированное сопроводительное письмо для отклика на вакансию. 
Вот резюме кандидата: {resume}

Вот описание вакансии: {vacancy_text}

Письмо должно быть:
- На русском языке
- Не длиннее 3 абзацев
- Выделять релевантные навыки из резюме под конкретную вакансию
- Заканчиваться фразой о готовности к собеседованию

Напиши только текст письма, без лишних фраз."""
        response = self.client.chat(model=self.model, messages=[{'role': 'user', 'content': prompt}])
        return response['message']['content'].strip()

5 Модуль поиска и отклика (hunter.py)

Самый жирный кусок. Playwright открывает страницу HH, логинится, выполняет поиск, собирает ссылки на вакансии, а потом для каждой открывает форму отклика и вставляет сгенерированное письмо.

Критически важный момент: имитация поведения человека. Между действиями — случайные задержки (1-3 секунды), движения мыши, иногда прокрутка страницы. HH использует поведенческую аналитику (следят за перемещениями курсора).

# hunter.py
import asyncio
from playwright.async_api import async_playwright, Page

class HHHunter:
    def __init__(self, login: str, password: str, search_query: str):
        self.login = login
        self.password = password
        self.search_query = search_query
        self.browser = None
        self.page = None

    async def __aenter__(self):
        self.playwright = await async_playwright().start()
        self.browser = await self.playwright.chromium.launch(
            headless=True,
            args=['--disable-blink-features=AutomationControlled']
        )
        self.context = await self.browser.new_context(
            viewport={'width': 1280, 'height': 720},
            user_agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ...'
        )
        self.page = await self.context.new_page()
        await self._login()
        return self

    async def __aexit__(self, *args):
        await self.browser.close()
        await self.playwright.stop()

    async def _login(self):
        await self.page.goto('https://hh.ru/account/login', wait_until='networkidle')
        # Ждём поле логина и вводим
        await self.page.fill('input[name="login"]', self.login)
        await asyncio.sleep(1)
        await self.page.fill('input[type="password"]', self.password)
        await self.page.click('button[data-qa="account-login-submit"]')
        await self.page.wait_for_url('**/account/me*', timeout=30000)
        print('Авторизация прошла успешно')

    async def collect_vacancies(self) -> list[dict]:
        """Ищем вакансии и возвращаем список ссылок и текстов"""
        await self.page.goto(f'https://hh.ru/search/vacancy?text={self.search_query}', wait_until='networkidle')
        await asyncio.sleep(2)
        links = await self.page.query_selector_all('a.bloko-link[data-qa="serp-item__title"]')
        vacancies = []
        for link in links[:10]:  # берём первые 10
            url = await link.get_attribute('href')
            title = await link.inner_text()
            # Собираем описание вакансии (открываем в соседней вкладке? Нет, лучше в этой)
            # Для простоты — переходим по ссылке, ждём, читаем описание
            await self.page.goto(url, wait_until='networkidle')
            desc = await self.page.inner_text('div[data-qa="vacancy-description"]')
            vacancies.append({'url': url, 'title': title, 'description': desc[:2000]})
            await self.page.go_back()
            await asyncio.sleep(1)
        return vacancies

    async def apply_to_vacancy(self, vacancy_url: str, cover_letter: str):
        """Открывает страницу вакансии и отправляет отклик"""
        await self.page.goto(vacancy_url, wait_until='networkidle')
        await asyncio.sleep(1)
        # Кнопка отклика (селектор может меняться, проверяй вручную)
        await self.page.click('button[data-qa="vacancy-response-link-top"]')
        await self.page.wait_for_selector('textarea[data-qa="vacancy-response-popup-form-letter"]', timeout=10000)
        await self.page.fill('textarea[data-qa="vacancy-response-popup-form-letter"]', cover_letter)
        # Человеческая задержка перед отправкой
        await asyncio.sleep(2)
        await self.page.click('button[data-qa="vacancy-response-submit"]')
        print(f'Отклик отправлен на {vacancy_url}')
        await asyncio.sleep(5)  # ждём между откликами

6 Telegram-бот (bot.py)

На Aiogram 3. Бот принимает команды /start_search и /stop, а также /status. Старт поиска запускает фоновую задачу. Полный код с управлением стейтом и очередью — в нашем репозитории (ссылка в конце).

# bot.py
import asyncio
from aiogram import Bot, Dispatcher, types
from aiogram.filters import Command

bot = Bot(token=config.BOT_TOKEN)
dp = Dispatcher()

@dp.message(Command('start_search'))
async def cmd_start(msg: types.Message):
    # Запускаем цикл охоты в фоновом режиме
    asyncio.create_task(run_hunting_cycle(msg.from_user.id))
    await msg.answer('Охота началась! Буду присылать отчёты.')

@dp.message(Command('stop'))
async def cmd_stop(msg: types.Message):
    # Останавливаем глобальный флаг
    global is_running
    is_running = False
    await msg.answer('Остановлено.')

async def run_hunting_cycle(chat_id):
    """Главный цикл: сбор вакансий -> генерация -> отклик"""
    async with HHHunter(config.HH_LOGIN, config.HH_PASSWORD, config.SEARCH_QUERY) as hunter:
        brain = ResumeGenerator(config.OLLAMA_URL)
        while is_running:
            vacancies = await hunter.collect_vacancies()
            for v in vacancies:
                cover = await brain.generate_cover_letter(v['description'], config.RESUME_TEXT)
                await hunter.apply_to_vacancy(v['url'], cover)
                await bot.send_message(chat_id, f'Откликнулся на {v['title']}')
            await asyncio.sleep(3600)  # пауза час между раундами
💡
Для продакшена добавь Redis как брокер очереди, чтобы не потерять задачи при перезапуске. Подробнее про снижение латентности AI-агентов читай в статье «Поиск для AI-агентов».

Подводные камни и как их обходить

Капча на HH.ru

Иногда HH просит ввести капчу после нескольких действий. Решений два: либо ставить очень большие задержки (5-10 секунд между откликами, рандомные паузы 3-7 секунд), либо использовать прокси и менять их. Капчу можно решать через сервисы распознавания типа 2captcha, но это уже не бесплатно. Лучше минимизировать частоту: запускать агента раз в 2-3 часа и не на все вакансии подряд.

Селекторы ломаются

HH постоянно обновляет интерфейс. data-qa атрибуты могут меняться. Чтобы не переписывать код каждый месяц, используй fallback-селекторы: например, ищем кнопку по тексту «Откликнуться». А ещё лучше — в начале каждого запуска сохранять скриншот страницы и логировать, смог ли кликнуть.

Одинаковые письма

Если модель генерирует одно и то же из-за маленького контекста — добавь в промпт случайную «персонализацию»: упоминать название компании из вакансии. Для этого нужно доставать название компании при парсинге и вставлять в промпт. Это повышает качество и уменьшает подозрения.

Блокировка аккаунта

HH может временно заблокировать аккаунт при подозрительной активности. Всегда используй headless=False для первой отладки, смотри что происходит. Поставь лимит — не более 5 откликов в сутки для теста. Постепенно увеличивай.

Бонус: запуск 24/7

Очевидно, что держать домашний ПК включённым 24/7 — так себе идея. Арендуй дешёвый VPS за 300-500 рублей в месяц. Тебе подойдёт конфигурация 2 vCPU, 4 GB RAM, 30 GB SSD. Установи туда систему, разверни Ollama (можно через Docker) и запусти бота. Для модели 7B этого хватит. Если хочешь 70B — нужно 32 GB RAM, бюджетный VPS не потянет. Используй Timeweb или Selectel — у них есть тарифы с GPU, но для 7B хватит и CPU на современном железе.

А что по этике?

Ты автоматизируешь отклики — это нарушение условий пользования HH.ru. Будь готов, что аккаунт могут забанить. Используй для личных целей, не перепродавай услугу. А ещё лучше — сначала найди работу честно, а потом автоматизируй что-то другое. Но если ты дейли сталкиваешься с 100 вакансиями и хочешь ускориться — этот инструмент сэкономит тебе дни.

Кстати, чтобы защитить себя от злонамеренных агентов, почитай статью «Как агенты ИИ взламывают сами себя: разбор кейса Alibaba ROME» — там про honeypot для таких ботов.

Полный код и следующий шаг

Весь проект с Dockerfile, requirements.txt и готовой структурой лежит на GitHub (ссылка в моём профиле). Но собрать по статье — тоже вариант, чтобы понять каждую строчку. Если хочешь углубиться в тему AI-агентов для бизнеса — советую курс от Kaggle, бесплатный, 5 дней: «Бесплатный курс-бестселлер».

И последний совет: не делай агента слишком умным. Чем проще логика — тем меньше багов. Запусти на паре вакансий, посмотри логи, почини селекторы — и только потом отпускай в свободное плавание. Удачи на собеседованиях (теперь они точно будут).

Подписаться на канал