ru-marketplace-mcp

by Vladimir-Human (community) · Claude Desktop, Claude Code, OpenCode, любой MCP-клиент, Python 3.12+, Chrome (для анти-бота)

MCP MCP Servers Open Source v1.6.0 · 20.08.2026 активный

Одиннадцать маркетплейсов как MCP-серверы: Wildberries, Ozon, Яндекс Маркет, Детский мир, Авито, AliExpress, Taobao, Мегамаркет, Lamoda, DNS, Ситилинк. Плюс сравнение цен по всем сразу. Только чтение, ключи не нужны.

v1.6.0
20.08.2026 current

Установка
# вариант 1: установка из PyPI
pip install ru-marketplace-mcp

# вариант 2: установка из исходников (требуются Python 3.12+ и uv)
git clone https://github.com/Vladimir-Human/ru-marketplace-mcp.git
cd ru-marketplace-mcp
uv sync --all-packages
uv run pytest -q -m "not live and not cdp"
показать оригинал переведено ИИ

ru-marketplace-mcp

CI Python 3.12+ License: MIT MCP

MCP-серверы для российских и китайских маркетплейсов. Цены, наличие, рейтинги, отзывы и реквизиты продавцов с Wildberries, Ozon, Яндекс Маркета, Детского мира, Авито, AliExpress, Taobao, Мегамаркета, Lamoda, DNS и Ситилинка. Плюс сравнение цен по всем источникам одним вызовом.

Только чтение. Ключи API, токены и регистрация не нужны — площадки с жёстким анти-ботом читаются через ваш собственный Chrome. Одно исключение по желанию: опциональный MPStats берёт платный токен (MPSTATS_MP_AUTH) — без него всё остальное работает как прежде.

English version below · Архитектура · Как добавить источник · Про анти-бот


Что внутри

Сервер Инструментов Что нужно, чтобы читалось Что умеет
Wildberries 8 анонимный HTTP Поиск, карточки, отзывы, вопросы о товаре, реквизиты продавца, каталог и товары категории
Яндекс Маркет 2 анонимный HTTP Цены разных продавцов, разбивка оценок по звёздам, отзывы
Детский мир 3 анонимный HTTP Детские товары, наличие в офлайн-магазинах, категории
Ozon 3 ваш Chrome; с домашнего IP часто и без него Поиск, карточки, отзывы
Авито 3 ваш Chrome + российский домашний IP и запросы вразрядку — иначе блок по IP Поиск объявлений, карточки, репутация продавца
Taobao 2 ваш Chrome с активным входом в Taobao Поиск и карточки, цены в юанях
Мегамаркет 2 ваш Chrome с активным входом — анонимной сессии API отдаёт пусто Поиск и карточки через мобильный API
Lamoda 2 карточки анонимно (GraphQL), поиск — ваш Chrome Поиск, карточки с размерами
DNS 2 ваш Chrome (Qrator) Поиск и карточки электроники
Ситилинк 2 ваш Chrome (Qrator) Поиск и карточки электроники
AliExpress 2 ваш Chrome (x5sec) Поиск и карточки, цены в рублях
Сравнение 2 опрашивает всё перечисленное «Где дешевле?» одним вызовом
MPStats 2 платный аккаунт MPStats, cookie mp_auth (опционально) Продажи/остатки/графики за 30 дней по SKU Ozon/WB, остатки по складам (FBS/FBO)

Читается анонимно, без браузера: Wildberries, Яндекс Маркет, Детский мир и карточки Lamoda. Остальным нужен ваш залогиненный Chrome (CDP). Taobao и Мегамаркет дополнительно требуют активного входа на саму площадку — без него Taobao упирается в стену логина, а Мегамаркет возвращает пустой ответ. Авито также блокирует по IP: с датацентрового адреса это полный отказ, с российского домашнего — работает, если не злоупотреблять запросами. Запросы к CDP-источникам отправляются с задержкой: очередь подряд без пауз их ломает (DNS и Taobao в тестах так и деградировали), поэтому коннекторы сами выдерживают паузу между вызовами. Текущее состояние вашей сессии покажет marketplace-mcp doctor.

MPStats выделяется отдельно: это единственный платный источник. Без MPSTATS_MP_AUTH сервер запускается, но инструменты отвечают auth_missing — поэтому он опционален и подключается по желанию, на остальные двенадцать серверов он никак не влияет.

Всего 35 инструментов в 13 серверах на общем рантайме mcp-core. Плюс объединённый marketplace-mcp, который монтирует всё разом — одна запись в конфиге клиента вместо тринадцати. Он добавляет собственный инструмент marketplace_sources (какие коннекторы поднялись, а какие отвалились и почему), так что в нём 36 инструментов: 35 смонтированных плюс этот.

Быстрый старт

Требуется Python 3.12+ и uv.

git clone https://github.com/Vladimir-Human/ru-marketplace-mcp.git
cd ru-marketplace-mcp
uv sync --all-packages
uv run pytest -q -m "not live and not cdp"   # 1208 офлайн-тестов, сеть не нужна

Проверка живого эндпоинта:

uv run python -c "
import asyncio
from wb_connector.server import wb_selfcheck
print(asyncio.run(wb_selfcheck()).status)   # ждём success
"

Подключение к MCP-клиенту

Каждый сервер — консольная команда, поэтому пути в конфиге не зашиваются.

Claude Desktop — claude_desktop_config.json

Windows: %APPDATA%\Claude\claude_desktop_config.json macOS: ~/Library/Application Support/Claude/claude_desktop_config.json

Проще всего подключить одну запись — объединённый сервер монтирует все источники разом, а имена инструментов (wb_search, avito_seller и т. д.) не меняются:

{
"mcpServers": {
  "marketplace": {
    "command": "uv",
    "args": ["run", "--directory", "C:/путь/к/ru-marketplace-mcp", "marketplace-mcp"],
  },
},
}

Если нужны отдельные серверы, marketplace-mcp install claude напечатает готовый блок для вставки. Путь к вашему checkout уже подставлен: заглушку /path/to/ru-marketplace-mcp править вручную не придётся. При установке из wheel вместо путей печатаются консольные команды на PATH. Неизвестное имя клиента (допустимы claude, claude-code, cursor, dsh) команда отклоняет с пояснением и кодом возврата 2 — молча подставить блок для Claude она не может. Минимальный вариант вручную:

{
"mcpServers": {
  "wildberries": {
    "command": "uv",
    "args": ["run", "--directory", "C:/путь/к/ru-marketplace-mcp", "wb-mcp"],
  },
  "ozon": {
    "command": "uv",
    "args": ["run", "--directory", "C:/путь/к/ru-marketplace-mcp", "ozon-mcp"],
  },
  "compare-prices": {
    "command": "uv",
    "args": ["run", "--directory", "C:/путь/к/ru-marketplace-mcp", "compare-mcp"],
  },
},
}

Путь пишите с прямыми слешами / или двойными обратными \\. Полный список команд: wb-mcp, ozon-mcp, yandex-mcp, detmir-mcp, avito-mcp, taobao-mcp, megamarket-mcp, lamoda-mcp, dns-mcp, citilink-mcp, compare-mcp, marketplace-mcp.

Claude Code

claude mcp add wildberries -- uv run --directory /путь/к/ru-marketplace-mcp wb-mcp
claude mcp add yandex-market -- uv run --directory /путь/к/ru-marketplace-mcp yandex-mcp
claude mcp add detsky-mir -- uv run --directory /путь/к/ru-marketplace-mcp detmir-mcp
claude mcp add ozon -- uv run --directory /путь/к/ru-marketplace-mcp ozon-mcp
claude mcp add compare-prices -- uv run --directory /путь/к/ru-marketplace-mcp compare-mcp

Cursor — .cursor/mcp.json

{
"mcpServers": {
  "compare-prices": {
    "command": "uv",
    "args": ["run", "--directory", "/путь/к/ru-marketplace-mcp", "compare-mcp"],
  },
},
}

Другой stdio-клиент

Запустите uv run --directory /путь/к/репозиторию <команда>, где команда — одна из wb-mcp, ozon-mcp, yandex-mcp, detmir-mcp, aliexpress-mcp, compare-mcp. Серверы общаются по JSON-RPC через stdin и stdout, диагностику выводят в stderr. Опциональный mpstats-mcp запускается так же, с MPSTATS_MP_AUTH в окружении.

DeepSeek Harness (dsh) — плагин-бандл

В dsh это не запись mcpServers, а слой профиля. Бандл лежит в подкаталоге dsh/ и устанавливается штатным менеджером плагинов (pnpm должен быть на PATH):

dsh plugin --profile web add github:Vladimir-Human/ru-marketplace-mcp#path:/dsh

Сразу после установки появляются 14 навыков и ни одного MCP-инструмента: обе строки MCP отключены, пока не задана переменная RU_MARKETPLACE_MCP_DIR с путём к клону. Так сделано потому, что смонтированный сервер расходуется в каждом запросе: рекомендуемый режим сравнения цен стоит ~0,9 тыс. токенов, полный набор — ~13,6 тыс. Включение и полный режим описаны в dsh/README.md.

После подключения перезапустите клиент и выполните marketplace-mcp doctor. Он запускает канарейку каждого коннектора и возвращает success, drift_detected или inconclusive.

Инструменты

Канарейки *_selfcheck в этом списке не указаны намеренно: они не публикуются по MCP, так как диагностика оператора стоила бы модели ~7,5 тыс. токенов в каждом запросе.

На каждом запросе. Запускает их marketplace-mcp doctor — все разом, из командной строки.

Wildberries — wb_*

Инструмент Что делает
wb_search(query, page) Поиск по тексту, до 100 товаров на страницу с ценами и остатками
wb_card(nm_ids) Пакетный запрос до 100 известных SKU
wb_root_info(nm_id) Находит imt_id (нужен для отзывов) и цветовые варианты
wb_reviews(imt_id, limit, sort) Пул отзывов. Ключ — imt_id, а не nm_id
wb_questions(imt_id, limit, skip, answered_only) Вопросы покупателей и ответы продавца. Тоже по imt_id
wb_seller(supplier_id) Юрлицо, ИНН, КПП, ОГРН, юридический адрес
wb_categories(root, max_depth) Дерево каталога с шардами и запросами самого WB
wb_category_products(shard, query, page, sort, dest) Товары категории по shard и query из wb_categories

wb_seller отвечает на вопрос, который карточка товара скрывает: кто на самом деле продаёт? Возвращает зарегистрированное юрлицо и налоговые номера. Так отличают официальный магазин бренда от перекупщика с похожим названием.

wb_questions закрывает другой пробел. Отзывы говорят, каково владеть товаром; вопросы уточняют, что это вообще за товар — «10 или 16 ампер», «кабель в комплекте?». Ответ продавца часто единственное публичное утверждение об этом. Пул общий для всех вариантов товара, ключ — imt_id из wb_root_info.

wb_category_products замыкает связку с wb_categories: та отдаёт shard и query, это — товары по ним. Формат элементов совпадает с wb_search, поэтому обход категорий и текстовый поиск сравнимы напрямую. Часть крупных разделов WB помечает шардом blackhole — у них нет своей выдачи, и инструмент честно об этом сообщает вместо пустого списка.

Яндекс Маркет — yandex_*

Инструмент Что делает
yandex_search(query, page, limit) Поиск с обеими ценами, рейтингами, продавцами
yandex_card(product_id, include_reviews) Карточка целиком: разбивка по звёздам и отзывы

Две цены, всегда. price_rub платит любой покупатель. price_with_plus требует подписку Яндекс Плюс и обычно на 25–30% ниже. Интерфейс Яндекса показывает вторую крупным шрифтом, поэтому назвать её без оговорки — значит пообещать цену, которую человек без подписки не получит.

rating_stars даёт распределение вида {1: 10, 2: 3, 3: 10, 4: 19, 5: 502}. Из него видно, честная ли средняя 4.8 или за ней прячется кучка единиц.

Детский мир — detmir_*

Инструмент Что делает
detmir_categories(parent, limit, region) Дерево каталога. Начинать отсюда
detmir_category(alias, limit, offset, region) Товары категории с настоящим счётчиком
detmir_card(product_id, region) Цена, рейтинг, наличие онлайн и в магазинах

Регион задаётся на каждый вызов. Цены и особенно наличие в офлайн-магазинах сильно зависят от города: один и тот же товар лежал в 152 магазинах Москвы, 37 Петербурга и 2 Хабаровска. Параметр region перекрывает DETMIR_REGION, так что города можно сравнивать в одной сессии.

Текстового поиска здесь нет, и это намеренно. API Детского мира молча игнорирует любые текстовые фильтры и возвращает весь каталог на 300 тысяч позиций, а сайтовый роут поиска отдаёт 404 с промо-каруселью. Инструмент поиска возвращал бы уверенно неверные товары, поэтому навигация идёт через категории. Подробности в docs/ANTI_BOT.md.

Ozon — ozon_*

Инструмент Что делает
ozon_search(query) Поиск по тексту
ozon_card(sku_or_path) Карточка товара
ozon_reviews(sku_or_path, limit, sort) Отзывы
Ozon отклоняет трафик из дата-центров, поэтому коннектор имеет двухуровневую архитектуру. Сначала
выполняется TLS-имперсонация. Если Cloudflare выдаёт челлендж, запрос выполняется внутри вашего
авторизованного Chrome через DevTools Protocol. Никакие данные не сохраняются: вход осуществляете вы
сами в браузере, который контролируете. Настройка описана в
docs/CDP_SETUP.md.

С российского домашнего IP первый уровень обычно работает, и браузер не требуется.

Авито — avito_*

Инструмент Что делает
avito_search(query, page, location_id, category_id) Поиск объявлений через внутренний js/items API
avito_card(item_id_or_url) Одно объявление: цена, описание, просмотры, продавец
avito_seller(seller_id_or_url) Рейтинг продавца, число отзывов, активные объявления

Авито — это доска объявлений, а не каталог: пула отзывов на товар нет, репутация продавца и является сигналом доверия. Бесплатное или обменное объявление приходит с price_rub: null — никогда не 0, чтобы не оказаться «самым дешёвым» при сравнении. С IP из дата-центра Авито отвечает 403-ошибкой файрвола, поэтому коннектор также двухуровневый: TLS-имперсонация, затем ваш Chrome (как у Ozon).

Taobao — taobao_*

Инструмент Что делает
taobao_search(query, page) Поиск по каталогу Taobao
taobao_card(item_id_or_url) Карточка товара

Поиск на Taobao — это клиентское React-приложение с подписанным mtop API: каждый запрос требует sign, который вычисляется из cookie-токена, поэтому анонимного доступа нет. Все запросы выполняются внутри вашего Chrome, где сайт самостоятельно подписывает запросы. Цены указаны в юанях (CNY) и не конвертируются: жёстко зашитый курс быстро устареет, поэтому сравнение с рублёвыми источниками нужно выполнять вручную.

Мегамаркет, Lamoda, DNS, Ситилинк

Эти четыре площадки считываются через ваш Chrome (CDP). Мегамаркет (megamarket_*) использует мобильный JSON API из-за ServicePipe, и одного пройденного челленджа недостаточно: анонимная сессия API возвращает пустой список, требуется активная авторизация в Мегамаркете. DNS (dns_*) и Ситилинк (citilink_*) отдают отрисованный DOM из-за Qrator; у всех трёх анонимного доступа нет вообще. Lamoda (lamoda_*) работает наполовину: карточки товаров получаются анонимно через GraphQL, а поиск — через Chrome. Chrome с CDP (scripts/start_chrome_cdp.sh) необходим для всех, кроме карточек Lamoda.

Всего через CDP работают восемь источников — эти четыре плюс Taobao, AliExpress, Ozon и Авито, где Chrome используется как запасной уровень: их первый уровень обычно отвечает, а браузер подключается, когда анонимный уровень сталкивается с челленджем. Команда marketplace-mcp doctor из вашего браузера покажет, какие эндпоинты подтверждены.

AliExpress — aliexpress_*

Инструмент Что делает
aliexpress_search(query) Поиск: до 48 карточек с ценами в рублях
aliexpress_card(item_id_or_url) Карточка: цена, рейтинг, число заказов

Данные считываются через ваш Chrome (CDP): x5sec блокирует анонимных клиентов капчей, поэтому коннектор открывает страницу поиска (её не блокируют) и загружает карточку в новой вкладке. Цены указаны в рублях и участвуют в compare_prices. Если карточка содержит название, но не цену — это известная проблема: под нагрузкой x5sec перестаёт отдавать ценовой модуль, коннектор возвращает price_missing, а не выдумывает значение. Цена «N ₽ с купоном» не публикуется в price_rub: там указана обычная цена, о купоне коннектор сообщает отдельно. Тексты отзывов не возвращаются: только рейтинг и число заказов. Как и у остальных CDP-источников, зелёный статус aliexpress_selfcheck подтверждает, что транспорт ответил, — но не гарантирует корректность цены.

Сравнение цен — compare_*

Инструмент Что делает
compare_prices(query, per_source_limit, sources) Все маркетплейсы одновременно с ранжированием
compare_sources() Какие маркетплейсы доступны в текущей установке
compare_prices("кроссовки мужские")

  wildberries      712 ₽   Кроссовки изи дышащие спортивные
  wildberries      814 ₽   Зимние кроссовки теплые с мехом
  yandex_market   2499 ₽   Кеды A-LOW
  yandex_market   3480 ₽   Кеды

  дешевле всего: wildberries 712 ₽, разброс 5858 ₽, complete: true

Маркетплейсы опрашиваются параллельно, и каждый отчитывается сам за себя. Если один заблокирован, сравнение не рушится: complete: false вместе с source_outcomes покажет, что именно вы видите. Подписочные цены в ранжировании не участвуют. Совпадающие предложения по паре (источник, id товара) схлопываются, так что один и тот же товар не занимает два места в ранжировании.

У каждого предложения есть currency (строчный ISO-код, по умолчанию rub) и price_native — цена в этой валюте, как её показывает маркетплейс. Для российских источников она совпадает с price_rub; у Taobao в ней лежит цена в юанях, которую price_rub намеренно оставляет пустой. Раньше юаневую цену забирали и молча выбрасывали, и строка Taobao приходила с пустой ценой без намёка, что цена вообще есть. Теперь юань виден, но в рублёвом ранжировании по-прежнему не участвует: в warnings появляется foreign_currency: … с числом исключённых предложений и причиной. Конвертировать здесь значило бы зашить курс, который молча устареет, — пересчёт за вами.

MPStats — mpstats_*

Аналитика продаж и остатков по SKU Ozon и Wildberries через плагин MPStats. В отличие от всех остальных коннекторов, этот опционален и требует платный аккаунт MPStats: авторизация — одна cookie mp_auth (JWT из залогиненной сессии плагина на mpstats.io), задаётся переменной MPSTATS_MP_AUTH. Без неё инструменты возвращают auth_missing, а сервер запускается как обычно — ни на что другое это не влияет.

Инструмент Что делает
mpstats_item(skus, place, oz_fbs=True) Аналитика за 30 дней по до 100 SKU: заказы, цена, остатки, графики по дням, продавец/бренд
mpstats_warehouses(skus, place) Остатки по складам: FBS (склад продавца) и FBO (склад маркетплейса), last_update

place — ozon или wildberries. Графики длиной 30, от старых к новым: последняя ненулевая ячейка — текущая цена или остаток. Цена и остаток при сплошь нулевом графике ведут себя намеренно по-разному: цена становится None (ложный 0 выиграл бы любое сравнение «где дешевле»), а остаток — 0, потому что «нулевой остаток» это осмысленное показание, а не отсутствие данных. Пустой график даёт None в обоих случаях. Ноль в отдельной ячейке — «нет данных за тот день», а не «значение было нулевым», поэтому сумму за окно считайте по графику. Отсутствие токена и транспортные сбои selfcheck отчитывает как inconclusive, не drift: гоняться за дрейфом схемы, которого не было, не нужно. Токен — секрет платного аккаунта с квотой: не логируйте и не коммитьте его.

