by Vladimir-Human (community) Claude Desktop, Claude Code, OpenCode, любой MCP-клиент, Python 3.12+, Chrome (для анти-бота)
Одиннадцать маркетплейсов как MCP-серверы: Wildberries, Ozon, Яндекс Маркет, Детский мир, Авито, AliExpress, Taobao, Мегамаркет, Lamoda, DNS, Ситилинк. Плюс сравнение цен по всем сразу. Только чтение, ключи не нужны.
Shopping-агент MCP на стороне покупателя, рекламно-нейтральный: детерминированное ранжирование, подписанные покупательские мандаты, локальный аудиторский след.
MCP-сервер расширения 1С: Platform Tools для агентов Visual Studio Code (Cursor) по IPC.
MCP-сервер для доступа к актуальной документации библиотек прямо в контексте LLM. Вместо устаревших обучающих данных — …
Официальный MCP от команды Chrome DevTools: дает AI-агентам полный доступ к инструментам разработчика браузера. Автоматизация через …
# вариант 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"
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
"
Каждый сервер — консольная команда, поэтому пути в конфиге не зашиваются.
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 — все разом, из командной строки.
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_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_search(query, page) |
Поиск по каталогу Taobao |
taobao_card(item_id_or_url) |
Карточка товара |
Поиск на Taobao — это клиентское React-приложение с подписанным mtop API: каждый запрос
требует sign, который вычисляется из cookie-токена, поэтому анонимного доступа нет.
Все запросы выполняются внутри вашего Chrome, где сайт самостоятельно подписывает запросы. Цены указаны в
юанях (CNY) и не конвертируются: жёстко зашитый курс быстро устареет, поэтому
сравнение с рублёвыми источниками нужно выполнять вручную.
Эти четыре площадки считываются через ваш 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_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_*Аналитика продаж и остатков по 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>/ — тот самый шаблон, который чинили как баг.
/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, а не возвращает частично разобранные данные.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 запускает их все из командной строки.
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_search(запрос) |
Текстовый поиск |
ozon_card(артикул_или_путь) |
Детальная информация о товаре |
ozon_reviews(артикул_или_путь, лимит, сортировка) |
Отзывы |
Ozon блокирует трафик из дата-центров, поэтому этот коннектор работает в два этапа: сначала имитация TLS, затем запрос через ваш собственный авторизованный Chrome по протоколу DevTools, если Cloudflare выдаёт вызов. Никакие данные не сохраняются; вы входите в систему самостоятельно в браузере, который контролируете. Настройка: docs/CDP_SETUP.md.
С российского домашнего IP первый этап обычно работает, и браузер не требуется.
aliexpress_*| Инструмент | Назначение |
|---|---|
aliexpress_search(запрос) |
Поиск: до 48 карточек с ценами в рублях |
aliexpress_card(идентификатор_товара_или_url) |
Карточка товара: название, цена, рейтинг, количество заказов |
Работает через ваш Chrome (CDP): x5sec блокирует анонимных клиентов, поэтому коннектор сначала открывает страницу поиска (которая никогда не блокируется), а затем открывает карточку товара в новой вкладке. Цены указаны в рублях и ранжируются в compare_prices. Карточка с названием, но без цены — известное состояние: при высокой нагрузке x5sec перестаёт отдавать модуль с ценой, и коннектор сообщает price_missing, а не придумывает число.
Цена "со скидкой" никогда не попадает в price_rub: туда идёт обычная цена, а скидка указывается как предупреждение. Тексты отзывов не предоставляются; рейтинг и количество заказов — да. Как и в любом источнике CDP, зелёный статус aliexpress_selfcheck подтверждает, что транспорт ответил — но не гарантирует корректность конкретной цены.
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_search(query, page) |
Поиск по каталогу |
taobao_card(item_id_or_url) |
Карточка товара |
Поиск на Taobao — это React-приложение с подписью mtop: каждый запрос требует sign, который генерируется на основе токена из cookie, поэтому анонимного пути нет. Все чтения выполняются внутри вашего Chrome, где сайт подписывает собственные запросы. Цены остаются в юанях (CNY) и никогда не конвертируются — встроенный курс со временем устаревает, поэтому сравнивайте рублёвые и юаневые предложения вручную.
Эти четыре источника работают через ваш 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_*Аналитика продаж и остатков по 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.