August 19

Как я написал бота, который ищет мне вакансии, пока я сплю

Есть такая штука в жизни 3D-художника и моушен-дизайнера: хорошие заказы разлетаются за пятнадцать минут после публикации. Пока ты долистываешь ленту в метро, вакансия уже занята кем-то, кто сидел в чате и обновлял канал каждые полминуты. Раньше я и сам так делал — держал открытыми штук двадцать телеграм-каналов с вакансиями и время от времени листал их вручную. Занятие такое себе: скучно, отнимает время и всё равно что-то пропускаешь.

В какой-то момент я решил, что раз я умею писать код, глупо продолжать заниматься этим руками. Так появился скрипт, который я в шутку назвал stalker.py — он действительно «выслеживает» вакансии по каналам, только не человека, а посты нужного профиля.

Что он вообще делает

Логика простая, если разложить по шагам. Скрипт подключается к моему телеграм-аккаунту через Pyrogram — это библиотека, которая позволяет работать с Telegram API как с обычным пользователем, а не через бота. Разница важна: боту нельзя просто так читать историю каналов, на которые он не подписан от имени владельца, а через юзер-аккаунт — можно, если ты сам туда вступил.

Дальше он проходит по списку каналов из файла channels.txt и смотрит последние сообщения. Есть два режима глубины поиска: если скрипт запустился утром, между 11:40 и 11:59, он сканирует последние 15 часов — то есть подчищает всё, что накопилось за ночь. В остальное время смотрит только последний час. Это запускается через cron или планировщик — сам скрипт не крутится в фоне постоянно, а стартует по расписанию, делает свою работу и завершается.

Фильтрация в два прохода

Самое интересное — как отсеивается мусор. Каналов с вакансиями для дизайнеров много, но процентов восемьдесят контента там не имеет ко мне отношения: UI/UX, инфографика, обложки для ютуба, таргетологи, резюме других людей, которые ищут работу в том же чате. Если пропускать это через нейросеть каждый раз, будет и медленно, и накладно.

Поэтому первым делом идёт грубый фильтр по ключевым словам. Список стоп-слов (GARBAGE_KEYWORDS) отсекает явно нерелевантные посты сразу, без всякого ИИ — просто проверка вхождения подстроки. Отдельно отсекаются резюме — по фразам вроде «меня зовут» или «ищу проекты», потому что это не вакансия, а чьё-то самопродвижение. И только если пост прошёл эти фильтры и в нём встретилось хоть одно слово из списка нужных тем (3D, motion, Cinema 4D, Houdini, Redshift и так далее), он попадает на следующий этап.

Второй этап — локальная модель. Скрипт стучится в Ollama, который крутится на том же компьютере на порту 11434, и просит модель gpt-oss:20b вынести финальный вердикт: подходит вакансия под профиль 3D/motion-дизайнера или нет. Тут смысл в том, что ключевые слова — штука грубая, они ловят и ложные срабатывания («ищем UI/UX дизайнера с опытом в 3D-иконках» тоже пройдёт по словам). Модель уже смотрит на контекст и отсеивает то, что формально совпало, но по сути не подходит.

Важный момент — почему это локальная модель, а не запрос к условному GPT через API. Во-первых, дешевле: постов проходит через фильтр по ключевым словам не так много, но за месяц набегает прилично, и гонять их все через платный API смысла нет. Во-вторых, скорость и приватность — не нужно ждать ответа от внешнего сервера и не нужно отправлять куда-то содержимое чужих каналов.

Что происходит с найденным

Если модель говорит «да», скрипт формирует короткую карточку — заголовок вакансии, название канала, ссылка на пост (или пометка «Private Link», если у канала нет юзернейма) и обрезанное описание — и отправляет всё это самому себе в Telegram, в «Избранное». Получается что-то вроде личной ленты уже отфильтрованных вакансий, куда можно просто иногда заглядывать вместо того, чтобы шерстить два десятка каналов.

Чтобы не присылать одно и то же дважды, есть простейшая база данных — обычный текстовый файл sent_tasks.txt, куда пишется уникальный идентификатор сообщения (айди чата плюс айди поста). Перед обработкой скрипт проверяет, нет ли такого идентификатора уже в файле. Никакой базы данных в полном смысле слова тут не нужно — задача маленькая, и текстовый файл справляется без проблем.

Почему так, а не иначе

Можно было бы сделать то же самое через официальный Bot API, но тогда пришлось бы добавлять бота в каждый канал отдельно, а часть каналов админы просто не пустят. Через юзер-сессию эта проблема снимается — если я сам состою в канале, скрипт может читать историю переписки.

Получилось не идеально — иногда модель ошибается, иногда список ключевых слов приходится донастраивать, когда появляется новый тип мусора в лентах. Но по сравнению с ручным листанием двадцати каналов каждый день это уже совсем другой уровень комфорта: просыпаешься, а в «Избранном» уже лежит десяток отфильтрованных вариантов, и можно сразу решать, куда откликаться.