Инструкция, как поднять такой же под свою профессию за вечер.

Летом я искала работу продактом. Подписалась на десяток телеграм‑каналов с вакансиями и каждое утро их листала. Быстро обнаружились проблемы:

  • Каналов много, в день в них 50+ постов, из которых мне подходят два‑три.

  • Одна и та же вакансия висит в четырёх каналах. Каждый раз её перечитываешь, пока не вспомнишь, что уже видела.

  • Фильтров нет: ни по грейду, ни по городу, ни по удалёнке, ни по ML. Всё приходится искать глазами.

  • Половина постов вообще не вакансии, а курсы, разборы резюме и «ищу работу».

Платные агрегаторы существуют, но платить за то, чтобы читать бесплатные каналы, мне не хотелось. Хотелось одну страницу, где всё лежит с фильтрами, без дублей и обновляется само.

В общем, накодила и навайбкодила. Получился вот такой агрегатор.

Потом один человек написал мне в LinkedIn с просьбой добавить пару вещей. Я подумала: «Фигасе, пользуются, что ли?» И решила адаптировать проект, чтобы другие тоже могли быстро поднять его у себя. Вынесла настройки профессии в YAML‑файл: теперь можно собирать вакансии не только для продактов.

С работой сейчас непросто. Может, кому‑то будет полезно форкнуть.

Что получилось

Одна статичная страница, которую скрипт генерирует раз в день:

  • Все каналы в одном месте. Скрипт читает папку в вашем Telegram. Добавили канал в папку, и при следующем запуске он попадёт в дайджест. Отдельного списка каналов в конфиге нет.

  • Склейка дублей. Вакансия, которую перепостили в четыре канала, показывается один раз, с пометкой «ещё в 3 каналах» и ссылками на источники.

  • Отсев лишнего. Скрипт отфильтровывает курсы, вебинары, «ищу работу», рекламу и митапы.

  • Поля из текста. Компания, грейд, город, удалёнка, зарплата, если она указана, и теги: ML/AI для продактов, React, Vue и TypeScript для фронтенда.

  • Фильтры и поиск. По периоду (24 часа, неделя, месяц, архив), грейду, локации (Москва, Питер, регионы, за рубежом, удалёнка) и тегам. Плюс поиск по тексту.

  • Полный пост в один клик. Со всеми ссылками из него, включая ссылки, спрятанные в тексте.

  • Разбор подборок. Если канал публикует «5 вакансий недели» одним постом, каждая становится отдельной карточкой.

У меня стоимость работы агрегатора нулевая: скрипт запускается в GitHub Actions, а страницу отдаёт GitHub Pages. Платный API нейросети не нужен. Из ключей нужны только ключи Telegram API.

Как это работает

Обработка состоит из шести шагов:

fetch_tg → parse → enrich → deduplicate → state → render
  1. fetch_tg через Telethon читает все каналы из папки и забирает посты с прошлого запуска.

  2. parse быстро проверяет регулярным выражением, упоминается ли в посте нужная роль.

  3. enrich по правилам определяет, вакансия ли это. Ищет слова вроде «ищем», «требования», «откликнуться» и отсеивает посты с признаками курсов, вебинаров или поиска работы. Затем извлекает заголовок, компанию, грейд, город, зарплату и теги. Ничего не генерируется.

  4. deduplicate сравнивает тексты через difflib.SequenceMatcher и склеивает похожие на 90% и больше.

  5. state помечает новое, отправляет старое в архив и удаляет то, чему больше 90 дней.

  6. render с помощью Jinja2 собирает страницу index.html с данными внутри. Фильтры и поиск работают в браузере на чистом JavaScript, отдельный сервер для страницы не нужен.

Почему без нейросети

Первая версия использовала LLM на третьем шаге: модель классифицировала пост и возвращала JSON с полями. Работало хорошо. А потом ключ API протух, скрипт начал падать с ошибкой 401, и дайджест неделю не обновлялся, пока я не заметила. Плюс это была единственная платная часть схемы.

Я заменила модель правилами и регулярками. Пара нерелевантных постов в день тоже проскакивает. Зато нет зависимости от API нейросети и расходов на него, а ещё понятно, почему карточка выглядит так, а не иначе. Для моего личного инструмента это подходящий размен. Если вам нужна модель, шаг enrich изолирован: её можно вернуть туда.

