Skip to content

Repository files navigation

Mercator Card Parser

Сервис парсинга карточек маркетплейсов. Умеет подбирать карточки под страту, например: Рубашки женские, 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

docker compose up --build

Swagger: http://localhost:8010/docs

Эндпоинты Ozon

  1. POST /api/v1/ozon/card/by-url — очищенная карточка по URL
  2. POST /api/v1/ozon/card/by-id — очищенная карточка по артикулу
  3. POST /api/v1/ozon/select — подбор карточек под страту
  4. POST /api/v1/ozon/category/by-url — информация о категории по URL
  5. POST /api/v1/ozon/category/by-id — информация о категории по ID
  6. POST /api/v1/ozon/raw/by-url — сырой __NUXT__ по URL
  7. POST /api/v1/ozon/raw/by-id — сырой __NUXT__ по артикулу
  8. POST /api/v1/ozon/search/diagnostics — диагностика поисковой выдачи (служебный)

Файлы debug и служебные эндпоинты:

  • GET /api/v1/files
  • GET /api/v1/files/{name}
  • GET /api/v1/health
  • GET /api/v1/health/ready

Машиночитаемый контракт API: http://localhost:8010/openapi.json

Воркер запускается отдельным контейнером тем же образом (нужен тот же Selenium + Chrome + Xvfb), отличается только команда — python -m app.worker.run_worker. Он и HTTP-сервис независимы: можно поднять оба или любой по отдельности.

Подбор карточек Ozon

На вход — параметры страты:

  • query - поисковый запрос,
  • count - требуемое количество карточек,
  • is_seasonal - есть ли сезонное разделение,
  • base_share - доля базовых для сезонных предметов,
  • exclude - уже отобранные карточки - исключения.

На выход — распарсенные карточки.

Работа через Kafka

Кроме HTTP, сервис умеет работать как консьюмер задач: читает задачи из Kafka, выполняет их существующей логикой (подбор select, обход карточки parse) и пишет результаты в топики результатов. Логика парсинга и подбора не меняется — Kafka это просто другой транспорт вместо HTTP.

Kafka используется для двух независимых вещей:

  1. Транспорт задач — воркер читает задачи и пишет результаты (см. раздел «Работа через Kafka»). Управляется KAFKA_BOOTSTRAP_SERVERS, OZON_GEO_*, KAFKA_GROUP_ID.
  2. Журнал событий — 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-файлы

При DEBUG=true каждый запрос сохраняет в app/debug/: полный HTML, сырой JSON, JSON карточки и скриншот. Имя — ozon_<sku>_<ГГГГММДД_ЧЧММСС>; для страниц выдачи — search_<ГГГГММДД_ЧЧММСС>. В проде ставьте DEBUG=false — файлы не пишутся, файловые эндпоинты пусты.

Очистить:

sudo rm -rf app/debug/*    # если файлы созданы из контейнера root

Настройки (.env)

Скопируйте .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 — проверка межсервисного ключа между сервисами.

About

Econometrics study project for MIPT: marketplace measurements

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages