update readme
This commit is contained in:
110
README.md
110
README.md
@@ -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 и расширяемый фильтр.
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user