Навыки для агента

У каждого коннектора — свой навык в skills/: четырнадцать штук, по одному на источник плюс общий marketplace. Навык это не пересказ README: он объясняет агенту, когда за этот источник вообще браться, чего у источника нет, и каким его ответам нельзя верить без второго взгляда.

Навык Сервер
skills/wb-connector wb-mcp
skills/ozon-connector ozon-mcp
skills/yandex-connector yandex-mcp
skills/detmir-connector detmir-mcp
skills/avito-connector avito-mcp
skills/taobao-connector taobao-mcp
skills/megamarket-connector megamarket-mcp
skills/lamoda-connector lamoda-mcp
skills/dns-connector dns-mcp
skills/citilink-connector citilink-mcp
skills/aliexpress-connector aliexpress-mcp
skills/compare-prices compare-mcp
skills/mpstats-connector mpstats-mcp
skills/marketplace marketplace-mcp

mcp-core — общий рантайм под остальными серверами. Своего навыка у него нет.

Соответствие проверяется тестом (packages/marketplace-connector/tests/test_skills_parity.py): новый коннектор без навыка роняет прогон, как и навык, который называет несуществующий инструмент или забыл существующий. До этого теста навык DNS почти год советовал формат ссылки /product/<24-hex>/ — тот самый шаблон, который чинили как баг.

Навыки поставляются в Docker-образ (/app/skills/), но в колёсах их нет: skills/

находится в корне репозитория. Устанавливаете из PyPI — возьмите навыки из репозитория отдельно.

Настройка

Все параметры задаются переменными окружения с префиксом коннектора. Все необязательные.

Префикс Основные параметры
WB_ TIMEOUT, MIN_GAP, DEFAULT_DEST, NET_RETRIES, MAX_BODY_BYTES, CACHE_TTL, PROXY
YANDEX_ TIMEOUT, MIN_GAP, CACHE_TTL, PROXY
DETMIR_ REGION (RU-MOW, RU-SPE и другие), CACHE_TTL, PROXY
OZON_ TIMEOUT, MIN_GAP, IMPERSONATE, CACHE_TTL, PROXY
AVITO_ TIMEOUT, MIN_GAP, IMPERSONATE, CACHE_TTL, PROXY, LOCATION_ID
TAOBAO_ TIMEOUT, MIN_GAP, CACHE_TTL
ALI_ TIMEOUT, MIN_GAP, CACHE_TTL
MEGAMARKET_ TIMEOUT, MIN_GAP, CACHE_TTL
LAMODA_ TIMEOUT, MIN_GAP, CACHE_TTL, PROXY
DNS_ / CITILINK_ TIMEOUT, MIN_GAP, CACHE_TTL
CHROME_ CDP_HOST, CDP_PORT, SCRAPING_PROFILE, BINARY, HEADLESS, STEALTH
COMPARE_ SOURCE_TIMEOUT
MPSTATS_ MP_AUTH (единственный обязательный — без него инструменты возвращают auth_missing), TIMEOUT, MIN_GAP, CACHE_TTL, PROXY
MCP_ TRANSPORT (stdio по умолчанию, либо http), HTTP_HOST, HTTP_PORT

CHROME_CDP_HOST указывает, куда подключаться CDP-клиенту (по умолчанию 127.0.0.1). Из контейнера используйте chrome (сайдкар) или host.docker.internal — это открывает источники второго уровня (Ozon, Avito, Taobao, Megamarket, Lamoda, DNS, Citilink) в Docker без сетевого режима хоста. Подробности в docs/DEPLOYMENT.md.

*_CACHE_TTL=0 отключает кэш. *_PROXY переопределяет стандартные HTTPS_PROXY и ALL_PROXY — собственный префикс есть у семи коннекторов: WB_, YANDEX_, DETMIR_, OZON_, AVITO_, LAMODA_ и MPSTATS_. У Taobao своего нет намеренно: поиск там подписан и работает через собственный клиент. У Megamarket, DNS и Citilink тоже нет: их трафик идёт через ваш Chrome, а его исходящий трафик — дело настроек браузера. Кэшируются только успешные ответы: запомнить сбой означало бы растянуть секундную проблему на весь TTL.

У Ozon прокси применяется к первому уровню. Второй уровень идёт через ваш собственный Chrome, и его трафик — дело настроек этого браузера.

Секрет один, и тот опциональный. Всем серверам, кроме MPStats, ничего не нужно: нечего настраивать, нечему утечь. У MPStats есть MPSTATS_MP_AUTH — JWT платного аккаунта, и поэтому его место только в переменных окружения клиентской записи: в коде и коммитах его нет и быть не должно.

Разработка

uv sync --all-packages
uv run pytest -q -m "not live and not cdp"    # 1208 офлайн-тестов
uv run pytest -q -m "not live"                # то, что гоняет CI
uv run pytest -q -m "not live" --cov          # покрытие, порог 70% в CI
uv run ruff check . && uv run ruff format --check .
uv run mypy                                   # что проверять — в [tool.mypy] files
uv run mypy --platform win32                  # ловит ошибки, видимые только на Windows
uv run python scripts/check_no_print.py       # запись в stdout ломает JSON-RPC
uv run python scripts/check_versions.py       # одна версия во всех 77 местах

Часть тестов запускает настоящий JS-экстрактор коннектора на сохранённой разметке и проверяет результат по ценам, которые были на странице в тот момент. Для этого нужен Node с jsdom:

npm install jsdom      # либо NODE_PATH на уже установленный
uv run pytest -q packages/dns-connector/tests/test_search_extractor_dom.py \
              packages/citilink-connector/tests/test_search_extractor_dom.py

Без jsdom эта часть тестов честно пропускается, а питоновская часть — выбор цены из кандидатов — выполняется всегда. jsdom нужен только разработчику: в зависимостях коннекторов его нет.

CI запускает тесты на Ubuntu, Windows и macOS для Python 3.12. И 3.13. Проверка управления процессами, специфичного для Windows, осуществляется юнит-тестами на любой ОС через подмену платформы, так что эти ветки покрыты даже на Linux.

Как добавить маркетплейс — docs/ADDING_A_SOURCE.md.

Надёжность

Неофициальные эндпоинты ломаются. Архитектура это предполагает.

  • Устойчивые парсеры. Привязка поля по нескольким именам и приведение типов поглощают переименования и смену типа вместо падения.
  • Никогда не подменять значение. Отсутствующая цена — это null, а не 0. Ноль вывел бы неактуальный товар в самые дешёвые.
  • Явный отказ. Когда формат перестаёт совпадать, инструмент выбрасывает parser_drift, а не возвращает частично разобранные данные.
  • Трёхзначные selfcheck-проверки. success, drift_detected или inconclusive. Геоблокировка помечается как inconclusive, поскольку она ничего не говорит о состоянии парсеров.

