RTP DesportoPortugal qualificado para `oitavos` do Europeu de voleibol após derrota de IsraelInquirerMarcos yet to decide on fuel excise tax suspension – Palaceוואלהבגלל תקלה באתר העירייה: המידע רפואי ונתוני הרווחה של תושבי בית שמש היו חשופים לציבורESPNFacts vs. Feelings: What eight notable Week 1 performances mean for Week 2The Jerusalem PostNetanyahu’s New York UN visit sees unusual US Secret Service security involvementESPN DeportesEl mayor éxito y la mayor decepción de los 30 equiposDaily MaverickGROUNDUP: The N1 town’s decade of decay — Beaufort WestBollywood HungamaThe Vvaan Trailer: Sidharth Malhotra and Tamannaah Bhatia starrer explores Indian folklore, supernatural elements and adventure; watchCollider‘Anaconda’ Meets ‘Jurassic Park’ in New Creature Feature With a Truly Gigantic Predator [Exclusive]SCMP ChinaChina uses 100,000 home-grown AI chips to build leading weather forecast systemDeadlineChris Harrison’s Dating Series ‘The Vow’ Sets Premiere On Fox Nation
The Daily Newsstand · Free, Always
Wednesday, September 16, 2026

Штрихкод есть, базы нет: как я собирал справочник продуктов для домашнего приложения

Translate

Я делаю приложение для кухни: что лежит дома, до какого числа, что купить и что из этого приготовить. Самая скучная его часть оказалась самой трудной: чтобы человек навёл камеру на пачку гречки и получил «Гречка ядрица, 900 г, 313 ккал», нужна база штрихкодов российских продуктов. Я был уверен, что она где-то есть. Её нет. Ниже - что я проверял, что из этого работает и как в итоге устроен справочник, в котором сейчас 258 тысяч позиций.

Где я искал и что нашёл

Проверял живыми запросами, а не по описаниям. Итог одной таблицей:

Источник

Что там

Годится

Open Food Facts, российская часть

35 тысяч товаров, КБЖУ примерно у трёх из четырёх в выборке

да, но это в основном крупные бренды

Открытая база с Хабра (1,8 млн кодов, CSV на 244 МБ)

категория, бренд, название, артикул

нет: автор исключил продукты, лекарства, одежду и обувь намеренно

Национальный каталог «Честного знака»

вся маркированная молочка и вода

нет: API только по договору интегратора

GS1 Russia / GEPIR

владелец префикса

нет: отдаёт компанию, а не товар

opengtindb.org

немецкий рынок

нет: регистрация, русских товаров нет

barcode-list.ru

поиск по коду и по названию

да, и это главная находка

С barcode-list.ru история такая: сайт отвечает по коду за 0,4 секунды, robots.txt разрешает обход целиком с Crawl-delay: 10, а на один код отдаёт несколько вариантов названия с рейтингом. Выкачивать его целиком я не стал - незачем. Вместо этого база собирается ленивым догоном: человек сканирует код, которого у меня нет, промах уходит в очередь, воркер спрашивает barcode-list с паузой в десять секунд и кладёт ответ в кэш. Второй человек с той же пачкой получает название уже из моей базы. Никаких выгрузок и договоров: база растёт ровно на то, что люди сканируют.

Отдельная строка - магазины. Я хотел взять каталоги сетей, где люди реально покупают: там и штрихкод, и название, и часто КБЖУ. Ашан и Лента за Qrator, Самокат, Пятёрочка и Чижик за ServicePipe с JS-челленджем на первом же запросе, у «Марии-Ра» онлайн-каталога нет вовсе. Открытыми оказались четыре каталога - METRO, Магнит, Быстроном и Добрянка, и только у METRO есть и штрихкод, и КБЖУ в одной карточке. Из них собрано ядро: 258 тысяч позиций, у 242 тысяч есть EAN-13, КБЖУ заполнены у 13 тысяч. Остальное дозаполняется по мере сканов.

Чек вместо каталога

