Сервис парсинга карточек маркетплейсов. Умеет подбирать карточки под страту, например: Рубашки женские, 4 шт.
А также умеет парсить данные карточек: на вход принимает URL или артикул, на выход отдает
сырой JSON __NUXT__ или очищенные данные карточки согласно модели данных.
Сейчас поддерживает только Ozon.
Работать с сервисом можно двумя способами: синхронно через HTTP (Swagger) или асинхронно через Kafka - для интеграции с таск-менеджером.
- Python 3.13
- FastAPI — HTTP-API - Swagger на
/docs - Selenium + selenium-stealth — управляемый браузер с обходом антибота
- Google Chrome (headless) + Xvfb — рендеринг страниц Ozon
- Pydantic / pydantic-settings — модели данных и настройки
- kafka-python — асинхронный транспорт задач
- Docker + Docker Compose — упаковка и запуск (HTTP-сервис + воркер)
- pytest — тесты маппера, подбора и воркера
app/
├── main.py # точка входа FastAPI
├── api/
│ ├── dependencies.py # проверка ключа между сервисами
│ └── routes/ # ozon.py, files.py, health.py
├── core/
│ ├── config.py # настройки + политики таймаутов/ретраев per-маркетплейс
│ ├── logging.py # уровни логирования
│ ├── exceptions.py # доменные ошибки (антибот / не найдено / невалидный вход)
│ └── security.py # декодирование токена
├── marketplaces/ozon/
│ ├── parser.py # Selenium + stealth: открыть страницу/выдачу, получить HTML
│ ├── nuxt.py # извлечение __NUXT__
│ ├── repository.py # источник данных: url/id/запрос -> сырьё
│ ├── mapper.py # сырьё -> доменная карточка OzonCard
│ ├── search_listing.py # выдача: извлечение кандидатов, отсев, пагинация
│ ├── selection.py # подбор под страту: потребность, сезон, оркестрация
│ ├── service.py # оркестрация + debug-файлы + события в Kafka
│ ├── models.py # доменные модели
│ └── schemas.py # схемы API (запрос/ответ)
├── worker/ # Kafka-транспорт: консьюмер задач + продюсер результатов
│ ├── kafka_config.py # топики, регион, group_id
│ ├── consumer.py # цикл: читать задачу → выполнить → отправить результат
│ ├── handlers.py # select/parse: задача → вызов сервиса → результат с эхо
│ ├── result_producer.py # отправка результатов в топики результатов
│ └── run_worker.py # точка входа воркера
├── shared/kafka_logger.py # журнал событий
└── debug/ # debug-артефакты - файлы, скриншоты
tests/ # тесты маппера и подбора на сохранённом HTML (без сети)
docker compose up --buildSwagger: http://localhost:8010/docs
POST /api/v1/ozon/card/by-url— очищенная карточка по URLPOST /api/v1/ozon/card/by-id— очищенная карточка по артикулуPOST /api/v1/ozon/select— подбор карточек под стратуPOST /api/v1/ozon/category/by-url— информация о категории по URLPOST /api/v1/ozon/category/by-id— информация о категории по IDPOST /api/v1/ozon/raw/by-url— сырой__NUXT__по URLPOST /api/v1/ozon/raw/by-id— сырой__NUXT__по артикулуPOST /api/v1/ozon/search/diagnostics— диагностика поисковой выдачи (служебный)
Файлы debug и служебные эндпоинты:
GET /api/v1/filesGET /api/v1/files/{name}GET /api/v1/healthGET /api/v1/health/ready
Машиночитаемый контракт API: http://localhost:8010/openapi.json
Воркер запускается отдельным контейнером тем же образом (нужен тот же Selenium +
Chrome + Xvfb), отличается только команда — python -m app.worker.run_worker.
Он и HTTP-сервис независимы: можно поднять оба или любой по отдельности.
На вход — параметры страты:
query- поисковый запрос,count- требуемое количество карточек,is_seasonal- есть ли сезонное разделение,base_share- доля базовых для сезонных предметов,exclude- уже отобранные карточки - исключения.
На выход — распарсенные карточки.
Кроме HTTP, сервис умеет работать как консьюмер задач: читает задачи из
Kafka, выполняет их существующей логикой (подбор select, обход карточки
parse) и пишет результаты в топики результатов. Логика парсинга и подбора не
меняется — Kafka это просто другой транспорт вместо HTTP.
Kafka используется для двух независимых вещей:
- Транспорт задач — воркер читает задачи и пишет результаты (см. раздел
«Работа через Kafka»). Управляется
KAFKA_BOOTSTRAP_SERVERS,OZON_GEO_*,KAFKA_GROUP_ID. - Журнал событий — HTTP-сервис шлёт события другим сервисам (аналитика,
таск-менеджер). Управляется
KAFKA_ENABLED. Мягкая зависимость: при недоступном брокере HTTP-сервис работает как обычно.
Запуск воркера (отдельный процесс, независим от HTTP):
python -m app.worker.run_workerТопики именуются с суффиксом кода региона (сейчас spb):
| Поток | Топик задач (читает) | Топик результатов (пишет) |
|---|---|---|
| Подбор | select.tasks.spb |
select.results.spb |
| Обход | parse.tasks.spb |
parse.results.spb |
Сообщения — UTF-8 JSON в snake_case. Каждая задача несёт эхо-поля трассировки
(task_id, set_id, stratum_id, card_id, geo), которые воркер возвращает
в результате без изменений. На каждую задачу — ровно один результат; при сбое
возвращается ok: false с текстом в error.
Воркер отказоустойчив к брокеру: если Kafka недоступна при старте или упала в
процессе — воркер ждёт и переподключается, не падая. Обрабатывает задачи по
одной (Selenium не потокобезопасен); для параллелизма поднимается несколько
инстансов с одним KAFKA_GROUP_ID.
python -m pytest tests/ -vТесты гоняют маппер и логику подбора на сохранённом HTML из tests/fixtures/ —
без обращения к Ozon (заход в карточку в тестах подбора замокан). Меняя логику
извлечения, отсева или расчёта потребности, сразу видно, не сломалось ли что-то.
При DEBUG=true каждый запрос сохраняет в app/debug/: полный HTML, сырой
JSON, JSON карточки и скриншот. Имя — ozon_<sku>_<ГГГГММДД_ЧЧММСС>; для страниц
выдачи — search_<ГГГГММДД_ЧЧММСС>.
В проде ставьте DEBUG=false — файлы не пишутся, файловые эндпоинты пусты.
Очистить:
sudo rm -rf app/debug/* # если файлы созданы из контейнера rootСкопируйте .env.example в .env. Ключевое:
| Переменная | Назначение |
|---|---|
DEBUG |
сохранять ли html/json/png и отдавать файловые эндпоинты |
LOG_LEVEL |
DEBUG / INFO / WARNING / ERROR |
KAFKA_ENABLED |
слать ли события в Kafka (без брокера сервис работает) |
OZON_RETRIES, OZON_*_TIMEOUT, OZON_PAUSE_*, OZON_RETRY_PAUSE_* |
политика Ozon: таймауты, паузы, ретраи (у каждого маркетплейса своя) |
OZON_MIN_RATING, OZON_MIN_REVIEWS |
пороги отсева кандидатов в подборе (рейтинг ≥, отзывов >) |
OZON_MAX_PAGES |
потолок числа страниц выдачи при подборе |
KAFKA_BOOTSTRAP_SERVERS |
адрес брокера Kafka (IP:порт машины с Kafka) |
OZON_GEO_CODE |
код региона — суффикс топиков (напр. spb) |
OZON_GEO_NAME |
имя региона — эхо-поле geo в результатах |
KAFKA_GROUP_ID |
consumer group региона (общий для инстансов одного региона) |
KAFKA_RECONNECT_DELAY_S |
пауза между попытками переподключения к брокеру, сек |
api/dependencies.py — проверка межсервисного ключа между сервисами.