update readme

This commit is contained in:
qananasikq
2026-04-24 20:47:18 +03:00
parent d484088e18
commit 31f1d39775

110
README.md
View File

@@ -1,10 +1,10 @@
# IAAI Scraper # DUBIZZLE Scraper
Парсер аукционных автомобилей с [iaai.com](https://www.iaai.com). Ходит по листингу, собирает карточки машин, вытаскивает данные из DOM и перехваченных XHR-ответов, складывает всё в PostgreSQL. Работает через Playwright, FastAPI, Celery и Docker. Парсер аукционных автомобилей с [dubizzle.com](https://www.dubizzle.com). Ходит по листингу, собирает карточки машин, вытаскивает данные из DOM и перехваченных XHR-ответов, складывает всё в PostgreSQL. Работает через Playwright, FastAPI, Celery и Docker.
## Как устроен сайт и его защита ## Как устроен сайт и его защита
У IAAI стоит **Imperva Incapsula** — внешний WAF и anti-bot. У DUBIZZLE стоит **Imperva Incapsula** — внешний WAF и anti-bot.
- **Anti-bot** — headless Chromium без прокси часто режется. - **Anti-bot** — headless Chromium без прокси часто режется.
- **Динамическая подгрузка** — часть данных приходит через XHR (`/Search`, `/VehicleDetail`), часть остаётся в HTML. - **Динамическая подгрузка** — часть данных приходит через XHR (`/Search`, `/VehicleDetail`), часть остаётся в HTML.
@@ -19,19 +19,19 @@
- **Python 3.11+** - **Python 3.11+**
- **Git** - **Git**
Docker **не нужен**. Данные хранятся в SQLite-файле `iaai_scraper.db` в корне проекта. Docker **не нужен**. Данные хранятся в SQLite-файле `dubizzle_scraper.db` в корне проекта.
### Быстрый старт ### Быстрый старт
```bash ```bash
git clone <repo-url> git clone <repo-url>
cd iaai_scraper_project cd dubizzle_scraper_project
pip install -e . pip install -e .
playwright install firefox playwright install firefox
iaai init-db dubizzle init-db
``` ```
`iaai init-db` актуален для SQLite/локального CLI-режима. Для PostgreSQL используйте Alembic-миграции (`alembic upgrade head` или сервис `migrate` в Docker). `dubizzle init-db` актуален для SQLite/локального CLI-режима. Для PostgreSQL используйте Alembic-миграции (`alembic upgrade head` или сервис `migrate` в Docker).
Готовый `.env` уже в репозитории и настроен на SQLite ничего менять не нужно. Готовый `.env` уже в репозитории и настроен на SQLite ничего менять не нужно.
@@ -40,42 +40,42 @@ iaai init-db
**Скрапинг одной машины:** **Скрапинг одной машины:**
```bash ```bash
iaai sync-vehicle "https://www.iaai.com/VehicleDetail/45089484~US" dubizzle sync-vehicle "https://www.dubizzle.com/VehicleDetail/45089484~US"
``` ```
**Сбор листинга + скрапинг (например Toyota, 10 штук):** **Сбор листинга + скрапинг (например Toyota, 10 штук):**
```bash ```bash
iaai sync-listing --make Toyota --limit 10 dubizzle sync-listing --make Toyota --limit 10
``` ```
**Только ссылки с листинга (без скрапинга):** **Только ссылки с листинга (без скрапинга):**
```bash ```bash
iaai collect-listing --make Toyota dubizzle collect-listing --make Toyota
``` ```
**С видимым браузером (для отладки):** **С видимым браузером (для отладки):**
```bash ```bash
iaai --headless false sync-vehicle "https://www.iaai.com/VehicleDetail/45089484~US" dubizzle --headless false sync-vehicle "https://www.dubizzle.com/VehicleDetail/45089484~US"
``` ```
**Проверка, что записалось в базу (`iaai_scraper.db`):** **Проверка, что записалось в базу (`dubizzle_scraper.db`):**
```bash ```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]" python -c "import sqlite3; c=sqlite3.connect('dubizzle_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`. Результаты сохраняются в `artifacts/json/` и в БД `dubizzle_scraper.db`.
### Полный стек (API + Worker + Beat) ### Полный стек (API + Worker + Beat)
Для API и автоматического сбора нужны **Redis** и **PostgreSQL**. В `.env` раскомментируй строки Redis/Celery и замени БД на PostgreSQL: Для API и автоматического сбора нужны **Redis** и **PostgreSQL**. В `.env` раскомментируй строки Redis/Celery и замени БД на PostgreSQL:
```env ```env
IAAI_DATABASE_URL=postgresql+psycopg2://iaai:iaai@localhost:5432/iaai_scraper DUBIZZLE_DATABASE_URL=postgresql+psycopg2://dubizzle:dubizzle@localhost:5432/dubizzle_scraper
IAAI_REDIS_URL=redis://localhost:6379/0 DUBIZZLE_REDIS_URL=redis://localhost:6379/0
CELERY_BROKER_URL=redis://localhost:6379/0 CELERY_BROKER_URL=redis://localhost:6379/0
CELERY_RESULT_BACKEND=redis://localhost:6379/0 CELERY_RESULT_BACKEND=redis://localhost:6379/0
``` ```
@@ -86,13 +86,13 @@ CELERY_RESULT_BACKEND=redis://localhost:6379/0
alembic upgrade head alembic upgrade head
# Терминал 1 — API # Терминал 1 — API
uvicorn iaai_scraper.api.app:app --reload --port 8000 uvicorn dubizzle_scraper.api.app:app --reload --port 8000
# Терминал 2 — Celery Worker # Терминал 2 — Celery Worker
celery -A iaai_scraper.worker.celery_app worker --loglevel=info --concurrency=1 --pool=solo -Q scraping celery -A dubizzle_scraper.worker.celery_app worker --loglevel=info --concurrency=1 --pool=solo -Q scraping
# Терминал 3 — Celery Beat (периодический запуск) # Терминал 3 — Celery Beat (периодический запуск)
celery -A iaai_scraper.worker.celery_app beat --loglevel=info celery -A dubizzle_scraper.worker.celery_app beat --loglevel=info
``` ```
API: `http://localhost:8000` · Swagger: `http://localhost:8000/docs` API: `http://localhost:8000` · Swagger: `http://localhost:8000/docs`
@@ -108,11 +108,19 @@ docker compose up -d
Будут запущены сервисы `postgres`, `redis`, `migrate`, `api`, `worker`, `beat`. API доступен на `http://localhost:8000`. Будут запущены сервисы `postgres`, `redis`, `migrate`, `api`, `worker`, `beat`. API доступен на `http://localhost:8000`.
В Docker-образ дополнительно проверяется Python-синтаксис на этапе сборки (`python -m compileall -q iaai_scraper`), чтобы не выкатывать битый код. В Docker-образ дополнительно проверяется Python-синтаксис на этапе сборки (`python -m compileall -q dubizzle_scraper`), чтобы не выкатывать битый код.
`entrypoint.sh` поднимает SOCKS5→HTTP proxy bridge (если задан `SOCKS5_PROXY_HOST`) и запускает `Xvfb` только для `worker` и CLI scraping-команд. `entrypoint.sh` поднимает SOCKS5→HTTP proxy bridge (если задан `SOCKS5_PROXY_HOST`) и запускает `Xvfb` только для `worker` и CLI scraping-команд.
По умолчанию `beat` запускает сбор листинга **раз в 1 час** (`CELERY_BEAT_SYNC_INTERVAL_MINUTES=60`). По умолчанию `beat` запускает сбор листинга **раз в 1 час** (`CELERY_BEAT_SYNC_INTERVAL_MINUTES=60`).
В текущей конфигурации hourly-цикл делает **полный обход всех машин через Algolia с серверной сегментацией**:
- `DUBIZZLE_DISCOVERY_MODE=algolia`
- `DUBIZZLE_LISTING_SEGMENTS=auto`
- `DUBIZZLE_ALWAYS_FULL_SCAN=true`
- `CELERY_PARALLEL_SEGMENTS=true`
Это означает, что каждый beat-тик разбивает каталог на year-only сегменты и проходит их через Algolia, чтобы обойти лимит одного общего запроса и собрать весь каталог, а не только первые ~10k записей.
Swagger-документация: `http://localhost:8000/docs` Swagger-документация: `http://localhost:8000/docs`
@@ -124,6 +132,38 @@ docker compose ps
- `worker` имеет healthcheck через `celery inspect ping` - `worker` имеет healthcheck через `celery inspect ping`
- `beat` имеет healthcheck по файлу `celerybeat-schedule` - `beat` имеет healthcheck по файлу `celerybeat-schedule`
### Разовый сбор через Docker с сохранением результата на хосте
Для ручного прогона листинга через `Algolia` без запуска `beat` используй отдельный one-shot сервис `sync`:
```bash
docker compose up -d postgres redis migrate
docker compose run --rm --profile manual sync
```
По умолчанию сервис запускает:
```bash
dubizzle sync-listing --limit 100 --output /app/artifacts/json/docker_sync_listing.json
```
Результат будет доступен на хосте в `artifacts/json/docker_sync_listing.json`, потому что каталог `./artifacts` примонтирован в контейнеры.
Чтобы изменить лимит без редактирования `docker-compose.yml`, передай env:
```bash
docker compose run --rm --profile manual -e DUBIZZLE_SYNC_LISTING_LIMIT=500 sync
```
Для непрерывного режима оставь обычный стек:
```bash
docker compose up -d api worker beat
```
В текущей конфигурации Docker использует `DUBIZZLE_DISCOVERY_MODE=algolia`, поэтому worker и one-shot `sync` идут в `Algolia` первым путём, а браузерный fallback включается только при ошибке discovery.
Для полного hourly/full bootstrap охвата держи включёнными `DUBIZZLE_LISTING_SEGMENTS=auto`, `DUBIZZLE_ALWAYS_FULL_SCAN=true` и `CELERY_PARALLEL_SEGMENTS=true`.
## API ## API
@@ -134,7 +174,7 @@ docker compose ps
**Машины:** **Машины:**
- `GET /api/v1/cars` — список с пагинацией - `GET /api/v1/cars` — список с пагинацией
- `GET /api/v1/cars/{id}` — карточка с картинками - `GET /api/v1/cars/{id}` — карточка с картинками
- `GET /api/v1/cars/by-origin/{origin_id}` — поиск по IAAI stock number - `GET /api/v1/cars/by-origin/{origin_id}` — поиск по DUBIZZLE stock number
**Задачи:** **Задачи:**
- `POST /api/v1/tasks/sync-vehicle` — скрапнуть одну машину по URL - `POST /api/v1/tasks/sync-vehicle` — скрапнуть одну машину по URL
@@ -146,7 +186,7 @@ docker compose ps
```bash ```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" \ -H "Content-Type: application/json" \
-d '{"vehicle_url": "https://www.iaai.com/VehicleDetail/45089484~US"}' -d '{"vehicle_url": "https://www.dubizzle.com/VehicleDetail/45089484~US"}'
``` ```
## CLI ## CLI
@@ -154,18 +194,18 @@ curl -X POST http://localhost:8000/api/v1/tasks/sync-vehicle \
Для отладки без API и Celery: Для отладки без API и Celery:
```bash ```bash
iaai init-db dubizzle init-db
iaai collect-listing --make Toyota dubizzle collect-listing --make Toyota
iaai sync-vehicle "https://www.iaai.com/VehicleDetail/45089484~US" dubizzle sync-vehicle "https://www.dubizzle.com/VehicleDetail/45089484~US"
iaai sync-listing --limit 10 dubizzle sync-listing --limit 10
``` ```
`iaai init-db` используйте для SQLite/локальных тестов. В PostgreSQL-сценарии применяйте миграции Alembic. `dubizzle init-db` используйте для SQLite/локальных тестов. В PostgreSQL-сценарии применяйте миграции Alembic.
## Структура ## Структура
```text ```text
iaai_scraper/ dubizzle_scraper/
scraper.py — оркестратор: связывает browser → parser → storage scraper.py — оркестратор: связывает browser → parser → storage
cli.py — CLI-команды (init-db, sync-vehicle, sync-listing, ...) cli.py — CLI-команды (init-db, sync-vehicle, sync-listing, ...)
proxy_bridge.py — HTTP→SOCKS5 мост (Chromium не умеет SOCKS5 с авторизацией) proxy_bridge.py — HTTP→SOCKS5 мост (Chromium не умеет SOCKS5 с авторизацией)
@@ -213,12 +253,12 @@ tests/ — тесты
Всё через env-переменные (полный список в `.env.example`): Всё через env-переменные (полный список в `.env.example`):
- **БД:** `IAAI_DATABASE_URL`, `IAAI_DATABASE_POOL_SIZE` - **БД:** `DUBIZZLE_DATABASE_URL`, `DUBIZZLE_DATABASE_POOL_SIZE`
- **Redis:** `IAAI_REDIS_URL` - **Redis:** `DUBIZZLE_REDIS_URL`
- **Celery:** `CELERY_BROKER_URL`, `CELERY_BEAT_SYNC_INTERVAL_MINUTES` - **Celery:** `CELERY_BROKER_URL`, `CELERY_BEAT_SYNC_INTERVAL_MINUTES`
- **Скрапер:** `IAAI_HEADLESS`, `IAAI_SYNC_ONLY_NEW`, `IAAI_MAX_PAGES_PER_RUN` - **Скрапер:** `DUBIZZLE_HEADLESS`, `DUBIZZLE_SYNC_ONLY_NEW`, `DUBIZZLE_MAX_PAGES_PER_RUN`
- **Прокси:** `IAAI_PROXY_SERVER`, `IAAI_PROXY_USERNAME`, `IAAI_PROXY_PASSWORD` - **Прокси:** `DUBIZZLE_PROXY_SERVER`, `DUBIZZLE_PROXY_USERNAME`, `DUBIZZLE_PROXY_PASSWORD`
- **Паузы:** `IAAI_BETWEEN_VEHICLES_MIN_S`, `IAAI_AFTER_PAGE_CHANGE_MAX_S` и т.д. - **Паузы:** `DUBIZZLE_BETWEEN_VEHICLES_MIN_S`, `DUBIZZLE_AFTER_PAGE_CHANGE_MAX_S` и т.д.
## Миграции ## Миграции
@@ -297,7 +337,7 @@ uv run pytest -q
"ids_next_size": null, "ids_next_size": null,
"ids_max_pages": null, "ids_max_pages": null,
"condition_check_enabled": false, "condition_check_enabled": false,
"lane": "iaai_cars", "lane": "dubizzle_cars",
"only_new": true, "only_new": true,
"limit": 50 "limit": 50
}, },
@@ -330,7 +370,7 @@ uv run pytest -q
} }
``` ```
Путь задаётся через `IAAI_RUNTIME_CONFIG_FILE`, по умолчанию — `/app/runtime_config.json`. Путь задаётся через `DUBIZZLE_RUNTIME_CONFIG_FILE`, по умолчанию — `/app/runtime_config.json`.
CLI и API аргументы имеют приоритет, а `runtime_config.json` работает как runtime-default и расширяемый фильтр. CLI и API аргументы имеют приоритет, а `runtime_config.json` работает как runtime-default и расширяемый фильтр.