Заметная часть покупок у людей вокруг меня приезжает из Самоката, а штрихкодов у его собственной фасовки нет. Зато есть чек. Электронный чек Платформы ОФД открывается по ссылке без авторизации и содержит полный состав покупки - ровно то, что я не смог забрать из каталога.

Устроен он занятно: состав лежит в скрытом div экранированным HTML, после раскодирования это лента из 182 вложенных таблиц. JSON-эндпоинта нет, QR с бумажного чека требует авторизации в ФНС, так что единственный вход - ссылка на электронный чек. Парсер режет ленту на позиции, нормализатор названий (тот же, что чистит каталоги магазинов) сопоставляет «Йогурт натур. 3,2% 130г» с типом продукта из справочника, и человек видит экран подтверждения: что опознано, что нет, что добавить в запасы. Живой прогон на настоящем чеке из 21 позиции дал 14 записей в запасах: три йогурта (130, 140 и 140 г) слились в один на 410 г.

Краудсорсинг без личных данных

Когда сканер промахивается мимо всех источников, приложение просит человека описать продукт: тип из справочника, вес, срок. Раньше эта пачка оставалась безымянной строкой, теперь описание уезжает на сервер парой «код → тип» - без имени, без времени, без состава семьи. Пара попадает в модерацию, и только после одобрения становится общей. Модерация - Laravel и Filament поверх того же Postgres, но решение принимает не PHP, а хранимая процедура: одна транзакция на одобрение, золотую запись и лог. Правил модерации в админке нет намеренно, иначе они разъедутся с мобильным клиентом.

Сколько это даёт? Честно - пока мало: 135 сканов от 10 устройств. База штрихкодов - актив на годы, а не на месяц, и она не нужна человеку, который ведёт только список покупок. Об этом ниже.

Офлайн-first, и почему это было обязательно

Холодильник стоит на кухне, а кухня - часто худшее место для Wi-Fi в квартире. Поэтому приложение читает только локальную базу (drift поверх SQLite), а каждая запись идёт в две таблицы: в локальную и в очередь операций. Движок синка реплеит очередь батчами в Supabase и подтягивает чужие изменения; конфликты решаются по updated_at, последний пишет.

Две грабли, на которые стоило наступить один раз:

  1. Сетевую ошибку и отказ сервера нельзя лечить одинаково. У очереди есть карантин: если сервер отверг операцию (RLS, валидация), счётчик попыток растёт и после порога операция уходит в dead-letter с кнопкой «Повторить». Транзиентная ошибка сети счётчик не трогает. Пока это было одним путём, валидные офлайн-правки улетали в карантин из-за лифта.

  2. Клиент слал локальное время без суффикса Z, Postgres читал его как UTC. Устройство в UTC+7 всегда выигрывало у остальных при слиянии, устройство западнее Гринвича всегда проигрывало. Правится одной строкой toUtc(), но данные на бою пришлось выравнивать руками, а метки «из будущего» лезли ещё неделю - с телефонов, которые не обновились.

Supabase self-hosted на своём VPS: Postgres, PostgREST, GoTrue, realtime. Схемой владеют только миграции, RLS на каждой таблице, аккаунт создаётся анонимно при первом запуске - регистрации нет, потому что для списка покупок она не нужна.

Аналитика, которая не знает, что вы едите

Продуктовые события уезжают в AppMetrica, но словарь событий закрытый: item_added, scan_finished, shopping_list_closed. Параметры - только из перечислений, свободный текст в событие не собрать физически: конструктор события приватный, фабрики типизированы, тест проверяет, что ни одно значение не выходит за словарь. Названия продуктов, аллергены, диеты, имена - не уходят никуда. Идентификатор установки не связывается с аккаунтом. Это не благотворительность, а 152-ФЗ и здравый смысл: что лежит у человека в холодильнике, мне знать незачем.

Что дальше

Приложение бесплатное, без рекламы и покупок, стоит в RuStore, в Google Play идёт закрытый тест, App Store впереди. Код закрыт, но подход к базе открыт здесь целиком - повторить можно за вечер. Если у вас есть источник российских продуктов со штрихкодами, которого нет в таблице выше, - напишите, я проверю живым запросом и допишу.

View the original on Хабр

KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.