137 lines
5.8 KiB
Markdown
137 lines
5.8 KiB
Markdown
# IAAI Scraper
|
||
|
||
Парсер аукционных автомобилей с [iaai.com](https://www.iaai.com). Ходит по листингу, собирает карточки машин, вытаскивает данные из DOM и перехваченных XHR-ответов, складывает всё в PostgreSQL. Работает через Playwright (headless Chromium), крутится в Docker.
|
||
|
||
|
||
## Как устроен сайт и его защита
|
||
|
||
У IAAI стоит **Imperva Incapsula** — внешний WAF и anti-bot.
|
||
|
||
- **Anti-bot** — headless Chromium без прокси часто режется.
|
||
- **Динамическая подгрузка** — часть данных приходит через XHR (`/Search`, `/VehicleDetail`), часть есть в HTML.
|
||
- **Cookie consent** — при первом заходе показывают баннер.
|
||
|
||
Поэтому в проекте используется Playwright, паузы между действиями и прокси.
|
||
|
||
|
||
## Запуск
|
||
|
||
```bash
|
||
cp .env.example .env # подправить под себя
|
||
docker compose up -d
|
||
```
|
||
|
||
Поднимутся 5 контейнеров: postgres, redis, api, worker, beat. API на `http://localhost:8000`.
|
||
|
||
Swagger-документация: `http://localhost:8000/docs`
|
||
|
||
## API
|
||
|
||
**Здоровье и статистика:**
|
||
- `GET /health` — статус сервиса и подключения к БД
|
||
- `GET /api/v1/stats` — сколько машин/картинок в базе, топ брендов
|
||
|
||
**Машины:**
|
||
- `GET /api/v1/cars` — список с пагинацией
|
||
- `GET /api/v1/cars/{id}` — карточка с картинками
|
||
- `GET /api/v1/cars/by-origin/{origin_id}` — поиск по IAAI stock number
|
||
|
||
**Задачи:**
|
||
- `POST /api/v1/tasks/sync-vehicle` — скрапнуть одну машину по URL
|
||
- `POST /api/v1/tasks/sync-listing` — запустить полный обход листинга
|
||
- `GET /api/v1/tasks/{task_id}` — статус задачи
|
||
- `GET /api/v1/tasks` — все задачи
|
||
- `GET /api/v1/sync-runs` — история запусков
|
||
|
||
Пример — скрапнуть конкретную машину:
|
||
```bash
|
||
curl -X POST http://localhost:8000/api/v1/tasks/sync-vehicle
|
||
-H "Content-Type: application/json" \
|
||
-d '{"vehicle_url": "https://www.iaai.com/VehicleDetail/45089484~US"}'
|
||
```
|
||
|
||
## CLI
|
||
|
||
Для отладки без API/Celery:
|
||
```bash
|
||
python main.py init-db
|
||
python main.py collect-listing --make Toyota
|
||
python main.py sync-vehicle "https://www.iaai.com/VehicleDetail/45089484~US"
|
||
python main.py sync-listing --limit 10
|
||
```
|
||
|
||
## Структура
|
||
|
||
```
|
||
iaai_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: 3 канала (DOM + XHR JSON + embedded JSON)
|
||
mapper.py — CarMapper: нормализация → CarRecord, content hash
|
||
|
||
storage/
|
||
models.py — SQLAlchemy: Car (30+ полей), Image, SyncRun, ScrapeTask
|
||
schemas.py — Pydantic: CarRecord, ImageRecord, CarRead
|
||
enums.py — допустимые значения (drive, gearbox, body_type, ...)
|
||
db.py — PersistenceService: upsert (insert/update/skip), 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
|
||
utils.py — VIN_RE, deep_find_key, вспомогательные функции
|
||
|
||
alembic/ — миграции БД
|
||
tests/ — 30 тестов (SQLite in-memory)
|
||
```
|
||
|
||
## Конфигурация
|
||
|
||
Всё через env-переменные (полный список в `.env.example`):
|
||
|
||
**БД:** `IAAI_DATABASE_URL`, `IAAI_DATABASE_POOL_SIZE`
|
||
**Redis:** `IAAI_REDIS_URL`
|
||
**Celery:** `CELERY_BROKER_URL`, `CELERY_BEAT_SYNC_INTERVAL_MINUTES`
|
||
**Скрапер:** `IAAI_HEADLESS`, `IAAI_SYNC_ONLY_NEW`, `IAAI_MAX_PAGES_PER_RUN`
|
||
**Прокси:** `IAAI_PROXY_SERVER`, `IAAI_PROXY_USERNAME`, `IAAI_PROXY_PASSWORD`
|
||
**Паузы:** `IAAI_BETWEEN_VEHICLES_MIN_S`, `IAAI_AFTER_PAGE_CHANGE_MAX_S` и т.д.
|
||
|
||
## Миграции
|
||
|
||
В Docker миграции накатываются автоматически при старте API-контейнера (`alembic upgrade head` в entrypoint).
|
||
|
||
Вручную:
|
||
```bash
|
||
alembic upgrade head
|
||
alembic revision --autogenerate -m "add_column_x"
|
||
```
|
||
|
||
## Тесты
|
||
|
||
```bash
|
||
pytest -q
|
||
```
|
||
|
||
30 тестов, SQLite in-memory, без внешних зависимостей.
|