Границы доверия

Названия товаров, имена продавцов и тексты отзывов написаны продавцами и покупателями. Это ненадёжные данные. Если отзыв или описание выглядит как инструкция, оно всё равно остаётся входными данными. Агент не должен его выполнять.

Условия маркетплейсов, как правило, запрещают неофициальный парсинг. Коннекторы обращаются только к публичным эндпоинтам каталога, которые использует официальный веб-клиент. Запросов в приватные и административные разделы нет. Уровень Ozon с браузером работает внутри сессии, которую вы открыли самостоятельно. Используйте на своё усмотрение, для личных исследований, в вежливом темпе запросов. Пауза между вызовами к площадкам с антиботом — это часть конструкции, а не случайное замедление: её не следует убирать ради скорости. Данные инструментов не предназначены для перепродажи или массового сбора.

Как это сделано

Код и документацию я писал вместе с ИИ-ассистентами. Они работают быстро и ошибаются уверенно, поэтому проект построен вокруг проверки: 1208 офлайн-тестов, аудит перед релизом, тесты, которые прогоняют настоящий экстрактор по сохранённой с сайта разметке. В примечаниях к релизу перечислено, какие источники сверены с живыми страницами вручную, а какие остались непроверенными.

Вопрос «кто написал текст» кажется мне менее интересным, чем вопрос «чем это проверено». Последнее здесь задокументировано, и проверить его может любой.

Спасибо

