mirror of
https://gitverse.ru/kpa39l/vesti.git
synced 2026-09-29 09:55:03 +00:00
67 lines
4.5 KiB
Markdown
67 lines
4.5 KiB
Markdown
# crawler-queue Specification
|
||
|
||
## Purpose
|
||
Двухфазный пайплайн сбора и обработки новостей: краулеры (фаза 1) собирают
|
||
кандидатов в очередь (`posts` со `status='new'` и неклассифицированные), воркеры
|
||
(фаза 3) разбирают её параллельно (ThreadPoolExecutor). Отдельная страница
|
||
`/crawlers` показывает статус источников и размер очереди.
|
||
|
||
## Requirements
|
||
|
||
### Requirement: Параллельная фаза 3 (воркеры)
|
||
Система MUST предоставлять `crawler/worker.py` с пулом `ThreadPoolExecutor(max_workers=N)`,
|
||
где каждый воркер атомарно забирает пост из очереди (`UPDATE posts SET classified=-1
|
||
WHERE id=? AND classified=0`), классифицирует его (`classify_text`: keywords → Ollama,
|
||
trafilatura для summary-only) и пишет результат (direction/relevance/interest/summary,
|
||
classified=1). При исключении посте MUST возвращаться в очередь (classified=0).
|
||
|
||
#### Scenario: Параллельная обработка очереди
|
||
- **GIVEN** 100 постов со `classified=0`
|
||
- **WHEN** `python -m crawler.worker --workers 8 --limit 200`
|
||
- **THEN** все 100 постов обработаны (classified=1), дублей нет (каждый обработан ровно 1 раз)
|
||
|
||
#### Scenario: Сбой воркера
|
||
- **GIVEN** пост, у которого `classify_text` бросает исключение (Ollama недоступна)
|
||
- **WHEN** воркер обрабатывает пост
|
||
- **THEN** пост возвращается в очередь (classified=0), воркер продолжает работу, запуск не падает
|
||
|
||
### Requirement: Очередь на основе posts (без новой таблицы)
|
||
«Размер очереди» MUST вычисляться из существующей таблицы `posts`:
|
||
`SELECT COUNT(*) FROM posts WHERE classified IS NULL OR classified=0` (pending),
|
||
`classified=-1` (processing), `classified=1 AND fetched_at >= date('now')` (done today).
|
||
Новая таблица для очереди НЕ создаётся — она дублировала бы `posts`.
|
||
|
||
#### Scenario: Размер очереди
|
||
- **GIVEN** в posts 10 новых (classified=0) и 2 в обработке (classified=-1)
|
||
- **WHEN** страница /crawlers запрашивает размер очереди
|
||
- **THEN** pending=10, processing=2, done today=0
|
||
|
||
### Requirement: Страница /crawlers
|
||
Веб MUST предоставлять `GET /crawlers` (за аутентификацией): таблица источников
|
||
(slug, name, crawler, status, last_fetch, last_error за 200 симв., error_count,
|
||
priority; для rss — etag/modified/last_build_date из rss_state) и карточки очереди
|
||
(pending/processing/done). Кнопка «Сбросить dead» (POST /crawlers/reset) MUST
|
||
устанавливать sources.status='alive', error_count=0, last_error=NULL для всех
|
||
источников со status='dead'.
|
||
|
||
#### Scenario: Просмотр статуса
|
||
- **GIVEN** источник lwn (rss, alive) и 15 новых постов в очереди
|
||
- **WHEN** GET /crawlers
|
||
- **THEN** страница показывает lwn с статусом alive, размер очереди 15
|
||
|
||
#### Scenario: Сброс dead-источников
|
||
- **GIVEN** источник со status='dead', error_count=7
|
||
- **WHEN** POST /crawlers/reset
|
||
- **THEN** источник становится alive, error_count=0, last_error=NULL
|
||
|
||
### Requirement: Фаза 1 (сбор) отдельно от фазы 3 (обработка)
|
||
Система MUST предоставлять `crawler/crawl_sources.py` — запуск сбора всех
|
||
включённых источников (telegram_crawler + rss_crawler) без классификации;
|
||
новые посты попадают в очередь (posts, classified=0). Обработка (фаза 3)
|
||
запускается отдельно (`crawler/worker.py`).
|
||
|
||
#### Scenario: Сбор без классификации
|
||
- **GIVEN** включённые источники telegram и rss
|
||
- **WHEN** `python -m crawler.crawl_sources --all`
|
||
- **THEN** новые посты добавлены в posts с classified=0, классификация НЕ запущена
|