2026-04-13 19:46:28 +02:00
2026-04-13 16:35:08 +03:00
2026-04-13 16:35:08 +03:00
2026-04-13 16:35:08 +03:00
2026-04-09 20:34:20 +03:00
2026-04-13 16:35:08 +03:00
2026-04-10 19:55:38 +03:00
2026-04-13 16:35:08 +03:00
2026-04-13 16:35:08 +03:00
2026-04-13 16:35:08 +03:00
2026-04-13 16:35:08 +03:00
2026-04-10 19:47:20 +03:00
2026-04-09 20:34:20 +03:00

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 /health
  • worker имеет healthcheck через celery inspect ping
  • beat имеет 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 — скрапнуть одну машину по URL
  • POST /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

Поддерживаются поля:

  • 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

Пример:

{
  "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 и расширяемый фильтр.

Description
No description provided
Readme 2.5 MiB
Languages
Python 99.2%
Shell 0.5%
Dockerfile 0.2%
Mako 0.1%