Zero-server новостник на Python: запускаем автономного ИИ-редактора в GitHub Actions с нулевым бюджетом

Обычно создание Telegram-канала с автоматическим постингом новостей выглядит одинаково: пишется скрипт на feedparser, арендуется копеечный VPS, настраивается Cron и раз в час в канал падает безликая простыня текста со ссылкой. На выходе получается очередная спам-помойка, от которой юзеры отписываются через день.

Zero-server новостник на Python: запускаем автономного ИИ-редактора в GitHub Actions с нулевым бюджетом

Я решил собрать проект «на максималках», решив три проблемы:

  1. Качество контента. Бот должен не просто парсить всё подряд, а фильтровать мусор, переводить, сжимать длинные статьи до 3–5 емких предложений и подбирать адекватный визуал.
  2. Частота постов. Никакого спама каждый час. Канал должен работать в режиме медиа: редкие точечные посты + структурированный дайджест по утрам и вечерам.
  3. Цена инфраструктуры. Стоимость хостинга, баз данных и API для обработки текста должна быть ровно 0 долларов.

В итоге получился автономный скрипт на Python, который живет прямо в GitHub Actions, сохраняет состояние в сам Git-репозиторий и использует бесплатные лимиты LLM.

Ниже подробно о том, как устроен пайплайн и какие грабли пришлось собрать в процессе.

Архитектурный финт: База данных в Git и Zero-Server

Главная проблема при отказе от постоянного сервера (VPS) — где хранить состояние? Бот должен помнить, какие статьи он уже опубликовал, и на каком источнике прервался прошлый цикл обхода (round-robin).

Я решил не поднимать облачные БД, а использовать файловую систему самого гит-репозитория как персистентное хранилище:

  • Текущие позиции курсора по RSS-лентам пишутся в data/state.json.
  • Хэши и URL отработанных статей складываются в обычный текстовый файл data/seen_urls.txt(защита от дубликатов).
  • Скрипт отрабатывает на виртуальной машине GitHub Actions (используется бесплатный лимит в 2000 минут/месяц).
  • В самом конце успешного воркфлоу встроен шаг, который делает git commit измененных файлов из папки data/ и пушит их обратно в main-ветку.

Для этого в настройках репозитория (Settings → Actions → General → Workflow permissions) достаточно выдать раннеру права на запись (Read and write permissions). Чтобы избежать конфликтов при одновременном доступе, воркфлоу жестко ограничен через параметр concurrency.group: post-news.

Внешний костыль для запуска

Встроенный планировщик GitHub Actions (cron) — вещь крайне ненадежная. Слот времени может опоздать на пару часов или вообще молча пропустить запуск.

Проблема решилась связкой workflow_dispatch и внешнего бесплатного сервиса cron-job.org. Раз в час сторонний сервис отправляет POST-запрос с токеном авторизации на API GitHub, принудительно и вовремя толкая раннер. Один прогон занимает чуть больше минуты, так что лимитов Actions хватает с огромным запасом.

Пайплайн обработки: от RSS до Telegram-поста

Логика запуска разбита на изолированные модули
Логика запуска разбита на изолированные модули

Бот собирает пул из 5 свежих статей из конфига. Чтобы отсеять неинтересный проходняк, эти 5 кандидатов (только заголовки и короткие описания) скармливаются модели на OpenRouter (на бесплатном тарифе отлично заходят модели вроде nvidia/nemotron).

Prompt заставляет модель проанализировать список и вернуть строго один ID статьи, которая потенциально наиболее интересна IT-аудитории. Если нейросеть ломается, выдает мусор или не попадает в регулярное выражение — срабатывает фолбэк (fallback) на стандартную очередь: берется первая рабочая ссылка из списка.

1.Выкачивание «мяса» и сжатие

По ссылке-победителю бот забирает «голый» текст страницы без рекламы и сайдбаров с помощью библиотеки trafilatura. Весь массив текста снова уходит в LLM.

Одним запросом модель решает сразу три задачи:

  1. Понимает язык (если это TechCrunch — переводит, если Хабр — оставляет как есть).
  2. Выдает хлесткий заголовок и выжимку строго в 3–5 предложений.
  3. Формирует короткий лаконичный промпт на английском для генерации визуала.

2. Отказоустойчивый конвейер графики

Пост без картинки в телеге не читают. Но внешние сервисы генерации часто выдают таймауты. Я написал цепочку из четырех фолбэков в main.py:

  • Шаг 1: Бот ищет тег картинки в самом RSS-элементе.
  • Шаг 2: Если там пусто, вытаскивает og:image из мета-тегов страницы во время парсинга (без повторного запроса к сайту).
  • Шаг 3: Если сайт закрыт от парсинга картинок, берется API бесплатного стока Pexels и ищет фото по ключевым словам новости.
  • Шаг 4: Крайний рубеж — отправка сгенерированного ИИ промпта в Pollinations.ai (работает без токенов и ключей).

Если падает вообще всё — пост уходит чистым текстом через publisher.py. Бот качает картинку напрямую в память и шлет в Telegram в виде байт. Если слать просто ссылкой, Telegram на медленных прокси часто обрезает превью, и пост выглядит убого.

Пересборка логики: Почему публикации раз в час — это плохо

Изначально бот молотил каждый час и сразу выплевывал результат в канал. Спустя сутки тестов стало понятно: это порождает жуткий информационный шум. 15-20 постов в день превращают канал в спам-бота, юзеры моментально жмут кнопку Mute.

Я переделал логику распределения контента (main.py):

  1. Дневной режим: Бот запускается каждый час, но лимит на отправку (MAX_ARTICLES_PER_RUN) зажат. В итоге в канал уходит всего 3–4 самых важных и отфильтрованных события за день.
  2. Режим Дайджеста: Ровно в 08:00(по Минску/Москве) бот переключается на ветку digest.py. Он идет в пул мировых медиа (BBC, CNN, Guardian), собирает повестку, агрегирует данные и выдает один структурированный пост-дайджест по 5 главным технологическим и научным категориям. Это закрывает потребность «быть в курсе событий», не забивая ленту уведомлениями.

Что по деньгам?

Стек полностью бесплатный, без необходимости привязывать зарубежные карты для триал-периодов:

  • Хостинг скрипта: GitHub Actions (0$).
  • Триггер таймингов: Cron-job.org (0$).
  • Текст и перевод: OpenRouter Free API (квоты в ~50 тяжелых запросов в сутки хватает впритык на 16 сессий).
  • Медиа: Лимиты Pexels API + Pollinations.ai (0$).

Исходный код

Код полностью открыт, архитектура модульная — при желании пайплайн можно пересобрать под любую другую нишу (крипта, гаджеты, региональные новости), просто поменяв RSS-фиды в config.py.

Буду рад фидбеку в комментариях. Что думаете по поводу идеи использовать коммиты в Git вместо полноценной базы данных для пет-проектов? Какие подводные камни я мог упустить?