296 lines
6.6 KiB
Markdown
296 lines
6.6 KiB
Markdown
# Dubizzle scraper
|
||
|
||
Сервис для сбора объявлений с `dubizzle.com`.
|
||
|
||
Основной сценарий работы:
|
||
|
||
- discovery списка машин через `Algolia`
|
||
- при необходимости fallback на сайт
|
||
- нормализация полей автомобиля
|
||
- upsert в PostgreSQL
|
||
- периодический запуск через `Celery Beat`
|
||
|
||
## Что делает проект
|
||
|
||
Проект:
|
||
|
||
- собирает все доступные машины из каталога `Dubizzle`
|
||
- использует `Algolia` как основной источник discovery
|
||
- обходит лимит одного запроса через сегментацию каталога
|
||
- сохраняет автомобили и изображения в PostgreSQL
|
||
- отдает API для просмотра данных и запуска задач
|
||
- поддерживает hourly/full scan через `Celery`
|
||
|
||
## Текущая архитектура
|
||
|
||
Сейчас проект настроен под `Dubizzle`.
|
||
|
||
Ключевые особенности:
|
||
|
||
- пакет проекта: `dubizzle_scraper`
|
||
- режим discovery по умолчанию: `algolia`
|
||
- full scan включен по умолчанию
|
||
- авто-сегментация: `DUBIZZLE_LISTING_SEGMENTS=auto`
|
||
- параллельный запуск сегментов: `CELERY_PARALLEL_SEGMENTS=true`
|
||
|
||
### Как собирается весь каталог
|
||
|
||
Один широкий запрос в `Algolia` упирается примерно в лимит $10{,}000$ доступных результатов. Поэтому проект использует сегментацию по годам.
|
||
|
||
Авто-сегменты (`auto`) сейчас разбивают каталог на 8 диапазонов по году:
|
||
|
||
- `1900-2012`
|
||
- `2013-2015`
|
||
- `2016-2017`
|
||
- `2018-2019`
|
||
- `2020-2021`
|
||
- `2022-2023`
|
||
- `2024-2025`
|
||
- `2026-2027`
|
||
|
||
Если сегмент все равно слишком большой, discovery дополнительно режет его по `id`-диапазонам.
|
||
|
||
## Стек
|
||
|
||
- `Python 3.11+`
|
||
- `Playwright`
|
||
- `FastAPI`
|
||
- `Celery`
|
||
- `Redis`
|
||
- `PostgreSQL`
|
||
- `SQLAlchemy`
|
||
- `Alembic`
|
||
- `Docker Compose`
|
||
|
||
## Быстрый старт через Docker
|
||
|
||
Это основной рекомендуемый способ запуска.
|
||
|
||
### 1. Подготовить окружение
|
||
|
||
```bash
|
||
cp .env.example .env
|
||
```
|
||
|
||
Если нужно, отредактируй `.env`.
|
||
|
||
### 2. Запустить стек
|
||
|
||
```bash
|
||
docker compose up -d
|
||
```
|
||
|
||
Поднимутся сервисы:
|
||
|
||
- `postgres`
|
||
- `redis`
|
||
- `migrate`
|
||
- `api`
|
||
- `worker`
|
||
- `beat`
|
||
|
||
### 3. Проверить статус
|
||
|
||
```bash
|
||
docker compose ps
|
||
```
|
||
|
||
API будет доступен по адресу:
|
||
|
||
- `http://localhost:18000`
|
||
- Swagger: `http://localhost:18000/docs`
|
||
|
||
> Порт пробрасывается как `${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 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`
|
||
|
||
### Запуск с другим лимитом
|
||
|
||
```bash
|
||
docker compose run --rm --profile manual -e DUBIZZLE_SYNC_LISTING_LIMIT=500 sync
|
||
```
|
||
|
||
### Запуск непрерывного режима
|
||
|
||
```bash
|
||
docker compose up -d api worker beat
|
||
```
|
||
|
||
## Локальный запуск без 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
|
||
|
||
Базовые роуты:
|
||
|
||
### Health
|
||
|
||
- `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:18000/api/v1/tasks/sync-vehicle \
|
||
-H "Content-Type: application/json" \
|
||
-d '{"vehicle_url": "https://www.dubizzle.com/VehicleDetail/45089484~US"}'
|
||
```
|
||
|
||
### Пример запуска sync listing
|
||
|
||
```bash
|
||
curl -X POST http://localhost:18000/api/v1/tasks/sync-listing \
|
||
-H "Content-Type: application/json" \
|
||
-d '{"limit": 100, "only_new": false}'
|
||
```
|
||
|
||
## Тесты
|
||
|
||
```bash
|
||
pytest -q
|
||
```
|
||
|
||
Тесты покрывают:
|
||
|
||
- mapper
|
||
- parser
|
||
- listing
|
||
- scraper
|
||
- worker tasks
|
||
- resilience
|
||
- self-heal
|
||
- db |