153 lines
5.5 KiB
Markdown
153 lines
5.5 KiB
Markdown
# OpenLane Scraper
|
||
|
||
Парсер автомобилей с [OpenLane marketplace](https://app.openlane.com). Забирает данные через API (`GET /api/v4/search`), сохраняет в PostgreSQL. Работает через Playwright (для авторизации), FastAPI, Celery и Docker.
|
||
|
||
## Архитектура
|
||
|
||
- **Авторизация**: Okta OAuth2 PKCE через `okta.iam.karglobal.com`. Сессия сохраняется в `storage_state.json` и переиспользуется между запусками.
|
||
- **Сбор данных**: API-запросы с заголовками `x-rbz-user-id`, `x-rbz-dealership-id` (извлекаются из JWT access_token).
|
||
- **Защита аккаунта**: throttle 2–5s между запросами, jitter 0–120s перед стартом, circuit breaker (2 ошибки подряд → стоп), 403→немедленный стоп, 429→backoff×3.
|
||
- **Anti-detection**: блокировка ресурсов (bugsnag, intercom, pusher, analytics), stealth patches.
|
||
|
||
## Локальный запуск
|
||
|
||
### Требования
|
||
|
||
- **Python 3.11+**
|
||
- **PostgreSQL** и **Redis** (для Celery)
|
||
|
||
### Быстрый старт
|
||
|
||
```bash
|
||
git clone <repo-url>
|
||
cd openlane_scraper_project
|
||
pip install -e .
|
||
playwright install chromium
|
||
|
||
cp .env.example .env
|
||
# Заполнить OPENLANE_USERNAME, OPENLANE_PASSWORD в .env
|
||
|
||
alembic upgrade head
|
||
```
|
||
|
||
### Запуск
|
||
|
||
```bash
|
||
# Терминал 1 — API
|
||
uvicorn openlane_scraper.api.app:app --reload --port 8000
|
||
|
||
# Терминал 2 — Celery Worker
|
||
celery -A openlane_scraper.worker.celery_app worker --loglevel=info --concurrency=1 --pool=solo -Q scraping
|
||
|
||
# Терминал 3 — Celery Beat (каждый час)
|
||
celery -A openlane_scraper.worker.celery_app beat --loglevel=info
|
||
```
|
||
|
||
CLI:
|
||
|
||
```bash
|
||
openlane scrape-openlane --limit 20
|
||
```
|
||
|
||
## Docker
|
||
|
||
```bash
|
||
cp .env.example .env
|
||
docker compose up -d
|
||
```
|
||
|
||
Сервисы: `postgres`, `redis`, `migrate`, `api`, `worker`, `beat`.
|
||
|
||
|
||
- `beat` запускает сбор **раз в 1 час** (`CELERY_BEAT_SYNC_INTERVAL_MINUTES=60`)
|
||
- Лимит по умолчанию: 20 машин (`OPENLANE_MAX_CARS_LIMIT=20`)
|
||
|
||
API (Docker): `http://localhost:58000` · Swagger: `http://localhost:58000/docs`
|
||
|
||
### Первый логин (storage state)
|
||
|
||
```bash
|
||
docker compose run --rm --service-ports openlane-auth openlane openlane-login
|
||
# Войти вручную в браузере — storage state сохранится в volume
|
||
```
|
||
|
||
## API
|
||
|
||
- `GET /health` — статус сервиса
|
||
- `GET /api/v1/stats` — статистика (машины, картинки, топ брендов)
|
||
- `GET /api/v1/cars` — список с пагинацией
|
||
- `GET /api/v1/cars/{id}` — карточка с картинками
|
||
- `POST /api/v1/tasks/sync-listing` — запустить сбор
|
||
- `GET /api/v1/sync-runs` — история запусков
|
||
|
||
## Структура
|
||
|
||
```text
|
||
openlane_scraper/
|
||
scraper.py — оркестратор: circuit breaker, batched upsert, early-stop
|
||
cli.py — CLI-команды (scrape-openlane, openlane-login)
|
||
|
||
browser/
|
||
factory.py — создание браузера, stealth-инъекции, resource blocking
|
||
|
||
openlane/
|
||
auth.py — авторизация через storage_state / Okta PKCE
|
||
client.py — запросы к /api/v4/search с JWT-заголовками
|
||
checkpoint.py — checkpoint/resume по страницам
|
||
writer.py — JSONL + aggregated JSON
|
||
runner.py — orchestration, retry/backoff, progress
|
||
|
||
storage/
|
||
models.py — SQLAlchemy: Car, Image, SyncRun
|
||
schemas.py — Pydantic: CarRecord, ImageRecord, CarRead
|
||
enums.py — enum-значения (drive, gearbox, body_type, ...)
|
||
db.py — PersistenceService: upsert, sync runs, статистика
|
||
|
||
api/
|
||
app.py — FastAPI factory
|
||
deps.py — dependency injection
|
||
routes/ — health, cars, tasks
|
||
|
||
worker/
|
||
celery_app.py — конфиг Celery, beat-расписание
|
||
tasks.py — sync_listing_task с jitter и lock
|
||
|
||
core/
|
||
config.py — Settings (dataclass), env-переменные
|
||
logs.py — логирование с trace_id
|
||
retry.py — @retryable с exponential backoff
|
||
runtime_config.py — runtime-конфиг
|
||
utils.py — утилиты
|
||
```
|
||
|
||
## Конфигурация
|
||
|
||
Всё через env-переменные (полный список в `.env.example`):
|
||
|
||
- **OpenLane**: `OPENLANE_USERNAME`, `OPENLANE_PASSWORD`, `OPENLANE_MAX_CARS_LIMIT`
|
||
- **БД**: `OPENLANE_DATABASE_URL`, `OPENLANE_DATABASE_POOL_SIZE`
|
||
- **Redis**: `OPENLANE_REDIS_URL`
|
||
- **Celery**: `CELERY_BROKER_URL`, `CELERY_BEAT_SYNC_INTERVAL_MINUTES`
|
||
- **Прокси**: `OPENLANE_PROXY_SERVER`, `OPENLANE_PROXY_USERNAME`, `OPENLANE_PROXY_PASSWORD`
|
||
|
||
## Миграции
|
||
|
||
В Docker миграции выполняются сервисом `migrate` (`alembic upgrade head`).
|
||
|
||
```bash
|
||
alembic upgrade head
|
||
alembic revision --autogenerate -m "add_column_x"
|
||
```
|
||
|
||
## Тесты
|
||
|
||
```bash
|
||
pytest -q
|
||
```
|
||
|
||
Тесты работают на SQLite in-memory, без внешних зависимостей.
|
||
|
||
## Runtime-фильтры
|
||
|
||
Файл `runtime_config.json` управляет runtime-поведением sync и фильтрацией автомобилей.
|