BirBozor
Блог · Строим публично

Как мы матчим SKU между 5 маркетплейсами (и почему это сложнее чем кажется)

B
Diyor Khaydarov
8 июня 2026 г. · 11 мин

Это было в марте, на третий месяц разработки. Я открыл админку, вбил в поиск «iPhone 15 Pro Max 256» и получил пять разных карточек из пяти разных магазинов. Уверен, что это один и тот же телефон, я был только потому, что его мне кто-то накануне посоветовал и я знал точную модель. Алгоритм об этом не догадывался. Для алгоритма это были пять разных строк, и ни одна из них не совпадала с другой.

На Uzum это было «iPhone 15 Pro Max 256ГБ Чёрный». На Olcha — «Apple iPhone 15 Pro Max (256GB, Space Black)». На Wildberries UZ — «iPhone 15 Pro Max 256GB Космический серый». На Asaxiy — «Смартфон Apple iPhone 15 Pro Max, 256 Gb, Black Titanium». На Texnomart — «iPhone 15 Pro Max 256 Gb (чёрный титан)». Один товар. Пять написаний. Пять разных слов для одного и того же оттенка корпуса. Английский, русский, кириллица, латиница, скобки и без скобок, «Gb» и «ГБ», «Pro Max» и «ProMax».

В этот момент я понял, что задача матчинга SKU между маркетплейсами — это не пятничная задачка на регулярки. Это инженерное ядро Birbozor. Ниже — как мы её решаем сейчас, что не работает, что работает, и какие цифры мы получаем на боевом каталоге в 340 тысяч SKU.

Что должно матчиться

Прежде чем писать алгоритм, нужно договориться, что считается «одним и тем же товаром». Мы определяем SKU-пару как совпадение по четырём осям: бренд и модель, объём памяти или ёмкость, цвет, состояние (новый, восстановленный, витринный).

Это звучит банально, пока не сталкиваешься с реальностью. «iPhone 15 Pro Max 256GB Чёрный» и «iPhone 15 Pro Max 256GB Чёрный титан» — это один и тот же товар. А «iPhone 15 Pro Max 256GB Чёрный» и «iPhone 15 Pro 256GB Чёрный» — это разные товары, хотя строки отличаются на четыре символа. Алгоритм должен понимать, что «Pro Max» — это слово-водораздел, а «титан» в конце — это уточнение того же цвета.

Дополнительно есть «эквивалентные» цвета: Apple переименовывает оттенки от поколения к поколению, маркетплейсы вписывают в карточку либо официальное название, либо то, как видит его контент-менеджер. «Космический серый» 14-го поколения и «Чёрный титан» 15-го поколения — это разные цвета на разных моделях. А «Space Black» и «Чёрный» на одной и той же модели — это один цвет.

Почему ML alone не хватает

Первая и самая популярная идея, которую мы попробовали и отбросили — взять multilingual sentence-transformers (paraphrase-multilingual-MiniLM-L12-v2), посчитать эмбеддинги названий и матчить по косинусной близости. На бумаге это выглядит чисто.

На практике эмбеддинг-модель даёт 0,85 косинусной близости между «iPhone 15 Pro 128GB Black» и «iPhone 15 Pro Max 128GB Black». 0,91 между «Samsung Galaxy S24» и «Samsung Galaxy S25». 0,88 между «AirPods Pro» и «AirPods Pro 2». То есть для модели «Pro» и «Pro Max» — почти синонимы, потому что в обучающем корпусе они стоят в одинаковом контексте. Это не баг — это отражение того, как модель обучалась. Но для матчинга цен это катастрофа: спутав S24 и S25, мы покажем покупателю «скидку» там, где её нет.

Второе ограничение — отсутствие GTIN (штрихкода) в карточках узбекских маркетплейсов. На Amazon или eBay матчинг по GTIN решает 80% задачи за нас. У нас GTIN есть в данных от Firecrawl примерно у 6% карточек, и почти всегда у бренд-моделей. На остальные 94% его не существует.

Вывод: эмбеддинги — необходимый, но недостаточный сигнал. Они дают нам кандидатов. Финальное решение всегда принимает rule-based слой со строгими проверками памяти, размера, цвета, и иерархии «Pro / Pro Max».

Pipeline

Pipeline у нас сейчас выглядит так — четыре стадии с двумя точками выхода в очередь ручной проверки.

Первая стадия — нормализация. Заголовок очищается от мусора: эмодзи, повторы пробелов, маркетинговые довески («Хит продаж», «-30%», «Топ»), скобки с гарантией. Кириллические бренды приводятся к латинице (Эппл → Apple, Самсунг → Samsung). Обозначения объёма памяти приводятся к одному формату (256ГБ, 256GB, 256 Gb, 256 гигабайт → 256gb). Цвета прогоняются через словарь синонимов из примерно 400 пар, который мы накапливаем вручную. На выходе получается канонический токен-вектор: бренд, модель-семейство, модификатор (Pro / Pro Max / Plus / Ultra), память, цвет, год.

Вторая стадия — эмбеддинг и top-K retrieval. Канонический токен-вектор кодируется multilingual моделью, и мы ищем top-50 ближайших кандидатов в pgvector. Это не финальный ответ — это сужение поискового пространства с 340 тысяч до 50.

Третья стадия — rule-based фильтр. Из 50 кандидатов оставляем только тех, у кого совпадает бренд (точно), совпадает модификатор (Pro и Pro Max — никогда не одно и то же), совпадает объём памяти (точно), и цвет либо точно совпадает, либо находится в одном кластере цветов-синонимов. Здесь обычно из 50 остаются 1–3.

