From 2931eee0a72a728f9491ebe4daf10849351da270 Mon Sep 17 00:00:00 2001 From: qananasikq Date: Thu, 9 Apr 2026 20:35:41 +0300 Subject: [PATCH] fix readme --- README.md | 188 ++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 155 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index fd467b6..a8a30ca 100644 --- a/README.md +++ b/README.md @@ -1,37 +1,49 @@ -# IAAI Scraper - -Парсер аукционных автомобилей с [iaai.com](https://www.iaai.com). Ходит по листингу, собирает карточки машин, вытаскивает данные из DOM и перехваченных XHR-ответов, складывает всё в PostgreSQL. Работает через Playwright (headless Chromium), крутится в Docker. +# IAAI Scraper +Парсер аукционных автомобилей с [iaai.com](https://www.iaai.com). Ходит по листингу, собирает карточки машин, вытаскивает данные из DOM и перехваченных XHR-ответов, складывает всё в PostgreSQL. Работает через Playwright, FastAPI, Celery и Docker. ## Как устроен сайт и его защита У IAAI стоит **Imperva Incapsula** — внешний WAF и anti-bot. - **Anti-bot** — headless Chromium без прокси часто режется. -- **Динамическая подгрузка** — часть данных приходит через XHR (`/Search`, `/VehicleDetail`), часть есть в HTML. +- **Динамическая подгрузка** — часть данных приходит через XHR (`/Search`, `/VehicleDetail`), часть остаётся в HTML. - **Cookie consent** — при первом заходе показывают баннер. -Поэтому в проекте используется Playwright, паузы между действиями и прокси. - +Поэтому в проекте используются Playwright, паузы между действиями, прокси и сохранение браузерного состояния. ## Запуск ```bash -cp .env.example .env # подправить под себя +cp .env.example .env docker compose up -d ``` -Поднимутся 5 контейнеров: postgres, redis, api, worker, beat. API на `http://localhost:8000`. +Будут запущены сервисы `postgres`, `redis`, `migrate`, `api`, `worker`, `beat`. API доступен на `http://localhost:8000`. + +В production Docker-образ дополнительно проверяет Python-синтаксис на этапе сборки (`python -m compileall -q iaai_scraper`), чтобы не выкатывать битый код. + +`entrypoint.sh` поднимает SOCKS5→HTTP proxy bridge (если задан `SOCKS5_PROXY_HOST`) и запускает `Xvfb` только для `worker` и CLI scraping-команд. По умолчанию `beat` запускает сбор листинга **раз в 1 час** и обрабатывает **до 26 машин за запуск** (`CELERY_BEAT_SYNC_INTERVAL_MINUTES=60`, `CELERY_BEAT_SYNC_LIMIT=26`). Swagger-документация: `http://localhost:8000/docs` +Проверка состояния сервисов: + +```bash +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/stats` — сколько машин и картинок в базе, топ брендов **Машины:** - `GET /api/v1/cars` — список с пагинацией @@ -41,30 +53,30 @@ Swagger-документация: `http://localhost:8000/docs` **Задачи:** - `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 +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: +Для отладки без 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 init-db +iaai collect-listing --make Toyota +iaai sync-vehicle "https://www.iaai.com/VehicleDetail/45089484~US" +iaai sync-listing --limit 10 ``` ## Структура -``` +```text iaai_scraper/ scraper.py — оркестратор: связывает browser → parser → storage cli.py — CLI-команды (init-db, sync-vehicle, sync-listing, ...) @@ -77,14 +89,14 @@ iaai_scraper/ pace.py — рандомные паузы и движение мыши parsing/ - parser.py — VehicleParser: 3 канала (DOM + XHR JSON + embedded JSON) - mapper.py — CarMapper: нормализация → CarRecord, content hash + parser.py — VehicleParser: DOM + XHR JSON + embedded JSON + mapper.py — CarMapper: нормализация → CarRecord storage/ - models.py — SQLAlchemy: Car (30+ полей), Image, SyncRun, ScrapeTask + models.py — SQLAlchemy: Car, Image, SyncRun schemas.py — Pydantic: CarRecord, ImageRecord, CarRead enums.py — допустимые значения (drive, gearbox, body_type, ...) - db.py — PersistenceService: upsert (insert/update/skip), sync runs + db.py — PersistenceService: upsert, sync runs, статистика api/ app.py — FastAPI factory, lifespan, роутеры @@ -92,38 +104,42 @@ iaai_scraper/ routes/ health.py — GET /health cars.py — CRUD по машинам + GET /stats - tasks.py — управление Celery-задачами + sync-runs + 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-переменные + 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/ — 30 тестов (SQLite in-memory) +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` и т.д. +- **БД:** `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). +В Docker миграции выполняются отдельным сервисом `migrate` (`alembic upgrade head`). + +`api`, `worker`, `beat` стартуют после успешного завершения `migrate`. Вручную: + ```bash alembic upgrade head alembic revision --autogenerate -m "add_column_x" @@ -135,4 +151,110 @@ alembic revision --autogenerate -m "add_column_x" pytest -q ``` -30 тестов, SQLite in-memory, без внешних зависимостей. +Тесты работают на SQLite in-memory, без внешних зависимостей. + +## `pyproject.toml` + `uv.lock` + +Проект переведён на современную схему зависимостей: + +- `pyproject.toml` — декларация зависимостей и метаданных проекта +- `uv.lock` — зафиксированные версии для воспроизводимых установок +- `requirements.txt` удалён: Docker тоже собирается из `pyproject.toml` + `uv.lock` + +Локально можно использовать: + +```bash +uv sync +uv run pytest -q +``` + +## Runtime-фильтры + +Файл `runtime_config.json` управляет runtime-поведением синка и фильтрацией автомобилей. + +### Секция `sync` + +Поддерживаются поля: + +- `name` +- `ids_initial_size` +- `ids_next_size` +- `ids_max_pages` +- `condition_check_enabled` +- `lane` +- `only_new` +- `limit` + +Часть полей сейчас служит заделом под более сложную стратегию синка. Рабочие поля уже используются: `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` + +Пример: + +```json +{ + "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 и расширяемый фильтр. +