openspec: архив 14 завершённых change-ов (веб-фиксы, crawler-queue, own-content-hub, publisher-service); спеки влиты в openspec/specs

This commit is contained in:
kpa39l
2026-09-16 17:01:00 +00:00
parent 584582a48c
commit 771f6a8276
88 changed files with 1632 additions and 3 deletions
+85
View File
@@ -0,0 +1,85 @@
# Proposal: rss-crawler
## Why
В VESTI сейчас есть только telegram_crawler (Telethon, MTProto). RSS-источники —
самый массовый и дешёвый класс источников (по оценке PRD — ~80% объёма новостей):
веб-СМИ, блоги, IT-порталы публикуют RSS/Atom-ленты. Без RSS-краулера они недоступны.
Проблемы, которые решает модуль:
- **нет спроса на RSS вовсе**: в источники (sources.yaml) нельзя добавить ни одного
RSS-канала — краулер просто не умеет их обрабатывать (`get_enabled_sources(crawler="rss")` вернёт пусто);
- **негражданный режим опроса**: без If-None-Match/If-Modified-Since каждый запуск
тянет полные ленты — это лишний трафик для издателей и для нас;
- **мусорные ссылки**: URL из лент часто содержат UTM-метки, якоря, трекеры —
дедупликация по сырому URL даёт дубли одной статьи;
- **обрывки вместо текста**: многие ленты отдают summary вместо полного текста —
без дотягивания полного текста карточки кандидатов бедные;
- **нет состояния/здоровья**: нет etag/modified/last_error/status для RSS-источников.
Для пользователя это значит: можно добавить в VESTI новостные сайты/блоги и получать
их посты в конвейере (классификация → кандидаты → публикация) наравне с Telegram-каналами.
## What Changes
- Новый модуль краулера `crawler/rss_crawler.py` (feedparser + httpx):
- читает `sources.yaml` (crawler: rss), группирует по priority;
- HTTP GET c `If-None-Match`/`If-Modified-Since` из таблицы `rss_state` → 304 = пропуск;
- нормализует URL (UTM-параметры, якоря) → `canonical_url`, дедуп по sha256;
- извлекает полный текст через trafilatura, если запись — обрывок;
- пишет посты в `posts` через существующий `store_posts`-механизм (дедуп по sha256 текста);
- пишет метаданные ленты в `rss_state` (etag/modified/last_error/status, last_build_date);
- ведёт `runs` (source_id, task, trigger) — как telegram_crawler;
- CLI: `python -m crawler.rss_crawler --all | --direction | --source <slug>`.
- Новая таблица `rss_state` (etag, modified, last_error, status, last_build_date) —
аналог `tg_state` для RSS-лент.
- `requirements.txt`: + trafilatura (извлечение полного текста).
- `sources.yaml`: первый реальный RSS-источник для проверки — LWN (lwn.net) —
и один закомментированный пример (opennet.ru).
- `.env.example`: + `RSS_USER_AGENT` (информативный User-Agent).
- Cron (Hermes): `vesti-rss-crawler` (каждые 15 мин, P0) — тихий, no_agent, как telegram.
### Не меняется
- Дедуп и схема `posts` — как у telegram_crawler (sha256 текста; url — дополнительный ключ).
- Классификатор, веб, publisher — не затрагиваются (RSS-посты идут в тот же `posts`).
- `tg_state` — остаётся для Telegram; RSS использует `rss_state`.
- Никаких автопубликаций: посты попадают в кандидаты, подтверждение — человеком.
## Capabilities
### New Capabilities
- `rss-crawler`: Синхронный краулер RSS/Atom-лент: реестр из sources.yaml,
условные GET (etag/modified), нормализация URL, извлечение полного текста
(trafilatura), состояние и здоровье ленты в БД, интеграция с runs/posts.
### Modified Capabilities
- `news-store` (таблица `posts`): без изменений схемы; RSS-посты используют её как есть.
- `sources.yaml`: новые источники с `crawler: rss`.
- `vesti-web`: без изменений (RSS-посты автоматически видны как кандидаты).
## Impact
- Файлы:
- новый: `crawler/rss_crawler.py` (модуль-краулер);
- изменён: `db/schema.sql` (+CREATE TABLE rss_state), `requirements.txt`
(+ trafilatura), `sources/sources.yaml` (+lwn, пример opennet), `.env.example`
(+RSS_USER_AGENT), `config.py` (+RSS_USER_AGENT, +RSS_*);
- доки: STATUS.md, TODO.md, WALKTHROUGH.md, AGENT.MD (команда запуска).
- Данные: миграция — `CREATE TABLE IF NOT EXISTS`; существующие данные не трогаются.
- Секреты: не требует (RSS публичные). User-Agent — из .env.
- Сеть: исходящие HTTP-запросы к сайтам издателей (только к лентам, учтиво:
условные GET, рейт-лимит 1–2 с между источниками).
- Rollback: модуль не вызывается cron — просто не добавлять источники/не запускать.
## Risks
- Издатели режут по User-Agent / отдают капчу — лечится `RSS_USER_AGENT`
(честный, с контактом) и `enabled: false` для проблемных.
- Ленты с кривой датой (`published_parsed` None) — ставим `fetched_at`, не падаем.
- summary-only ленты — trafilatura может не найти полный текст; тогда сохраняем summary,
пост всё равно попадает в конвейер (пометка в content_type/text).
- Огромные ленты (мега-фиды): ограничение — первые N записей, остальные догонятся
следующими запусками (инкрементально), как в telegram_crawler.
- Троттлинг издателя: проставляем last_error и status='dead' после N=5 ошибок подряд
(правило фид-здоровья), watchdog молчит до перехода alive↔dead.