Files
vesti/openspec/changes/rss-crawler/proposal.md
T

85 lines
6.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.