Что понадобится

  • Git и Python 3.11 для запуска проекта на компьютере. Python можно установить с официального сайта.

  • Аккаунты на GitHub и в Telegram.

  • Готовность открыть терминал, выполнить команды и настроить репозиторий по инструкции.

  • Для своей профессии нужно будет немного подправить регулярки.

Если слово «регулярка» пугает, речь про выражения вида \bfrontend\b|фронтенд\w*. Их можно попросить написать ChatGPT или Claude по описанию: «Мне нужны паттерны для слова „тестировщик“ во всех падежах».

Для базового запуска писать код не потребуется. Я сама не разработчик.

Инструкция

1. Форк и установка

Откройте репозиторий проекта и нажмите Fork. Затем скачайте свою копию. Вместо <ваш-логин> подставьте логин на GitHub:

git clone https://github.com/<ваш-логин>/tg\_digest.git
cd tg_digest

macOS и Linux:

python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env

Windows, командная строка cmd:

python -m venv .venv
.venv\Scripts\activate
pip install -r requirements.txt
copy .env.example .env

Дальше команды запуска скриптов одинаковые для всех систем: используем python внутри активированного окружения.

2. Ключи Telegram

Зайдите на my.telegram.org, откройте API development tools и создайте приложение. Название можно указать любое.

Скопируйте полученные api_id и api_hash в файл .env. Значения ниже приведены только для примера:

TG_API_ID=12345678
TG_API_HASH=0123456789abcdef0123456789abcdef

Затем один раз авторизуйтесь, чтобы получить сессию:

python scripts/generate_session.py

Скрипт спросит номер телефона, код из Telegram и пароль двухфакторной аутентификации, если она включена. В ответ напечатает длинную строку. Вставьте её в .env как значение TG_SESSION_B64.

Строка сессии даёт доступ к вашему аккаунту Telegram. Никому её не показывайте и не коммитьте. Файл .env уже добавлен в .gitignore.

Если возникнут проблемы с получением кода, я отдельно описала этот процесс в статье про авторизацию в Telegram.

3. Папка с каналами

В Telegram откройте Настройки → Папки, создайте папку vacancy и добавьте в неё каналы с вакансиями.

Всё. Если хотите использовать другое название, поменяйте значение tg_folder_name в файле config/sources.yml.

4. Первый запуск

python scripts/main.py

Первый запуск заберёт посты за последние 30 дней. Обычно это занимает минуту‑две.

Откройте получившийся файл index.html в браузере. В macOS это можно сделать командой:

open index.html

В Windows, в командной строке cmd:

start index.html

Посмотрите на карточки. Если в них попал мусор, лучше подправить правила до публикации. Ниже разберу, как это сделать в профиле профессии.

5. Публикация на GitHub Pages

Чтобы страница обновлялась сама, нужно передать GitHub те же три значения из .env:

  1. В репозитории откройте Settings → Secrets and variables → Actions → New repository secret. Создайте три секрета: TG_API_ID, TG_API_HASH и TG_SESSION_B64.

  2. Откройте Actions → digest → Run workflow. Первый запуск в облаке создаст ветку gh-pages с готовой страницей.

  3. В Settings → Pages выберите публикацию из ветки gh-pages, папка /.

После публикации страница появится по адресу https://<ваш-логин>.github.io/tg_digest/. Дальше workflow будет запускаться по расписанию каждое утро.

Одна оговорка: запуск по расписанию может задерживаться. У меня сборка стоит на 06:23 МСК, а фактически иногда проходит около десяти утра. Если нужно обновить страницу прямо сейчас, можно запустить workflow вручную кнопкой Run workflow.

6. Настройка под свою профессию

Скрипт берёт настройки профессии из файла профиля, например profiles/product.yml или profiles/frontend.yml. Чтобы сделать дайджест для себя, нужно ответить на четыре вопроса. Разберу на примере QA‑инженера.

Как называют вашу роль?

Пост берётся в работу, если в нём встретилось одно из нужных слов. Перечислите варианты, которые видели в вакансиях: по‑русски, по‑английски, на профессиональном сленге.

roles:
  patterns:
    - '\bqa\b'
    - '\bтестировщик\w*'
    - '\btest automation\b'

Здесь \b означает границу слова, а \w* позволяет захватить окончание: «тестировщик», «тестировщика», «тестировщику». Если хотите добавить ещё одно слово, можно попросить ChatGPT или Claude: «Напиши регулярку для слова „автотестер“ во всех падежах».