Четвёртая стадия — confidence score. Если после фильтра остался ровно один кандидат с косинусной близостью ≥ 0,95 — матч уходит в продакшн автоматически. Если кандидатов несколько, или близость 0,80–0,95, или цвет нашёлся через нечёткий синоним — пара уходит в очередь ручной проверки. Очередь обрабатывается человеком (сейчас это я и ещё один контрактник, по часу в день каждый).

Edge cases

Самый болезненный класс кейсов — мультиязычные титулы. Узбекские маркетплейсы свободно мешают узбекский на латинице, узбекский на кириллице и русский в одном поле. «Muzlatkich Samsung 350L» и «Холодильник Samsung 350Л» и «Samsung Refrigerator 350L» — это один и тот же товар, и нормализатор должен это понимать. Мы держим словарь категорийных слов на трёх языках, около 1200 пар, и расширяем его, когда находим новое расхождение в логах.

Второй класс — bundle-карточки. Продавец продаёт телефон в комплекте с наушниками и чехлом, а заголовок выглядит как обычный SKU телефона. Если матчить такой бандл с одиночным SKU телефона на другом маркетплейсе, цена будет выглядеть «дороже на 400 тысяч» по непонятной причине. Мы детектируем bundle по ключевым словам в заголовке и в описании («комплект», «в подарок», «+ чехол», «+ наушники») и метим такие карточки отдельным флагом.

Третий класс — refurbished и витринные. Восстановленный iPhone и новый iPhone могут иметь идентичные заголовки, особенно если продавец на маркетплейсе не указал статус явно. Мы парсим описание и поле «состояние», но в 4% карточек этот сигнал отсутствует, и нам приходится оставлять матч в подвешенном состоянии до ручной проверки.

Четвёртый класс — контрафакт. Карточка называется «Adidas Originals Ozweego», а по фото и цене это явно подделка за 280 тысяч сум. Алгоритм матчинга такие карточки матчит с настоящим Adidas, и в результате на графике появляется аномально дешёвая «акция». Мы фильтруем такие случаи через правило цены: если SKU в одном магазине стоит более чем в 2,5 раза дешевле медианы по остальным четырём — матч ставится на ручную проверку, а не публикуется автоматически. Это компромисс: мы теряем некоторое количество настоящих скидок, но защищаем пользователя от ложного сигнала.

Цифры по нашему prod-каталогу

На данный момент в каталоге Birbozor 340 тысяч SKU, собранных через Firecrawl с пяти маркетплейсов: Uzum, Olcha, Asaxiy, Texnomart, Mediapark.

Из них около 62 тысяч SKU имеют хотя бы одну подтверждённую кросс-маркетплейсовую пару — то есть тот же товар найден минимум на двух площадках. Это 18% каталога. Цифра кажется маленькой, но она объяснима: длинный хвост каталога — это региональные продавцы и одиночные карточки, которые в принципе нигде кроме одного маркетплейса не существуют.

Очередь ручной проверки забирает около 3% от всех попыток матчинга. На 500-парном аудит-сете, который мы вручную разметили в феврале, false-positive rate (ложные матчи, прошедшие в продакшн) — менее 0,5%. False-negative (упущенные матчи, которые должны были найтись) — около 7%. Мы оптимизируем именно в эту сторону: лучше упустить матч, чем показать пользователю неверную «скидку».

Стоимость одного цикла обновления цен по всему каталогу — около 18 долларов в Firecrawl-кредитах, занимает 4 часа. Мы прогоняем его дважды в день.

Что дальше

Следующая большая ставка — image-based matching. У большинства карточек есть несколько фотографий товара, и хеш-сравнение картинок с pHash + сегментацией решает несколько проблем сразу: помогает на бандлах (если фото показывает три коробки, это не одиночный SKU), помогает на контрафакте (фото настоящего и поддельного Adidas хешируются по-разному), и помогает на цветах, которые продавец не вписал в заголовок. Прототип уже работает на 5 тысячах SKU.

Параллельно запускаем OCR по фотографиям упаковки, чтобы вытащить GTIN с фоток коробки в случаях, когда продавец его не указал. Это даст ещё одну точку строгой проверки и позволит снизить порог по эмбеддингу без потери точности.

Если вы строите похожее

Мы в публичных репозиториях постепенно выкладываем компоненты pipeline (нормализатор, словари синонимов, аудит-набор) — следить за прогрессом удобнее всего через github.com/birbozor и Telegram-канал @birbozor_dev, где я раз в неделю пишу о том, что сломалось и что починили. Если у вас похожая задача в другой стране — заходите, обсудим.

Из статьи

Хотите видеть честную цену?

Сохраните товар в BirBozor — мы покажем минимум за 90 дней рядом с текущим ценником.

Скачать
Топ-5 скидок дня — в Telegram

Подписаться @birbozor_uz

Перепост — пожалуйста, со ссылкой на birbozor.uz/blog

Читайте также

Семья
Подготовка к школе 2026: бюджет родителя — где можно сэкономить
8 мин
Гайд
Электромобиль в Узбекистане 2026: цены, льготы и руководство по зарядке
7 мин
Гайд
Как выбрать робот-пылесос 2026: Pa, навигация и цены — практический гайд
7 мин
Как мы матчим SKU между 5 маркетплейсами (и почему это сложнее чем кажется) | BirBozor