mirror of
https://gitverse.ru/kpa39l/vesti.git
synced 2026-09-29 18:05:03 +00:00
openspec: архив 14 завершённых change-ов (веб-фиксы, crawler-queue, own-content-hub, publisher-service); спеки влиты в openspec/specs
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
# Spec: rss-crawler
|
||||
|
||||
## Purpose
|
||||
|
||||
Краулер RSS/Atom-лент с реестра источников (sources.yaml, crawler: rss): условные
|
||||
GET (etag/modified), нормализация URL, извлечение полного текста (trafilatura),
|
||||
состояние и здоровье ленты в БД (rss_state), интеграция с posts/runs. RSS-посты
|
||||
попадают в общий конвейер (классификация → кандидаты → публикация) наравне с
|
||||
Telegram-постами.
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Чтение RSS-источников из реестра
|
||||
Система MUST поддерживать источники с `crawler: rss` в sources.yaml и запускать
|
||||
их краулинг через `python -m crawler.rss_crawler` (фильтры: --all / --direction /
|
||||
--source <slug>). Каждый запуск источника MUST записываться в таблицу `runs`
|
||||
(start_run/finish_run) как задача `rss:<slug>`.
|
||||
|
||||
#### Scenario: Список RSS-источников
|
||||
- **GIVEN** sources.yaml содержит источник `lwn` с `crawler: rss`, `enabled: true`
|
||||
- **WHEN** выполняется `python -m crawler.rss_crawler --all`
|
||||
- **THEN** краулер обрабатывает источник lwn, в `runs` появляется запись задачи `rss:lwn`
|
||||
|
||||
### Requirement: Условные GET (etag/modified)
|
||||
Краулер MUST отправлять `If-None-Match` (etag) и `If-Modified-Since` (modified) из
|
||||
`rss_state` при запросе ленты. При ответе 304 краулер MUST НЕ парсить ленту и НЕ
|
||||
добавлять посты (запуск завершается ok с 0 fetched/0 new). Полученные etag/modified
|
||||
из ответа MUST сохраняться в `rss_state`.
|
||||
|
||||
#### Scenario: Неизменённая лента
|
||||
- **GIVEN** rss_state для lwn содержит etag "xyz" и лента не менялась
|
||||
- **WHEN** запускается краулер
|
||||
- **THEN** сервер отвечает 304, новых постов нет, запуск в runs имеет status ok
|
||||
|
||||
### Requirement: Нормализация URL и дедупликация
|
||||
Краулер MUST нормализовать URL записи перед использованием в качестве ключа:
|
||||
удалять UTM-параметры (utm_*), fbclid/gclid/ref и якоря (#). Дедупликация MUST
|
||||
использовать sha256 от текста записи (как в telegram_crawler); повторная вставка
|
||||
той же статьи MUST не создавать второй строки в `posts`.
|
||||
|
||||
#### Scenario: Дубликат: та же статья, разные URL
|
||||
- **GIVEN** статья с URL вида `https://site/a?utm_source=rss&utm_medium=feed#top`
|
||||
- **WHEN** краулер обрабатывает запись
|
||||
- **THEN** canonical_url = `https://site/a` (без утм и якоря), sha256 уникален,
|
||||
вторая идентичная запись из другой ленты не создаёт дубль
|
||||
|
||||
### Requirement: Полный текст (trafilatura)
|
||||
Краулер MUST извлекать полный текст страницы через trafilatura для записей,
|
||||
у которых нет полного текста в ленте (только summary). Сбой извлечения MUST НЕ
|
||||
ронять источник: в `posts.text` сохраняется доступный текст (summary), запуск
|
||||
продолжается.
|
||||
|
||||
#### Scenario: Summary-only лента
|
||||
- **GIVEN** лента отдаёт краткое описание (summary) без полного контента
|
||||
- **WHEN** краулер обрабатывает запись
|
||||
- **THEN** текст записи дотягивается trafilatura; при неудаче сохраняется summary,
|
||||
запуск завершается ok, ошибка логгируется
|
||||
|
||||
### Requirement: Здоровье и состояние ленты (rss_state)
|
||||
Система MUST хранить состояние ленты в таблице `rss_state` (slug PK, etag, modified,
|
||||
last_build_date, last_error, error_count, status). После успешного прогона
|
||||
`status='alive'`, error_count=0, last_error=NULL. При ошибке error_count
|
||||
инкрементируется; при error_count>=5 подряд `status='dead'` (фид-здоровье),
|
||||
sources.status синхронизируется.
|
||||
|
||||
#### Scenario: Лента постоянно падает
|
||||
- **GIVEN** лента возвращает 500/таймаут 5 раз подряд
|
||||
- **WHEN** краулер завершает 5-й неудачный запуск
|
||||
- **THEN** rss_state.status='dead', sources.status='dead', last_error непуст;
|
||||
watchdog (cron) уведомит при переходе alive→dead
|
||||
|
||||
### Requirement: Запуск по приоритетам и CLI
|
||||
Краулер MUST запускаться из cron с каденцией по приоритету источника (P0 раз в
|
||||
15 мин, P1 раз в час, P2 раз в 4 часа). Ручной запуск MUST поддерживать
|
||||
`--dry-run` (печать записей без записи в БД).
|
||||
|
||||
#### Scenario: Dry-run
|
||||
- **GIVEN** выполняется `python -m crawler.rss_crawler --source lwn --dry-run`
|
||||
- **THEN** краулер печатает записи ленты, но НЕ пишет в posts/rss_state/runs;
|
||||
БД остаётся неизменной
|
||||
|
||||
## NOT Requirements
|
||||
|
||||
- Не реализуем инкрементальный обход истории (как telegram_crawler по last_post_id):
|
||||
RSS-ленты — это «последние N записей», состояние — через etag/modified.
|
||||
- Не пишем полный контент в отдельную таблицу/колонку (text в posts уже достаточен;
|
||||
bundles создаёт веб при approve).
|
||||
- Не скачиваем медиа из RSS (картинки-иконки ленты) — контент текстовый.
|
||||
- Не делаем автопостинг: RSS-посты проходят тот же путь подтверждения человеком.
|
||||
- Не затрагиваем tg_state/telegram_crawler (отдельный механизм для Telegram).
|
||||
Reference in New Issue
Block a user