Какие слова похожи на вашу роль, но не определяют её однозначно?

Слово «тест» встречается и в «A/B‑тесте» у продактов, и в «тестовом задании». По нему можно взять пост на рассмотрение, но нельзя уверенно решить, что вакансия про QA. Такие слова идут в loose внутри раздела roles:

roles:
  loose:
    - '\bтест\w*'

Какие соседние роли нужно исключать?

Это самый важный список. Пост про разработчика может упоминать QA в требованиях: например, «умеете работать с тестировщиками». По первому фильтру он пройдёт.

Поэтому после определения заголовка карточки скрипт проверяет: если в заголовке названа роль из exclude и нет вашей, карточка отбрасывается.

roles:
  exclude:
    - 'разработчик|developer|аналитик|analyst'
    - 'продакт|product manager|проджект|project manager'

От этого списка сильно зависит точность дайджеста. После первого запуска пролистайте карточки и добавьте сюда лишние роли, которые проскочили.

Как узнать вашу вакансию внутри подборки?

Некоторые каналы публикуют по пять вакансий одним постом. Скрипт разбивает его на отдельные карточки и по короткому заголовку каждой решает, подходит ли она вам. Для этого используется hint:

roles:
  hint:
    - 'qa|тест'

Все четыре настройки находятся в одном разделе roles. Полный файл ниже показывает, как объединить эти фрагменты.

Необязательные настройки: грейды и теги

Слова senior, junior, “стажёр” и lead скрипт уже знает. Если у вашей роли есть свои названия уровней, добавьте их:

grades_extra:
  Head: 'head of qa|руководител\w+ отдела тестирования'
  Lead: 'qa lead|лид тестирования'

Теги позволяют фильтровать вакансии по дополнительным признакам. Каждый тег появляется на карточке и в фильтрах. У продактов это ML/AI, у фронтендеров React, Vue и TypeScript. Для QA можно добавить «Автотесты»:

tags:
  - key: automation  # Имя поля, латиницей
    label: Автотесты  # Надпись на карточке и в фильтрах
    pattern: 'автотест\w*|automation|selenium|playwright'

Заголовок и подзаголовок страницы тоже берутся из профиля:

site:
  title: QA jobs
  tagline: from Telegram

Полный пример профиля

Создайте файл profiles/qa.yml:

site:
  title: QA jobs
  tagline: from Telegram

roles:
  patterns:
    - '\bqa\b'
    - '\bтестировщик\w*'
    - '\btest automation\b'
  loose:
    - '\bтест\w*'
  hint:
    - 'qa|тест'
  exclude:
    - 'разработчик|developer|аналитик|analyst'
    - 'продакт|product manager|проджект|project manager'

grades_extra:
  Head: 'head of qa|руководител\w+ отдела тестирования'
  Lead: 'qa lead|лид тестирования'

tags:
  - key: automation
    label: Автотесты
    pattern: 'автотест\w*|automation|selenium|playwright'

В YAML важны отступы. При копировании сохраняйте вложенность и используйте пробелы вместо табуляции.

В config/sources.yml укажите:

profile: qa

Затем снова запустите скрипт:

python scripts/main.py

QA‑профиль я написала для примера и на реальных каналах не проверяла. Его стоит воспринимать как заготовку. В моей работе с профилями обычно хватает двух‑трёх итераций: запустила, посмотрела карточки, дописала exclude.

7. Удаление повторяющихся строк

Ещё одна настройка, которая понадобится не всем. Иногда канал вставляет в каждый пост одну и ту же строку: навигацию по своим каналам или рекламу бота. Она может попадать в заголовки карточек.

Для этого в config/sources.yml есть ignore_lines. Скрипт пропускает строки, которые соответствуют этим регуляркам:

ignore_lines:
  - '^Вакансии\s*│'
  - 'первый ai ассистент'

Если не хочется разбираться с регулярками

Можно не упарываться: открыть свою копию репозитория в Claude Code, дать ему эту статью и описать, для какой профессии нужен дайджест. Попросить создать профиль, а потом проверить результат на своих каналах.

Самой замороченной частью может оказаться авторизация в Telegram. Если застрянете на получении кода, вот моя отдельная инструкция.

Код лежит на GitHub, работающий дайджест для продактов можно посмотреть здесь. Если пригодится, форкайте и настраивайте под себя.