update readme
This commit is contained in:
512
README.md
512
README.md
@@ -1,276 +1,281 @@
|
||||
# DUBIZZLE Scraper
|
||||
# Dubizzle scraper
|
||||
|
||||
Парсер аукционных автомобилей с [dubizzle.com](https://www.dubizzle.com). Ходит по листингу, собирает карточки машин, вытаскивает данные из DOM и перехваченных XHR-ответов, складывает всё в PostgreSQL. Работает через Playwright, FastAPI, Celery и Docker.
|
||||
Сервис для сбора объявлений с `dubizzle.com`.
|
||||
|
||||
## Как устроен сайт и его защита
|
||||
Основной сценарий работы:
|
||||
|
||||
У DUBIZZLE стоит **Imperva Incapsula** — внешний WAF и anti-bot.
|
||||
- discovery списка машин через `Algolia`
|
||||
- при необходимости fallback на сайт
|
||||
- нормализация полей автомобиля
|
||||
- upsert в PostgreSQL
|
||||
- периодический запуск через `Celery Beat`
|
||||
|
||||
- **Anti-bot** — headless Chromium без прокси часто режется.
|
||||
- **Динамическая подгрузка** — часть данных приходит через XHR (`/Search`, `/VehicleDetail`), часть остаётся в HTML.
|
||||
- **Cookie consent** — при первом заходе показывают баннер.
|
||||
## Что делает проект
|
||||
|
||||
Поэтому в проекте используются Playwright, паузы между действиями, прокси и сохранение браузерного состояния.
|
||||
Проект:
|
||||
|
||||
## Локальный запуск (без Docker)
|
||||
- собирает все доступные машины из каталога `Dubizzle`
|
||||
- использует `Algolia` как основной источник discovery
|
||||
- обходит лимит одного запроса через сегментацию каталога
|
||||
- сохраняет автомобили и изображения в PostgreSQL
|
||||
- отдает API для просмотра данных и запуска задач
|
||||
- поддерживает hourly/full scan через `Celery`
|
||||
|
||||
### Требования
|
||||
## Текущая архитектура
|
||||
|
||||
- **Python 3.11+**
|
||||
- **Git**
|
||||
Сейчас проект настроен под `Dubizzle`.
|
||||
|
||||
Docker **не нужен**. Данные хранятся в SQLite-файле `dubizzle_scraper.db` в корне проекта.
|
||||
Ключевые особенности:
|
||||
|
||||
### Быстрый старт
|
||||
- пакет проекта: `dubizzle_scraper`
|
||||
- режим discovery по умолчанию: `algolia`
|
||||
- full scan включен по умолчанию
|
||||
- авто-сегментация: `DUBIZZLE_LISTING_SEGMENTS=auto`
|
||||
- параллельный запуск сегментов: `CELERY_PARALLEL_SEGMENTS=true`
|
||||
|
||||
```bash
|
||||
git clone <repo-url>
|
||||
cd dubizzle_scraper_project
|
||||
pip install -e .
|
||||
playwright install firefox
|
||||
dubizzle init-db
|
||||
```
|
||||
### Как собирается весь каталог
|
||||
|
||||
`dubizzle init-db` актуален для SQLite/локального CLI-режима. Для PostgreSQL используйте Alembic-миграции (`alembic upgrade head` или сервис `migrate` в Docker).
|
||||
Один широкий запрос в `Algolia` упирается примерно в лимит $10{,}000$ доступных результатов. Поэтому проект использует сегментацию по годам.
|
||||
|
||||
Готовый `.env` уже в репозитории и настроен на SQLite ничего менять не нужно.
|
||||
Авто-сегменты (`auto`) сейчас разбивают каталог на 8 диапазонов по году:
|
||||
|
||||
### Запуск парсера
|
||||
- `1900-2012`
|
||||
- `2013-2015`
|
||||
- `2016-2017`
|
||||
- `2018-2019`
|
||||
- `2020-2021`
|
||||
- `2022-2023`
|
||||
- `2024-2025`
|
||||
- `2026-2027`
|
||||
|
||||
**Скрапинг одной машины:**
|
||||
Если сегмент все равно слишком большой, discovery дополнительно режет его по `id`-диапазонам.
|
||||
|
||||
```bash
|
||||
dubizzle sync-vehicle "https://www.dubizzle.com/VehicleDetail/45089484~US"
|
||||
```
|
||||
## Стек
|
||||
|
||||
**Сбор листинга + скрапинг (например Toyota, 10 штук):**
|
||||
- `Python 3.11+`
|
||||
- `Playwright`
|
||||
- `FastAPI`
|
||||
- `Celery`
|
||||
- `Redis`
|
||||
- `PostgreSQL`
|
||||
- `SQLAlchemy`
|
||||
- `Alembic`
|
||||
- `Docker Compose`
|
||||
|
||||
```bash
|
||||
dubizzle sync-listing --make Toyota --limit 10
|
||||
```
|
||||
## Быстрый старт через Docker
|
||||
|
||||
**Только ссылки с листинга (без скрапинга):**
|
||||
Это основной рекомендуемый способ запуска.
|
||||
|
||||
```bash
|
||||
dubizzle collect-listing --make Toyota
|
||||
```
|
||||
|
||||
**С видимым браузером (для отладки):**
|
||||
|
||||
```bash
|
||||
dubizzle --headless false sync-vehicle "https://www.dubizzle.com/VehicleDetail/45089484~US"
|
||||
```
|
||||
|
||||
**Проверка, что записалось в базу (`dubizzle_scraper.db`):**
|
||||
|
||||
```bash
|
||||
python -c "import sqlite3; c=sqlite3.connect('dubizzle_scraper.db'); q=c.cursor(); print('cars:', q.execute('select count(*) from cars').fetchone()[0]); print('images:', q.execute('select count(*) from images').fetchone()[0]); rows=q.execute('select id, brand, model, year, origin_id from cars order by id desc limit 10').fetchall(); [print(r) for r in rows]"
|
||||
```
|
||||
|
||||
Результаты сохраняются в `artifacts/json/` и в БД `dubizzle_scraper.db`.
|
||||
|
||||
### Полный стек (API + Worker + Beat)
|
||||
|
||||
Для API и автоматического сбора нужны **Redis** и **PostgreSQL**. В `.env` раскомментируй строки Redis/Celery и замени БД на PostgreSQL:
|
||||
|
||||
```env
|
||||
DUBIZZLE_DATABASE_URL=postgresql+psycopg2://dubizzle:dubizzle@localhost:5432/dubizzle_scraper
|
||||
DUBIZZLE_REDIS_URL=redis://localhost:6379/0
|
||||
CELERY_BROKER_URL=redis://localhost:6379/0
|
||||
CELERY_RESULT_BACKEND=redis://localhost:6379/0
|
||||
```
|
||||
|
||||
Применить миграции и запустить в трёх терминалах:
|
||||
|
||||
```bash
|
||||
alembic upgrade head
|
||||
|
||||
# Терминал 1 — API
|
||||
uvicorn dubizzle_scraper.api.app:app --reload --port 8000
|
||||
|
||||
# Терминал 2 — Celery Worker
|
||||
celery -A dubizzle_scraper.worker.celery_app worker --loglevel=info --concurrency=1 --pool=solo -Q scraping
|
||||
|
||||
# Терминал 3 — Celery Beat (периодический запуск)
|
||||
celery -A dubizzle_scraper.worker.celery_app beat --loglevel=info
|
||||
```
|
||||
|
||||
API: `http://localhost:8000` · Swagger: `http://localhost:8000/docs`
|
||||
|
||||
---
|
||||
|
||||
## Запуск через Docker (все сервисы в контейнерах)
|
||||
### 1. Подготовить окружение
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
Если нужно, отредактируй `.env`.
|
||||
|
||||
### 2. Запустить стек
|
||||
|
||||
```bash
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
Будут запущены сервисы `postgres`, `redis`, `migrate`, `api`, `worker`, `beat`. API доступен на `http://localhost:8000`.
|
||||
Поднимутся сервисы:
|
||||
|
||||
В Docker-образ дополнительно проверяется Python-синтаксис на этапе сборки (`python -m compileall -q dubizzle_scraper`), чтобы не выкатывать битый код.
|
||||
- `postgres`
|
||||
- `redis`
|
||||
- `migrate`
|
||||
- `api`
|
||||
- `worker`
|
||||
- `beat`
|
||||
|
||||
`entrypoint.sh` поднимает SOCKS5→HTTP proxy bridge (если задан `SOCKS5_PROXY_HOST`) и запускает `Xvfb` только для `worker` и CLI scraping-команд.
|
||||
|
||||
По умолчанию `beat` запускает сбор листинга **раз в 1 час** (`CELERY_BEAT_SYNC_INTERVAL_MINUTES=60`).
|
||||
В текущей конфигурации hourly-цикл делает **полный обход всех машин через Algolia с серверной сегментацией**:
|
||||
|
||||
- `DUBIZZLE_DISCOVERY_MODE=algolia`
|
||||
- `DUBIZZLE_LISTING_SEGMENTS=auto`
|
||||
- `DUBIZZLE_ALWAYS_FULL_SCAN=true`
|
||||
- `CELERY_PARALLEL_SEGMENTS=true`
|
||||
|
||||
Это означает, что каждый beat-тик разбивает каталог на year-only сегменты и проходит их через Algolia, чтобы обойти лимит одного общего запроса и собрать весь каталог, а не только первые ~10k записей.
|
||||
|
||||
Swagger-документация: `http://localhost:8000/docs`
|
||||
### 3. Проверить статус
|
||||
|
||||
```bash
|
||||
docker compose ps
|
||||
```
|
||||
|
||||
- `api` имеет healthcheck на `GET /health`
|
||||
- `worker` имеет healthcheck через `celery inspect ping`
|
||||
- `beat` имеет healthcheck по файлу `celerybeat-schedule`
|
||||
API будет доступен по адресу:
|
||||
|
||||
### Разовый сбор через Docker с сохранением результата на хосте
|
||||
- `http://localhost:18000`
|
||||
- Swagger: `http://localhost:18000/docs`
|
||||
|
||||
Для ручного прогона листинга через `Algolia` без запуска `beat` используй отдельный one-shot сервис `sync`:
|
||||
> Порт пробрасывается как `${DUBIZZLE_API_HOST_PORT:-18000}:8000`.
|
||||
|
||||
## Полный сброс и чистый старт
|
||||
|
||||
Если нужен запуск с нуля с чистой БД и чистым Redis:
|
||||
|
||||
```bash
|
||||
docker compose down -v --remove-orphans
|
||||
docker compose up -d --build
|
||||
```
|
||||
|
||||
Важно:
|
||||
|
||||
- `docker compose down` **не удаляет volume**
|
||||
- `docker compose down -v` удаляет данные `PostgreSQL` и `Redis`
|
||||
|
||||
## Основные Docker-сервисы
|
||||
|
||||
### `postgres`
|
||||
|
||||
PostgreSQL база проекта.
|
||||
|
||||
По умолчанию:
|
||||
|
||||
- DB: `dubizzle_scraper`
|
||||
- user: `dubizzle`
|
||||
- password: `dubizzle`
|
||||
- host port: `15432`
|
||||
|
||||
### `redis`
|
||||
|
||||
Используется как:
|
||||
|
||||
- broker для `Celery`
|
||||
- backend для task state
|
||||
- хранилище progress/state ключей
|
||||
|
||||
По умолчанию host port: `16379`
|
||||
|
||||
### `migrate`
|
||||
|
||||
Отдельный сервис, который выполняет:
|
||||
|
||||
```bash
|
||||
alembic upgrade head
|
||||
```
|
||||
|
||||
### `api`
|
||||
|
||||
Запускает:
|
||||
|
||||
```bash
|
||||
uvicorn dubizzle_scraper.api.app:app --host 0.0.0.0 --port 8000
|
||||
```
|
||||
|
||||
### `worker`
|
||||
|
||||
Запускает `Celery worker` и выполняет scraping-задачи.
|
||||
|
||||
### `beat`
|
||||
|
||||
Планировщик periodic tasks.
|
||||
|
||||
По умолчанию проект настроен на запуск каждые `60` минут.
|
||||
|
||||
|
||||
## Ручной запуск задач в Docker
|
||||
|
||||
### Разовый sync listing
|
||||
|
||||
```bash
|
||||
docker compose up -d postgres redis migrate
|
||||
docker compose run --rm --profile manual sync
|
||||
```
|
||||
|
||||
По умолчанию сервис запускает:
|
||||
По умолчанию это запустит:
|
||||
|
||||
```bash
|
||||
dubizzle sync-listing --limit 100 --output /app/artifacts/json/docker_sync_listing.json
|
||||
```
|
||||
|
||||
Результат будет доступен на хосте в `artifacts/json/docker_sync_listing.json`, потому что каталог `./artifacts` примонтирован в контейнеры.
|
||||
Результат будет на хосте в:
|
||||
|
||||
Чтобы изменить лимит без редактирования `docker-compose.yml`, передай env:
|
||||
- `artifacts/json/docker_sync_listing.json`
|
||||
|
||||
### Запуск с другим лимитом
|
||||
|
||||
```bash
|
||||
docker compose run --rm --profile manual -e DUBIZZLE_SYNC_LISTING_LIMIT=500 sync
|
||||
```
|
||||
|
||||
Для непрерывного режима оставь обычный стек:
|
||||
### Запуск непрерывного режима
|
||||
|
||||
```bash
|
||||
docker compose up -d api worker beat
|
||||
```
|
||||
|
||||
В текущей конфигурации Docker использует `DUBIZZLE_DISCOVERY_MODE=algolia`, поэтому worker и one-shot `sync` идут в `Algolia` первым путём, а браузерный fallback включается только при ошибке discovery.
|
||||
Для полного hourly/full bootstrap охвата держи включёнными `DUBIZZLE_LISTING_SEGMENTS=auto`, `DUBIZZLE_ALWAYS_FULL_SCAN=true` и `CELERY_PARALLEL_SEGMENTS=true`.
|
||||
## Локальный запуск без Docker
|
||||
|
||||
Локальный запуск возможен, но проект в первую очередь ориентирован на `PostgreSQL + Redis`.
|
||||
|
||||
### Установка
|
||||
|
||||
```bash
|
||||
pip install -e .
|
||||
playwright install chromium
|
||||
```
|
||||
|
||||
### Миграции
|
||||
|
||||
```bash
|
||||
alembic upgrade head
|
||||
```
|
||||
|
||||
### Запуск API
|
||||
|
||||
```bash
|
||||
uvicorn dubizzle_scraper.api.app:app --reload --port 8000
|
||||
```
|
||||
|
||||
### Запуск worker
|
||||
|
||||
```bash
|
||||
celery -A dubizzle_scraper.worker.celery_app worker --loglevel=info --concurrency=1 --pool=solo -Q scraping
|
||||
```
|
||||
|
||||
### Запуск beat
|
||||
|
||||
```bash
|
||||
celery -A dubizzle_scraper.worker.celery_app beat --loglevel=info
|
||||
```
|
||||
|
||||
|
||||
### Полный sync листинга
|
||||
|
||||
```bash
|
||||
dubizzle sync-listing --limit 100
|
||||
```
|
||||
|
||||
Если включена сегментация и не переданы `make`, `model`, `limit`, CLI автоматически пойдет в segmented sync.
|
||||
|
||||
## API
|
||||
|
||||
**Статистика:**
|
||||
- `GET /health` — статус сервиса и подключения к БД
|
||||
- `GET /api/v1/stats` — сколько машин и картинок в базе, топ брендов
|
||||
Базовые роуты:
|
||||
|
||||
**Машины:**
|
||||
- `GET /api/v1/cars` — список с пагинацией
|
||||
- `GET /api/v1/cars/{id}` — карточка с картинками
|
||||
- `GET /api/v1/cars/by-origin/{origin_id}` — поиск по DUBIZZLE stock number
|
||||
### Health
|
||||
|
||||
**Задачи:**
|
||||
- `POST /api/v1/tasks/sync-vehicle` — скрапнуть одну машину по URL
|
||||
- `POST /api/v1/tasks/sync-listing` — запустить полный обход листинга
|
||||
- `GET /api/v1/sync-runs` — история запусков
|
||||
- `GET /health`
|
||||
|
||||
Скрапим конкретную машину:
|
||||
### Машины
|
||||
|
||||
- `GET /api/v1/cars`
|
||||
- `GET /api/v1/cars/{car_id}`
|
||||
- `GET /api/v1/cars/by-origin/{origin_id}`
|
||||
- `GET /api/v1/stats`
|
||||
|
||||
### Задачи
|
||||
|
||||
- `POST /api/v1/tasks/sync-vehicle`
|
||||
- `POST /api/v1/tasks/sync-listing`
|
||||
- `GET /api/v1/tasks/{task_id}`
|
||||
- `GET /api/v1/sync-runs`
|
||||
|
||||
### Пример запуска sync vehicle
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8000/api/v1/tasks/sync-vehicle \
|
||||
curl -X POST http://localhost:18000/api/v1/tasks/sync-vehicle \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"vehicle_url": "https://www.dubizzle.com/VehicleDetail/45089484~US"}'
|
||||
```
|
||||
|
||||
## CLI
|
||||
|
||||
Для отладки без API и Celery:
|
||||
### Пример запуска sync listing
|
||||
|
||||
```bash
|
||||
dubizzle init-db
|
||||
dubizzle collect-listing --make Toyota
|
||||
dubizzle sync-vehicle "https://www.dubizzle.com/VehicleDetail/45089484~US"
|
||||
dubizzle sync-listing --limit 10
|
||||
```
|
||||
|
||||
`dubizzle init-db` используйте для SQLite/локальных тестов. В PostgreSQL-сценарии применяйте миграции Alembic.
|
||||
|
||||
## Структура
|
||||
|
||||
```text
|
||||
dubizzle_scraper/
|
||||
scraper.py — оркестратор: связывает browser → parser → storage
|
||||
cli.py — CLI-команды (init-db, sync-vehicle, sync-listing, ...)
|
||||
proxy_bridge.py — HTTP→SOCKS5 мост (Chromium не умеет SOCKS5 с авторизацией)
|
||||
|
||||
browser/
|
||||
factory.py — создание браузера, stealth-инъекции, fingerprint
|
||||
listing.py — сбор ссылок на машины с листинга, пагинация
|
||||
network.py — перехват XHR/fetch ответов через Playwright events
|
||||
pace.py — рандомные паузы и движение мыши
|
||||
|
||||
parsing/
|
||||
parser.py — VehicleParser: DOM + XHR JSON + embedded JSON
|
||||
mapper.py — CarMapper: нормализация → CarRecord
|
||||
|
||||
storage/
|
||||
models.py — SQLAlchemy: Car, Image, SyncRun
|
||||
schemas.py — Pydantic: CarRecord, ImageRecord, CarRead
|
||||
enums.py — допустимые значения (drive, gearbox, body_type, ...)
|
||||
db.py — PersistenceService: upsert, sync runs, статистика
|
||||
|
||||
api/
|
||||
app.py — FastAPI factory, lifespan, роутеры
|
||||
deps.py — dependency injection (Settings, PersistenceService)
|
||||
routes/
|
||||
health.py — GET /health
|
||||
cars.py — CRUD по машинам + GET /stats
|
||||
tasks.py — запуск Celery-задач и история sync-runs
|
||||
|
||||
worker/
|
||||
celery_app.py — конфиг Celery, beat-расписание
|
||||
tasks.py — sync_vehicle_task, sync_listing_task
|
||||
|
||||
core/
|
||||
config.py — Settings (dataclass), env-переменные
|
||||
logs.py — логирование с trace_id (ContextVar)
|
||||
retry.py — декоратор @retryable с exponential backoff
|
||||
runtime_config.py — чтение и нормализация runtime-конфига
|
||||
utils.py — VIN_RE, deep_find_key, вспомогательные функции
|
||||
|
||||
alembic/ — миграции БД
|
||||
tests/ — тесты
|
||||
```
|
||||
|
||||
## Конфигурация
|
||||
|
||||
Всё через env-переменные (полный список в `.env.example`):
|
||||
|
||||
- **БД:** `DUBIZZLE_DATABASE_URL`, `DUBIZZLE_DATABASE_POOL_SIZE`
|
||||
- **Redis:** `DUBIZZLE_REDIS_URL`
|
||||
- **Celery:** `CELERY_BROKER_URL`, `CELERY_BEAT_SYNC_INTERVAL_MINUTES`
|
||||
- **Скрапер:** `DUBIZZLE_HEADLESS`, `DUBIZZLE_SYNC_ONLY_NEW`, `DUBIZZLE_MAX_PAGES_PER_RUN`
|
||||
- **Прокси:** `DUBIZZLE_PROXY_SERVER`, `DUBIZZLE_PROXY_USERNAME`, `DUBIZZLE_PROXY_PASSWORD`
|
||||
- **Паузы:** `DUBIZZLE_BETWEEN_VEHICLES_MIN_S`, `DUBIZZLE_AFTER_PAGE_CHANGE_MAX_S` и т.д.
|
||||
|
||||
## Миграции
|
||||
|
||||
В Docker миграции выполняются отдельным сервисом `migrate` (`alembic upgrade head`).
|
||||
|
||||
`api`, `worker`, `beat` стартуют после успешного завершения `migrate`.
|
||||
|
||||
Вручную:
|
||||
|
||||
```bash
|
||||
alembic upgrade head
|
||||
alembic revision --autogenerate -m "add_column_x"
|
||||
curl -X POST http://localhost:18000/api/v1/tasks/sync-listing \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"limit": 100, "only_new": false}'
|
||||
```
|
||||
|
||||
## Тесты
|
||||
@@ -279,98 +284,13 @@ alembic revision --autogenerate -m "add_column_x"
|
||||
pytest -q
|
||||
```
|
||||
|
||||
Тесты работают на SQLite in-memory, без внешних зависимостей.
|
||||
|
||||
## `pyproject.toml` + `uv.lock`
|
||||
|
||||
Локально можно использовать:
|
||||
|
||||
```bash
|
||||
uv sync
|
||||
uv run pytest -q
|
||||
```
|
||||
|
||||
### Секция `sync`
|
||||
|
||||
Поддерживаются поля:
|
||||
|
||||
- `name`
|
||||
- `ids_initial_size`
|
||||
- `ids_next_size`
|
||||
- `ids_max_pages`
|
||||
- `condition_check_enabled`
|
||||
- `lane`
|
||||
- `only_new`
|
||||
- `limit`
|
||||
|
||||
### Секция `filters`
|
||||
|
||||
Поддерживаются поля:
|
||||
|
||||
- `brands`
|
||||
- `models`
|
||||
- `years`
|
||||
- `body_types`
|
||||
- `colors`
|
||||
- `drives`
|
||||
- `gearboxes`
|
||||
- `locations`
|
||||
- `exclude_brands`
|
||||
- `exclude_models`
|
||||
- `exclude_years`
|
||||
- `exclude_body_types`
|
||||
- `exclude_colors`
|
||||
- `exclude_drives`
|
||||
- `exclude_gearboxes`
|
||||
- `exclude_locations`
|
||||
- `price.min`, `price.max`
|
||||
- `mileage.min`, `mileage.max`
|
||||
- `flags.damaged_only`, `flags.run_and_drive`
|
||||
|
||||
Пример:
|
||||
|
||||
```json
|
||||
{
|
||||
"sync": {
|
||||
"name": null,
|
||||
"ids_initial_size": null,
|
||||
"ids_next_size": null,
|
||||
"ids_max_pages": null,
|
||||
"condition_check_enabled": false,
|
||||
"lane": "dubizzle_cars",
|
||||
"only_new": true,
|
||||
"limit": 50
|
||||
},
|
||||
"filters": {
|
||||
"price": {
|
||||
"min": null,
|
||||
"max": null
|
||||
},
|
||||
"mileage": {
|
||||
"min": null,
|
||||
"max": null
|
||||
},
|
||||
"flags": {
|
||||
"damaged_only": null,
|
||||
"run_and_drive": null
|
||||
},
|
||||
"brands": ["Toyota", "Honda"],
|
||||
"models": [],
|
||||
"years": [2021, 2022],
|
||||
"body_types": ["SUV"],
|
||||
"colors": [],
|
||||
"drives": [],
|
||||
"gearboxes": [],
|
||||
"locations": [],
|
||||
"exclude_brands": [],
|
||||
"exclude_models": [],
|
||||
"exclude_years": [],
|
||||
"exclude_body_types": []
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Путь задаётся через `DUBIZZLE_RUNTIME_CONFIG_FILE`, по умолчанию — `/app/runtime_config.json`.
|
||||
|
||||
CLI и API аргументы имеют приоритет, а `runtime_config.json` работает как runtime-default и расширяемый фильтр.
|
||||
Тесты покрывают:
|
||||
|
||||
- mapper
|
||||
- parser
|
||||
- listing
|
||||
- scraper
|
||||
- worker tasks
|
||||
- resilience
|
||||
- self-heal
|
||||
- db
|
||||
Reference in New Issue
Block a user