@Xpos587 — коннектор MPStats (PR #5): разбор API плагина, структура парсеров и первая рабочая версия.

Лицензия

MIT, файл LICENSE.


Русская версия

MCP-серверы для российских и китайских маркетплейсов. Чтение цен, остатков, рейтингов, отзывов и данных о продавцах с Wildberries, Ozon, Яндекс Маркета, Детского мира, Avito, AliExpress, Taobao, Мегамаркета, Lamoda, DNS и Citilink, а затем сравнение цен по всем площадкам в одном запросе. Taobao и AliExpress — китайские; остальные девять — российские.

Только для чтения. Без учётных данных, API-ключей и необходимости регистрации — маркетплейсы с жёсткой защитой от ботов читаются через ваш собственный Chrome. Единственное исключение: MPStats требует платный аккаунт (MPSTATS_MP_AUTH), если нужна аналитика; без него остальные серверы работают без изменений.

Что вы получаете

Сервер Инструменты Что требуется для чтения Примечания
Wildberries 8 анонимный HTTP Поиск, карточки товаров, отзывы, вопросы покупателей, юридические данные продавца, дерево каталога и списки категорий
Яндекс Маркет 2 анонимный HTTP Цены от нескольких продавцов, распределение звёзд, отзывы
Детский мир 3 анонимный HTTP Детские товары, остатки в офлайн-магазинах, списки категорий
Ozon 3 ваш Chrome; часто без браузера с домашнего IP Поиск, карточки товаров, отзывы
Avito 3 ваш Chrome + российский домашний IP и запросы с паузами — иначе блокировка IP Поиск объявлений, карточки товаров, репутация продавца
Taobao 2 ваш Chrome с активной сессией Taobao Поиск и карточки товаров, цены в юанях
Мегамаркет 2 ваш Chrome с активной сессией — анонимная сессия возвращает пустые данные Поиск и карточки товаров через мобильное API
Lamoda 2 карточки — анонимно (GraphQL), поиск — через ваш Chrome Поиск, карточки товаров с размерами
DNS 2 ваш Chrome (Qrator) Поиск и карточки электроники
Citilink 2 ваш Chrome (Qrator) Поиск и карточки электроники
AliExpress 2 ваш Chrome (x5sec) Поиск и карточки товаров, цены в рублях
Compare 2 агрегирует данные выше «Где дешевле?» в одном запросе
MPStats 2 платный аккаунт MPStats, mp_auth cookie (опционально) Графики продаж/остатков за 30 дней по SKU Ozon/WB, разделение по складам (FBS/FBO)
Анонимно, без браузера: карты Wildberries, Яндекс Маркет, Детский Мир и Lamoda.
Для остальных требуется ваш авторизованный Chrome (CDP). Для Taobao и Мегамаркета
дополнительно нужно войти в сам маркетплейс — без этого Taobao блокирует вход,
а Мегамаркет возвращает пустой результат. Avito также блокирует по IP: с адреса
дата-центра следует прямой отказ, с российского домашнего адреса работает, пока
не начинаются слишком частые запросы. Запросы к источникам CDP распределяются
во времени — серия подряд идущих вызовов ухудшает их работу (в тестировании так
отваливались DNS и Taobao), поэтому коннекторы сами выдерживают паузу между вызовами.
Запустите marketplace-mcp doctor из своей сессии, чтобы узнать текущее состояние.

MPStats выделяется как единственный платный источник: без MPSTATS_MP_AUTH сервер запускается, но его инструменты отвечают auth_missing. Поэтому он опционален — подключайте его, если у вас есть аккаунт; остальные тринадцать серверов этого не замечают.

35 инструментов на базе 13 серверов MCP с stdio-интерфейсом, использующих единую среду выполнения (mcp-core), плюс унифицированный marketplace-mcp, который объединяет их все под одним клиентским интерфейсом. Он добавляет собственный инструмент marketplace_sources — какие коннекторы подключены, а какие отвалились и почему — так что в итоге доступно 36 инструментов: 35 подключённых плюс этот. stdio используется по умолчанию; HTTP-транспорт подключается опционально для удалённого развёртывания — см. docs/DEPLOYMENT.md.

Быстрый старт

Требуется Python 3.12+ и uv.

git clone https://github.com/Vladimir-Human/ru-marketplace-mcp.git
cd ru-marketplace-mcp
uv sync --all-packages
uv run pytest -q -m "not live and not cdp"    # 1208 offline tests, no network needed

Конфигурация клиента повторяет раздел выше о российских сервисах. Каждый сервер — это консольный скрипт (wb-mcp, ozon-mcp, yandex-mcp, detmir-mcp, aliexpress-mcp, compare-mcp), запускаемый через uv run --directory /путь/к/репозиторию <скрипт>. Опциональный mpstats-mcp запускается так же, но с переменной MPSTATS_MP_AUTH в env (платный аккаунт MPStats; без неё инструменты возвращают auth_missing). Команда marketplace-mcp install [claude|claude-code|cursor|dsh] выводит блок с реальным путём к вашему репозиторию — без плейсхолдеров для ручного редактирования — или пути к консольным скриптам в PATH, если установлено как wheel; неизвестное имя клиента отклоняется. Для цели dsh выводится строка cordis.patch.yml вместо JSON mcpServers — см. dsh/README.md.

DeepSeek Harness (dsh) устанавливается как плагин, а не запись mcpServers, из поддиректории dsh/ (pnpm должен быть в PATH):

dsh plugin --profile web add github:Vladimir-Human/ru-marketplace-mcp#path:/dsh

Это сразу даёт 14 навыков и никаких инструментов MCP: обе строки MCP остаются отключёнными, пока RU_MARKETPLACE_MCP_DIR не укажет на клон репозитория. Подключённый сервер оплачивается за каждый запрос — ~0,9 тыс. токенов для рекомендуемого режима сравнения цен, ~13,6 тыс. для полного набора — поэтому подключение остаётся на ваше усмотрение. В dsh/README.md описано, как его включить, а также полный режим.

После подключения запустите marketplace-mcp doctor. Он проверяет канарейку каждого коннектора и сообщает success, drift_detected или inconclusive для каждого.

Инструменты

Канарейки *_selfcheck намеренно отсутствуют в этих таблицах: они не публикуются через MCP, так как диагностика оператора стоила бы модели ~7,5 тыс. токенов на каждый запрос. marketplace-mcp doctor запускает их все из командной строки.

Wildberries — wb_*

Инструмент Назначение
wb_search(запрос, страница) Текстовый поиск, до 100 товаров/страница с ценами и остатками
wb_card(nm_ids) Пакетный запрос до 100 известных SKU
wb_root_info(nm_id) Определяет imt_id (нужен для отзывов) и варианты цвета
wb_reviews(imt_id, лимит, сортировка) Пул отзывов, привязанный к imt_id, а не nm_id
wb_questions(imt_id, лимит, пропуск, только_с_ответами) Вопросы покупателей с ответами продавца, также по imt_id
wb_seller(идентификатор_поставщика) Зарегистрированное юрлицо, ИНН, КПП, ОГРН, юридический адрес
wb_categories(корень, макс_глубина) Дерево каталога с собственными селекторами шардов/запросов WB
wb_category_products(шард, запрос, страница, сортировка, регион) Товары в категории с использованием этих селекторов
wb_seller отвечает на вопрос, который скрывает объявление: кто на самом деле доставляет этот товар? Возвращает зарегистрированное юридическое лицо и налоговые идентификаторы, что позволяет отличить официальный фирменный магазин от перекупщика, торгующего под похожим названием.

wb_questions закрывает другой пробел. Отзывы описывают, каково владеть продуктом; вопросы уточняют, что он из себя представляет — «10А или 16А?», «кабель входит в комплект?» — и ответ продавца часто является единственным публичным подтверждением этого факта. Один пул на imt_id, общий для всех вариантов товара.

wb_category_products завершает цикл, начатый wb_categories: тот инструмент возвращает shard и query Wildberries, а этот получает товары по ним. Элементы имеют тот же формат, что и в wb_search, поэтому обход категорий и текстовый поиск напрямую сопоставимы. Несколько крупнейших разделов Wildberries имеют shard blackhole и не имеют ленты вообще; инструмент сообщает об этом вместо возврата пустого списка.

Яндекс Маркет — yandex_*

Инструмент Назначение
yandex_search(запрос, страница, лимит) Поиск с ценами, рейтингами и продавцами
yandex_card(идентификатор_товара, включить_отзывы) Полная информация плюс распределение звёзд и отзывы

Две цены, всегда. price_rub — это то, что платят все. price_with_plus требует платной подписки Яндекс Плюс и обычно на 25–30% ниже. Яндекс показывает подписчикам более низкую цену, поэтому её безоговорочное цитирование искажает реальную стоимость.

rating_stars показывает распределение, например {1: 10, 2: 3, 3: 10, 4: 19, 5: 502}. Это позволяет понять, заслужена ли средняя оценка 4.8 или она скрывает скопление жалоб.

Детский мир — detmir_*

Инструмент Назначение
detmir_categories(родитель, лимит, регион) Дерево каталога, начинать следует отсюда
detmir_category(псевдоним, лимит, смещение, регион) Товары в категории с реальными итогами
detmir_card(идентификатор_товара, регион) Цена, рейтинг, наличие в онлайн- и офлайн-магазинах

Регион указывается при каждом вызове. Цены и особенно офлайн-наличие сильно зависят от города — один товар может быть в 152 магазинах Москвы, 37 в Санкт-Петербурге и 2 в Хабаровске. Параметр region переопределяет DETMIR_REGION, так что в одной сессии можно сравнивать города.

Текстовый поиск отсутствует намеренно. API Детского мира молча игнорирует все текстовые фильтры и возвращает весь каталог из 300 тысяч товаров; поисковый маршрут на сайте отвечает 404 и показывает рекламный карусель. Инструмент поиска возвращал бы уверенно неверные товары, поэтому обнаружение происходит через категории. См. docs/ANTI_BOT.md.

Ozon — ozon_*

Инструмент Назначение
ozon_search(запрос) Текстовый поиск
ozon_card(артикул_или_путь) Детальная информация о товаре
ozon_reviews(артикул_или_путь, лимит, сортировка) Отзывы

Ozon блокирует трафик из дата-центров, поэтому этот коннектор работает в два этапа: сначала имитация TLS, затем запрос через ваш собственный авторизованный Chrome по протоколу DevTools, если Cloudflare выдаёт вызов. Никакие данные не сохраняются; вы входите в систему самостоятельно в браузере, который контролируете. Настройка: docs/CDP_SETUP.md.

С российского домашнего IP первый этап обычно работает, и браузер не требуется.

AliExpress — aliexpress_*

Инструмент Назначение
aliexpress_search(запрос) Поиск: до 48 карточек с ценами в рублях
aliexpress_card(идентификатор_товара_или_url) Карточка товара: название, цена, рейтинг, количество заказов

Работает через ваш Chrome (CDP): x5sec блокирует анонимных клиентов, поэтому коннектор сначала открывает страницу поиска (которая никогда не блокируется), а затем открывает карточку товара в новой вкладке. Цены указаны в рублях и ранжируются в compare_prices. Карточка с названием, но без цены — известное состояние: при высокой нагрузке x5sec перестаёт отдавать модуль с ценой, и коннектор сообщает price_missing, а не придумывает число. Цена "со скидкой" никогда не попадает в price_rub: туда идёт обычная цена, а скидка указывается как предупреждение. Тексты отзывов не предоставляются; рейтинг и количество заказов — да. Как и в любом источнике CDP, зелёный статус aliexpress_selfcheck подтверждает, что транспорт ответил — но не гарантирует корректность конкретной цены.

Avito — avito_*

Инструмент Назначение
avito_search(query, page, location_id, category_id) Поиск объявлений через внутренний API js/items
avito_card(item_id_or_url) Данные одного объявления: цена, описание, просмотры, продавец
avito_seller(seller_id_or_url) Рейтинг продавца, количество отзывов, активные объявления

Avito — это доска объявлений, а не каталог: здесь нет общего пула отзывов на товар, репутация продавца И есть сигналом доверия. Бесплатные/обменные объявления приходят с price_rub: null — никогда не 0, чтобы не выигрывать в категории "самый дешёвый". С IP дата-центра Avito отвечает фаерволом 403, поэтому используется двухступенчатый транспорт: сначала TLS-имитация, затем ваш Chrome, как и в случае с Ozon.

Taobao — taobao_*

Инструмент Назначение
taobao_search(query, page) Поиск по каталогу
taobao_card(item_id_or_url) Карточка товара

Поиск на Taobao — это React-приложение с подписью mtop: каждый запрос требует sign, который генерируется на основе токена из cookie, поэтому анонимного пути нет. Все чтения выполняются внутри вашего Chrome, где сайт подписывает собственные запросы. Цены остаются в юанях (CNY) и никогда не конвертируются — встроенный курс со временем устаревает, поэтому сравнивайте рублёвые и юаневые предложения вручную.

Megamarket, Lamoda, DNS, Citilink

Эти четыре источника работают через ваш Chrome (CDP). Megamarket (megamarket_*) использует мобильный JSON API за ServicePipe и требует активной авторизации — анонимная сессия возвращает пустые данные. DNS (dns_*) и Citilink (citilink_*) рендерят DOM за Qrator без анонимного доступа. Lamoda (lamoda_*) разделена: карточки товаров доступны через анонимный GraphQL, а поиск — через Chrome. Для всех них, кроме карточек Lamoda, нужен Chrome с CDP (scripts/start_chrome_cdp.sh). Всего через CDP работают восемь источников: эти четыре плюс Taobao, AliExpress, Ozon и Avito, где Chrome используется как запасной вариант, если анонимный путь блокируется.

Кросс-маркетплейсы — compare_*

Инструмент Назначение
compare_prices(query, per_source_limit, sources) Сравнение цен по всем маркетплейсам сразу с ранжированием
compare_sources() Список доступных для запроса маркетплейсов
compare_prices("кроссовки мужские")

  wildberries      712 RUB   Кроссовки изи дышащие спортивные
  wildberries      814 RUB   Зимние кроссовки теплые с мехом
  yandex_market   2499 RUB   Кеды A-LOW
  yandex_market   3480 RUB   Кеды

  cheapest: wildberries 712 RUB, spread 5858 RUB, complete: true

Источники опрашиваются параллельно, и каждый возвращает свой результат. Блокировка одного маркетплейса не нарушает сравнение: complete: false и source_outcomes показывают, что именно доступно. Подписочные цены никогда не выигрывают в ранжировании. Предложения, совпадающие по (источник, идентификатор товара), объединяются, поэтому одно объявление не может занять два места в рейтинге.

Каждое предложение содержит currency (код ISO в нижнем регистре, по умолчанию rub) и price_native — цену в этой валюте, как её указывает маркетплейс. Для российских источников она дублирует price_rub; для Taobao хранит цену в юанях, которую price_rub намеренно оставляет пустой. Раньше цена в юанях извлекалась и молча отбрасывалась, поэтому в строке Taobao отображалась пустая цена без намёка на её существование. Теперь юани указываются, но по-прежнему не сравниваются с рублями: предупреждение foreign_currency: … сообщает, сколько предложений было исключено и почему. Конвертация здесь зафиксировала бы курс, который со временем устареет, поэтому конвертацией занимается вызывающая сторона, если это необходимо.

MPStats — mpstats_*

Аналитика продаж и остатков по SKU Ozon или Wildberries через браузерный плагин MPStats. В отличие от остальных коннекторов, этот опционален и требует платного аккаунта MPStats: аутентификация — это единственная cookie mp_auth (JWT из авторизованной сессии плагина на mpstats.io), которая задаётся через переменную окружения MPSTATS_MP_AUTH. Без неё инструменты возвращают auth_missing, но сервер запускается нормально — на остальные функции это не влияет.

Инструмент Назначение
mpstats_item(skus, place, oz_fbs=True) Аналитика за 30 дней для до 100 SKU: заказы, цена, остатки, графики по дням, продавец/бренд
mpstats_warehouses(skus, place) Разбивка по складам: FBS (склад продавца) vs FBO (склад маркетплейса), last_update
place — это ozon или wildberries. Графики имеют длину 30, от старых к новым: последняя ненулевая ячейка — это текущая цена или остаток. Они намеренно различаются, когда весь график равен нулю: цена становится None (ложный 0 выиграл бы любое сравнение "самый дешёвый"), а остаток становится 0, так как "нет в наличии" — это реальное значение, а не отсутствие данных. Пустой график возвращает None для обоих. Нулевая ячейка означает "нет данных за этот день", а не "значение было равно нулю", поэтому суммируйте график для получения итога за период. Отсутствующий токен или сбой транспорта возвращают inconclusive, а не drift — не нужно гоняться за дрейфом схемы, которого никогда не было. Токен — это секрет платного аккаунта с лимитами: никогда не логируйте и не коммитьте его.

Навыки агента

Каждый коннектор поставляется со своим навыком в директории skills/ — их четырнадцать — по одному на источник плюс общий обзор marketplace. Навык — это не пересказ этого README: он подсказывает агенту, когда вообще обращаться к этому источнику, чего в нём нет и какие из его ответов не стоит принимать на веру без дополнительной проверки.

Навык Сервер
skills/wb-connector wb-mcp
skills/ozon-connector ozon-mcp
skills/yandex-connector yandex-mcp
skills/detmir-connector detmir-mcp
skills/avito-connector avito-mcp
skills/taobao-connector taobao-mcp
skills/megamarket-connector megamarket-mcp
skills/lamoda-connector lamoda-mcp
skills/dns-connector dns-mcp
skills/citilink-connector citilink-mcp
skills/aliexpress-connector aliexpress-mcp
skills/compare-prices compare-mcp
skills/mpstats-connector mpstats-mcp
skills/marketplace marketplace-mcp

mcp-core — это общая среда выполнения, а не сервер, поэтому у него нет навыка.

Соответствие проверяется тестом (packages/marketplace-connector/tests/test_skills_parity.py): новый коннектор без навыка приводит к падению теста, как и навык, который ссылается на несуществующий инструмент или пропускает существующий. До появления этого теста навык DNS несколько месяцев подсказывал операторам использовать /product/<24-hex>/ — тот самый шаблон, который уже был удалён исправлением.

Навыки копируются в Docker-образ (/app/skills/), но не входят в wheel-пакеты: директория skills/ находится в корне репозитория, а не внутри пакетов. Установка из PyPI подразумевает отдельное скачивание навыков из репозитория.

Конфигурация

Каждая настройка — это переменная окружения с префиксом, специфичным для коннектора. Все параметры необязательные.

Префикс Общие параметры
WB_ TIMEOUT, MIN_GAP, DEFAULT_DEST, NET_RETRIES, MAX_BODY_BYTES, CACHE_TTL, PROXY
YANDEX_ TIMEOUT, MIN_GAP, CACHE_TTL, PROXY
DETMIR_ REGION (RU-MOW, RU-SPE и другие), CACHE_TTL, PROXY
OZON_ TIMEOUT, MIN_GAP, IMPERSONATE, CACHE_TTL, PROXY
AVITO_ TIMEOUT, MIN_GAP, IMPERSONATE, CACHE_TTL, PROXY, LOCATION_ID
TAOBAO_ TIMEOUT, MIN_GAP, CACHE_TTL
ALI_ TIMEOUT, MIN_GAP, CACHE_TTL
MEGAMARKET_ TIMEOUT, MIN_GAP, CACHE_TTL
LAMODA_ TIMEOUT, MIN_GAP, CACHE_TTL, PROXY
DNS_ / CITILINK_ TIMEOUT, MIN_GAP, CACHE_TTL
CHROME_ CDP_HOST, CDP_PORT, SCRAPING_PROFILE, BINARY, HEADLESS, STEALTH
COMPARE_ SOURCE_TIMEOUT
MPSTATS_ MP_AUTH (единственный обязательный — без него инструменты возвращают auth_missing), TIMEOUT, MIN_GAP, CACHE_TTL, PROXY
MCP_ TRANSPORT (stdio по умолчанию или http), HTTP_HOST, HTTP_PORT
*_CACHE_TTL=0 отключает кэширование. *_PROXY переопределяет стандартные
HTTPS_PROXY/ALL_PROXY — семь коннекторов поддерживают свои: WB_, YANDEX_, DETMIR_,
OZON_, AVITO_, LAMODA_ и MPSTATS_. У Taobao по дизайну нет прокси,
а у Megamarket, DNS и Citilink его тоже нет:
их трафик проходит через ваш собственный Chrome, чей выходной трафик определяется
настройками этого браузера. Кэшируются только успешные чтения: запоминание ошибки
растянуло бы одномоментный сбой на весь период TTL.

Прокси Ozon применяется к первому уровню. Второй уровень работает внутри вашего собственного Chrome, чей выходной трафик определяется настройками этого браузера, а не нашими.

Контейнеры. CHROME_CDP_HOST указывает клиенту CDP на Chrome (по умолчанию 127.0.0.1; используйте chrome или host.docker.internal внутри Docker). Эта единственная переменная открывает доступ к источникам второго уровня — Ozon, Avito, Taobao, Megamarket, Lamoda, DNS, Citilink и AliExpress — из контейнера без сетевого доступа к хосту. См. docs/DEPLOYMENT.md.

Один секрет, и он опционален. Всем серверам, кроме MPStats, ничего не нужно: ничего настраивать, ничего не раскрывать. Только у MPStats есть MPSTATS_MP_AUTH — JWT платного аккаунта — он должен находиться только в переменных окружения клиента, никогда в коде или коммитах.

Разработка

uv sync --all-packages
uv run pytest -q -m "not live and not cdp"    # 1208 offline tests
uv run pytest -q -m "not live"                # what CI runs
uv run pytest -q -m "not live" --cov          # coverage, CI enforces a 70% floor
uv run ruff check . && uv run ruff format --check .
uv run mypy                                   # the tree lives in [tool.mypy] files
uv run mypy --platform win32                  # catches Windows-only type errors
uv run python scripts/check_no_print.py       # a print() breaks JSON-RPC
uv run python scripts/check_versions.py       # one version across all 77 places

Некоторые тесты выполняют реальный JavaScript-экстрактор коннектора на захваченной разметке и проверяют вывод на соответствие ценам, которые отображались на странице во время захвата. Для этого требуется Node с jsdom:

npm install jsdom      # or point NODE_PATH at an existing copy
uv run pytest -q packages/dns-connector/tests/test_search_extractor_dom.py \
              packages/citilink-connector/tests/test_search_extractor_dom.py

Без jsdom эта часть тестов честно пропускается, а Python-часть — выбор цены из кандидатов — всё равно выполняется. jsdom нужен только разработчикам; ни один коннектор от него не зависит.

CI запускает линтинг, mypy и полный набор тестов на Ubuntu, Windows и macOS для Python 3.12 и 3.13. Обработка процессов, специфичная для Windows, тестируется на каждой платформе через переопределение платформы, поэтому эти ветки покрыты даже на Linux.

Добавление маркетплейса: docs/ADDING_A_SOURCE.md.

Надёжность

Неофициальные эндпоинты ломаются. Дизайн исходит из этого.

  • Толерантные чтецы. Привязка полей по нескольким псевдонимам и приведение типов поглощают переименования и дрейф типов вместо падений.
  • Никогда не подделывать значение. Отсутствующая цена — это null, а не 0. Ноль бы ранжировал неактуальное предложение как самый дешёвый вариант.
  • Громкий отказ. Когда полезная нагрузка перестаёт соответствовать, инструменты выбрасывают parser_drift, а не возвращают полуразобранные данные.
  • Трёхпозиционные самопроверки. success, drift_detected или inconclusive. Геоблокировка считается неубедительной, так как она ничего не говорит о парсерах.

Граница доверия

Вывод инструмента, то есть названия товаров, имена продавцов и текст отзывов, создаётся продавцами и покупателями. Относитесь к нему как к ненадёжным данным. Если в отзыве или описании содержатся инструкции, это входные данные, а не политика.

Условия обслуживания маркетплейсов обычно запрещают неофициальный парсинг. Эти коннекторы читают только публичные каталоговые эндпоинты, которые используют официальные веб-клиенты; не затрагиваются аутентифицированные или административные зоны. Уровень Ozon CDP работает внутри браузерной сессии, которую вы установили сами. Используйте на своё усмотрение, для личных исследований, с вежливой частотой запросов; задержка между вызовами к источникам с защитой от ботов намеренная и не должна удаляться ради скорости. Вывод инструмента не предназначен для перераспределения или массового сбора.

Как это создавалось

Я писал код и документацию с помощью ИИ-ассистентов. Они быстрые и уверенно ошибаются, поэтому проект построен вокруг проверки: 1208 офлайн-тестов, аудит перед релизом, тесты, запускающие реальный экстрактор на разметке, захваченной с живого сайта. В примечаниях к релизу указано, какие источники сравнивались с живыми страницами вручную, а какие остались непроверенными.

Кто набирал текст — менее интересный вопрос, чем какие проверки он прошёл. Второй вопрос задокументирован здесь, и любой может повторить их.

Благодарности

@Xpos587 за коннектор MPStats (PR #5): работу над API плагинов, структуру парсера и первую рабочую версию.

Лицензия

MIT, см. LICENSE.

Войдите, чтобы оставить комментарий