IAAI Scraper
Парсер аукционных автомобилей с iaai.com. Ходит по листингу, собирает карточки машин, вытаскивает данные из DOM и перехваченных XHR-ответов, складывает всё в PostgreSQL. Работает через Playwright, FastAPI, Celery и Docker.
Как устроен сайт и его защита
У IAAI стоит Imperva Incapsula — внешний WAF и anti-bot.
- Anti-bot — headless Chromium без прокси часто режется.
- Динамическая подгрузка — часть данных приходит через XHR (
/Search,/VehicleDetail), часть остаётся в HTML. - Cookie consent — при первом заходе показывают баннер.
Поэтому в проекте используются Playwright, паузы между действиями, прокси и сохранение браузерного состояния.
Локальный запуск (без Docker)
Требования
- Python 3.11+
- Git
Docker не нужен. Данные хранятся в SQLite-файле iaai_scraper.db в корне проекта.
Быстрый старт
git clone <repo-url>
cd iaai_scraper_project
pip install -e .
playwright install firefox
iaai init-db
iaai init-db актуален для SQLite/локального CLI-режима. Для PostgreSQL используйте Alembic-миграции (alembic upgrade head или сервис migrate в Docker).
Готовый .env уже в репозитории и настроен на SQLite ничего менять не нужно.
Запуск парсера
Скрапинг одной машины:
iaai sync-vehicle "https://www.iaai.com/VehicleDetail/45089484~US"
Сбор листинга + скрапинг (например Toyota, 10 штук):
iaai sync-listing --make Toyota --limit 10
Только ссылки с листинга (без скрапинга):
iaai collect-listing --make Toyota
С видимым браузером (для отладки):
iaai --headless false sync-vehicle "https://www.iaai.com/VehicleDetail/45089484~US"
Проверка, что записалось в базу (iaai_scraper.db):
python -c "import sqlite3; c=sqlite3.connect('iaai_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/ и в БД iaai_scraper.db.
Полный стек (API + Worker + Beat)
Для API и автоматического сбора нужны Redis и PostgreSQL. В .env раскомментируй строки Redis/Celery и замени БД на PostgreSQL:
IAAI_DATABASE_URL=postgresql+psycopg2://iaai:iaai@localhost:5432/iaai_scraper
IAAI_REDIS_URL=redis://localhost:6379/0
CELERY_BROKER_URL=redis://localhost:6379/0
CELERY_RESULT_BACKEND=redis://localhost:6379/0
Применить миграции и запустить в трёх терминалах:
alembic upgrade head
# Терминал 1 — API
uvicorn iaai_scraper.api.app:app --reload --port 8000
# Терминал 2 — Celery Worker
celery -A iaai_scraper.worker.celery_app worker --loglevel=info --concurrency=1 --pool=solo -Q scraping
# Терминал 3 — Celery Beat (периодический запуск)
celery -A iaai_scraper.worker.celery_app beat --loglevel=info
API: http://localhost:8000 · Swagger: http://localhost:8000/docs
Запуск через Docker (все сервисы в контейнерах)
cp .env.example .env
docker compose up -d
Будут запущены сервисы postgres, redis, migrate, api, worker, beat. API доступен на http://localhost:8000.
В Docker-образ дополнительно проверяется Python-синтаксис на этапе сборки (python -m compileall -q iaai_scraper), чтобы не выкатывать битый код.
entrypoint.sh поднимает SOCKS5→HTTP proxy bridge (если задан SOCKS5_PROXY_HOST) и запускает Xvfb только для worker и CLI scraping-команд.
По умолчанию beat запускает сбор листинга раз в 1 час (CELERY_BEAT_SYNC_INTERVAL_MINUTES=60).
Swagger-документация: http://localhost:8000/docs
docker compose ps
apiимеет healthcheck наGET /healthworkerимеет healthcheck черезcelery inspect pingbeatимеет healthcheck по файлуcelerybeat-schedule
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— скрапнуть одну машину по URLPOST /api/v1/tasks/sync-listing— запустить полный обход листингаGET /api/v1/sync-runs— история запусков
Скрапим конкретную машину:
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:
iaai init-db
iaai collect-listing --make Toyota
iaai sync-vehicle "https://www.iaai.com/VehicleDetail/45089484~US"
iaai sync-listing --limit 10
iaai init-db используйте для SQLite/локальных тестов. В PostgreSQL-сценарии применяйте миграции Alembic.
Структура
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: 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):
- БД:
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 миграции выполняются отдельным сервисом migrate (alembic upgrade head).
api, worker, beat стартуют после успешного завершения migrate.
Вручную:
alembic upgrade head
alembic revision --autogenerate -m "add_column_x"
Тесты
pytest -q
Тесты работают на SQLite in-memory, без внешних зависимостей.
pyproject.toml + uv.lock
Проект переведён на современную схему зависимостей:
pyproject.toml— декларация зависимостей и метаданных проектаuv.lock— зафиксированные версии для воспроизводимых установок
Локально можно использовать:
uv sync
uv run pytest -q
Runtime-фильтры
Файл runtime_config.json управляет runtime-поведением sync и фильтрацией автомобилей.
Секция sync
Поддерживаются поля:
nameids_initial_sizeids_next_sizeids_max_pagescondition_check_enabledlaneonly_newlimit
Секция filters
Поддерживаются поля:
brandsmodelsyearsbody_typescolorsdrivesgearboxeslocationsexclude_brandsexclude_modelsexclude_yearsexclude_body_typesexclude_colorsexclude_drivesexclude_gearboxesexclude_locationsprice.min,price.maxmileage.min,mileage.maxflags.damaged_only,flags.run_and_drive
Пример:
{
"sync": {
"name": null,
"ids_initial_size": null,
"ids_next_size": null,
"ids_max_pages": null,
"condition_check_enabled": false,
"lane": "iaai_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": []
}
}
Путь задаётся через IAAI_RUNTIME_CONFIG_FILE, по умолчанию — /app/runtime_config.json.
CLI и API аргументы имеют приоритет, а runtime_config.json работает как runtime-default и расширяемый фильтр.