rewrite readme

This commit is contained in:
qananasikq
2026-04-08 22:55:00 +03:00
parent e5700f9623
commit 3992067a94

283
README.md
View File

@@ -1,189 +1,136 @@
# IAAI Scraper # IAAI Scraper
Скрапер листинга автомобилей с сайта IAAI. Парсер аукционных автомобилей с [iaai.com](https://www.iaai.com). Ходит по листингу, собирает карточки машин, вытаскивает данные из DOM и перехваченных XHR-ответов, складывает всё в PostgreSQL. Работает через Playwright (headless Chromium), крутится в Docker.
Собирает данные карточек через Playwright, парсит HTML и перехваченные JSON ответы,
нормализует и сохраняет в PostgreSQL или SQLite через SQLAlchemy.
По умолчанию работает последовательно одна машина за раз, с паузами между запросами.
JSON-результаты CLI по умолчанию сохраняются в `artifacts/json/`, чтобы не засорять корень проекта. ## Как устроен сайт и его защита
## Что делает IAAI не использует Cloudflare или Akamai, но у них своя защита:
1. Открывает страницу листинга `Vehiclelisting/Cars`, собирает ссылки на карточки. - **Детект автоматизации** — проверяют `navigator.webdriver`, `chrome.runtime`, WebGL-рендерер, `hardwareConcurrency` и прочие browser fingerprint параметры. Если видят Playwright/Puppeteer — блокируют.
2. Переходит на каждую карточку, перехватывает XHR/fetch JSON-ответы. - **Rate limiting** — после нескольких быстрых запросов подряд начинают отдавать пустые страницы или редиректить. Нет явного 429, просто перестают отдавать данные.
3. Парсит DOM-текст, `<title>`, встроенные `<script>` с JSON, сетевые payload'ы. - **Geo-блокировка** — часть контента доступна только с US/CA IP. С европейских адресов листинг может быть пустым.
4. Маппит всё в единую структуру `CarRecord` (pydantic) с нормализацией полей. - **Динамическая подгрузка** — карточки машин подгружаются через XHR (`/Search`, `/VehicleDetail`), часть данных приходит в JSON, часть рендерится на сервере. Нельзя просто дёрнуть HTML нужен полноценный браузер с JS.
5. Делает upsert в БД по `origin_id`, сравнивая `content_hash`, чтобы пропускать неизменившиеся записи. - **Cookie consent** — при первом заходе показывают баннер, без принятия кук часть функционала не работает.
6. Защищён от дублей: `origin_id` уникален, одинаковые записи пропускаются, а повторные картинки заменяются безопасно.
## Структура проекта
## Запуск
```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/ iaai_scraper/
├── browser/ scraper.py — оркестратор: связывает browser → parser → storage
├── factory.py # запуск Chrome/Chromium с desktop-фингерпринтом cli.py — CLI-команды (init-db, sync-vehicle, sync-listing, ...)
├── network.py # перехват XHR/fetch, фильтрация и категоризация JSON proxy_bridge.py — HTTP→SOCKS5 мост (Chromium не умеет SOCKS5 с авторизацией)
│ └── pace.py # паузы между действиями
├── core/ browser/
├── config.py # настройки из .env factory.py — создание браузера, stealth-инъекции, fingerprint
├── logs.py # setup logging listing.py сбор ссылок на машины с листинга, пагинация
├── retry.py # retry-декоратор network.py — перехват XHR/fetch ответов через Playwright events
└── utils.py # regex, deep_find_key, save_to_json pace.py — рандомные паузы и движение мыши
├── parsing/
│ ├── parser.py # DOM + JSON парсинг parsing/
└── mapper.py # нормализация в CarRecord parser.py — VehicleParser: 3 канала (DOM + XHR JSON + embedded JSON)
├── storage/ mapper.py — CarMapper: нормализация → CarRecord, content hash
│ ├── models.py # ORM: cars, images, sync_runs
│ ├── schemas.py # pydantic-схемы storage/
├── db.py # upsert с content_hash models.py — SQLAlchemy: Car (30+ полей), Image, SyncRun, ScrapeTask
├── listing.py # сбор ссылок из листинга schemas.py — Pydantic: CarRecord, ImageRecord, CarRead
└── enums.py # enum-значения для БД enums.py — допустимые значения (drive, gearbox, body_type, ...)
├── scraper.py # главный модуль db.py — PersistenceService: upsert (insert/update/skip), sync runs
└── cli.py # CLI (argparse)
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 ```bash
pip install -r requirements.txt alembic upgrade head
python -m playwright install chromium alembic revision --autogenerate -m "add_column_x"
```
## Настройка
Создайте `.env` на основе `.env.example`:
```env
IAAI_HEADLESS=true
IAAI_DATABASE_URL=postgresql+psycopg://postgres:postgres@localhost:5432/iaai_scraper
```
Все настройки (pacing, лимиты, gentle mode, retry/backoff, scheduler) задаются через переменные окружения в `.env.example`.
### БД для рабочего сайта
Для рабочего запуска нужно **PostgreSQL**.
Пример:
```env
IAAI_DATABASE_URL=postgresql+psycopg://postgres:postgres@localhost:5432/iaai_scraper
```
SQLite подходит для локальной отладки, но не для production-сценария сайта.
### Scheduler по умолчанию
Режим :
- запуск раз в **1 час**
- лимит **30 машин за цикл**
- обрабатываются только **новые авто** (по умолчанию `IAAI_SYNC_ONLY_NEW=true`)
Это уже отражено в актуальных env-настройках.
### Прокси и anti-bot
IAAI использует anti-bot / fraud protection.
Что важно:
- предпочтительно использовать **USA residential** или **USA mobile** прокси
- для Playwright лучше использовать **HTTP/HTTPS proxy**
- Chromium **не поддерживает SOCKS5 с аутентификацией напрямую**
- поэтому для production желательно покупать прокси, который отдаёт именно HTTP/HTTPS доступ
Если используется встроенный bridge `iaai_scraper/proxy_bridge.py` (HTTP/HTTPS → SOCKS5),
в нём добавлены базовые меры стабильности:
- корректное чтение request body через `rfile`
- поддержка `Transfer-Encoding: chunked` для request body
- базовое логирование запросов и ошибок
- таймауты relay-соединений
- ограничение числа рабочих потоков (`PROXY_BRIDGE_MAX_WORKERS`)
- безопасный ответ `502 Bad Gateway` без утечки внутренних исключений
Пример:
```env
IAAI_PROXY_SERVER=http://proxy.example.com:8080
IAAI_PROXY_USERNAME=username
IAAI_PROXY_PASSWORD=password
```
### Captcha / anti-bot detection
В парсере добавлены признаки для определения возможной captcha / anti-bot страницы:
- `possible_captcha`
- `possible_antibot`
- `dom_hints.has_captcha_text`
- `dom_hints.has_antibot_text`
Если сайт начнёт отдавать защитную страницу, это можно увидеть в результате scrape.
## Команды
```bash
# создать таблицы
python main.py init-db
# собрать ссылки из листинга
python main.py collect-listing --make Toyota --model Camry --output artifacts/json/listing.json
# scrape одной карточки
python main.py scrape-vehicle "https://www.iaai.com/VehicleDetail/41180634~US" --output artifacts/json/result.json
# scrape + запись в БД
python main.py sync-vehicle "https://www.iaai.com/VehicleDetail/41180634~US" --lane iaai
# массовая синхронизация листинга
python main.py sync-listing --make Toyota --model Camry --lane iaai_cars --limit 30
# при необходимости можно принудительно отключить фильтр only-new
python main.py sync-listing --limit 30 --only-new false
# daemon-режим (цикл каждые N минут)
python main.py run-daemon --interval 60
``` ```
## Тесты ## Тесты
```bash ```bash
pytest tests -q pytest -q
``` ```
## Docker 30 тестов, SQLite in-memory, без внешних зависимостей.
```bash
# собрать образ
docker compose build
# запустить daemon (по умолчанию run-daemon)
docker compose up -d
# посмотреть логи
docker compose logs -f
# одноразовая команда
docker compose run --rm iaai-scraper python main.py sync-listing --limit 5
# остановить (graceful shutdown)
docker compose down
```
Контейнер автоматически перезапускается при крашах (`restart: unless-stopped`).
Для Docker прокси также задаются через `.env`.
## Защита от дублей
Система защищена от дублей на нескольких уровнях:
- `origin_id` уникален в БД
- при совпадении `content_hash` запись **пропускается** (`skipped`)
- при обновлении запись не дублируется, а обновляется
- изображения пересобираются без накопления дублей