From 243801f6282d80a31b83e6ce1ca6802bd86d674f Mon Sep 17 00:00:00 2001 From: qananasikq Date: Thu, 16 Apr 2026 18:01:45 +0300 Subject: [PATCH] update readme --- README.md | 402 ++++++++++++------------------------------------------ 1 file changed, 86 insertions(+), 316 deletions(-) diff --git a/README.md b/README.md index 14a25db..b743f73 100644 --- a/README.md +++ b/README.md @@ -1,344 +1,114 @@ -# IAAI Scraper +# Encar Scraper -Парсер аукционных автомобилей с [iaai.com](https://www.iaai.com). Ходит по листингу, собирает карточки машин, вытаскивает данные из DOM и перехваченных XHR-ответов, складывает всё в PostgreSQL. Работает через Playwright, FastAPI, Celery и Docker. +Парсер автомобилей с [encar.com](https://www.encar.com) — крупнейшей корейской площадки продажи авто. -## Как устроен сайт и его защита +## Как это работает -У IAAI стоит **Imperva Incapsula** — внешний WAF и anti-bot. +Скрапер работает через публичный JSON API Encar (без браузера, без Selenium): -- **Anti-bot** — headless Chromium без прокси часто режется. -- **Динамическая подгрузка** — часть данных приходит через XHR (`/Search`, `/VehicleDetail`), часть остаётся в HTML. -- **Cookie consent** — при первом заходе показывают баннер. +1. **Листинг** — `api.encar.com/search/car/list/general` отдаёт список авто с пагинацией (до 1000 на страницу) +2. **Шардирование** — API лимитирует выдачу ~10k результатов, поэтому весь каталог (~230k авто) разбивается на 36 шардов по типу (domestic/import) и диапазонам годов +3. **Фото** — batch endpoint `api.encar.com/v1/readside/vehicles` (до 20 ID за запрос) возвращает все фото с CDN `ci.encar.com` +4. **Переводы** — корейские названия брендов, моделей и цветов автоматически переводятся в русские/английские +5. **Sold-трекинг** — авто, пропавшие из листинга, помечаются как проданные -Поэтому в проекте используются Playwright, паузы между действиями, прокси и сохранение браузерного состояния. +Celery Beat запускает полную синхронизацию каждые 60 минут. Worker обходит все шарды, парсит данные и пишет батчами в PostgreSQL. -## Локальный запуск (без Docker) +## Особенности -### Требования +- **Чистые HTTP запросы** — без браузера, через публичный API Encar +- **~100 авто/сек** — полный sync ~230k авто за ~35 мин +- **PostgreSQL** для хранения данных (авто + фото) +- **Celery + Redis** для фоновых задач и периодической синхронизации +- **FastAPI** REST API для управления задачами и просмотра данных -- **Python 3.11+** -- **Git** - -Docker **не нужен**. Данные хранятся в SQLite-файле `iaai_scraper.db` в корне проекта. - -### Быстрый старт +## Быстрый старт (Docker) ```bash -git clone -cd iaai_scraper_project -pip install -e . -playwright install firefox -iaai init-db +docker compose up -d --build ``` -`iaai init-db` актуален для SQLite/локального CLI-режима. Для PostgreSQL используйте Alembic-миграции (`alembic upgrade head` или сервис `migrate` в Docker). - -Готовый `.env` уже в репозитории и настроен на SQLite ничего менять не нужно. - -### Запуск парсера - -**Скрапинг одной машины:** - -```bash -iaai sync-vehicle "https://www.iaai.com/VehicleDetail/45089484~US" -``` - -**Сбор листинга + скрапинг (например Toyota, 10 штук):** - -```bash -iaai sync-listing --make Toyota --limit 10 -``` - -**Только ссылки с листинга (без скрапинга):** - -```bash -iaai collect-listing --make Toyota -``` - -**С видимым браузером (для отладки):** - -```bash -iaai --headless false sync-vehicle "https://www.iaai.com/VehicleDetail/45089484~US" -``` - -**Проверка, что записалось в базу (`iaai_scraper.db`):** - -```bash -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: - -```env -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 -``` - -Применить миграции и запустить в трёх терминалах: - -```bash -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 (все сервисы в контейнерах) - -```bash -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` - -```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/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` — история запусков - -Скрапим конкретную машину: - -```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"}' -``` +Поднимутся сервисы: +- `encar-postgres` — PostgreSQL +- `encar-redis` — Redis +- `encar-api` — FastAPI на порту 8000 +- `encar-worker` — Celery worker +- `encar-beat` — периодическая синхронизация (каждые 60 мин) ## CLI -Для отладки без API и Celery: +```bash +# Инициализация БД +encar init-db + +# Собрать листинг +encar collect-listing --limit 100 + +# Синхронизировать листинг в БД +encar sync-listing --limit 100 --car-type all --only-new true + +# Синхронизировать одно авто +encar sync-vehicle "https://www.encar.com/dc/dc_cardetailview.do?carid=41421262" +``` + +### Фильтры ```bash -iaai init-db -iaai collect-listing --make Toyota -iaai sync-vehicle "https://www.iaai.com/VehicleDetail/45089484~US" -iaai sync-listing --limit 10 +encar sync-listing --car-type import --manufacturer BMW --year-from 2020 --limit 50 ``` -`iaai init-db` используйте для SQLite/локальных тестов. В PostgreSQL-сценарии применяйте миграции Alembic. +## API endpoints -## Структура +- `GET /health` — проверка доступности +- `GET /api/v1/cars` — список авто с пагинацией и фильтрами +- `GET /api/v1/cars/{car_id}` — детали авто +- `GET /api/v1/cars/by-origin/{origin_id}` — поиск по origin_id +- `POST /api/v1/tasks/sync-vehicle` — синхронизация одного авто +- `POST /api/v1/tasks/sync-listing` — синхронизация листинга +- `GET /api/v1/tasks/{task_id}` — статус задачи +- `GET /api/v1/sync-runs` — история синхронизаций -```text -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`. - -Вручную: +### Примеры ```bash -alembic upgrade head -alembic revision --autogenerate -m "add_column_x" +# Запуск синхронизации +curl -X POST http://localhost:8000/api/v1/tasks/sync-listing \ + -H "Content-Type: application/json" \ + -d '{"car_type": "all", "limit": 100, "only_new": true}' + +# Статус задачи +curl http://localhost:8000/api/v1/tasks/ + +# Список авто +curl "http://localhost:8000/api/v1/cars?page=1&per_page=20" ``` -## Тесты +## Переменные окружения -```bash -pytest -q +- **БД:** `ENCAR_DATABASE_URL` +- **Redis:** `ENCAR_REDIS_URL` +- **Celery:** `CELERY_BROKER_URL`, `CELERY_RESULT_BACKEND`, `CELERY_TASK_TIME_LIMIT` +- **Beat:** `ENCAR_BEAT_INTERVAL_MINUTES` (по умолчанию 60), `ENCAR_BEAT_LIMIT` (0 = без лимита) + +## Структура проекта + +``` +encar_scraper/ +├── encar.py — скрапер Encar (HTTP API), маппер, переводы +├── cli.py — CLI интерфейс +├── api/ +│ ├── app.py — FastAPI приложение +│ └── routes/ — health, cars, tasks +├── core/ +│ ├── config.py — настройки из env vars +│ ├── utils.py — утилиты (save_to_json, deep_find_key) +│ └── logs.py — логирование с trace_id +├── storage/ +│ ├── db.py — PersistenceService (upsert, session_scope) +│ ├── models.py — SQLAlchemy модели (Car, Image, SyncRun) +│ └── schemas.py — Pydantic схемы (CarRecord, ImageRecord) +└── worker/ + ├── celery_app.py — Celery app + beat schedule + └── tasks.py — encar_sync_listing_task, encar_sync_vehicle_task ``` -Тесты работают на SQLite in-memory, без внешних зависимостей. - -## `pyproject.toml` + `uv.lock` - -Проект переведён на современную схему зависимостей: - -- `pyproject.toml` — декларация зависимостей и метаданных проекта -- `uv.lock` — зафиксированные версии для воспроизводимых установок - -Локально можно использовать: - -```bash -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` - -Пример: - -```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 и расширяемый фильтр. -