Всё началось с того, что знакомый музыкант спросил: «Где взять UPC-код релиза? Дистрибьютор потерял, а мне нужно для перевода каталога». Я полез искать — и обнаружил, что простого способа узнать UPC-код нет. Spotify его хранит, но не показывает в интерфейсе. Яндекс Музыка — аналогично. Существующие веб-сервисы поддерживают одну-две платформы и работают через раз.
Я решил написать Telegram-бота, который решает задачу универсально: кидаешь ссылку с любой музыкальной платформы — получаешь UPC-код за секунды. Казалось бы, задача на вечер. В реальности — три недели и парсеры для десяти платформ с совершенно разными API.
Что такое UPC-код и зачем он нужен
Для тех, кто не в музыкальной индустрии: UPC (Universal Product Code) — это уникальный штрих-код, который присваивается каждому музыкальному релизу. Не треку, а именно релизу: альбому, синглу, EP. Это как ISBN для книг, только для музыки.
UPC-код нужен музыкантам в нескольких ситуациях: при переходе между дистрибьюторами (чтобы не потерять историю стримов), при регистрации в организациях по управлению правами, для настройки пресейвов и для корректного учёта продаж. Проблема в том, что дистрибьюторы не всегда сообщают этот код артисту, а найти UPC-код самостоятельно — целый квест.
Помимо UPC, существует ISRC (International Standard Recording Code) — уникальный код для каждого отдельного трека. Если UPC — это «паспорт альбома», то ISRC — «паспорт песни». Оба кода нужны для корректной работы с правами и роялти. Бот умеет находить и то, и другое.
Какие платформы поддерживает бот
Главная техническая задача — универсальный парсер ссылок. Пользователь кидает ссылку, бот должен понять, с какой платформы она пришла, извлечь идентификатор и найти UPC-код релиза. Поддерживаемые платформы:
Spotify
Apple Music
Яндекс Музыка
YouTube Music / YouTube
Deezer
Tidal
SoundCloud
VK Музыка
Звук (Сбер Звук)
Amazon Music
У каждой платформы — свой формат ссылок, свои API (или их отсутствие), свои подводные камни.
Архитектура
Telegram (aiogram 3.x) ↓ URL Parser → определяет платформу ↓ Platform Resolver → извлекает метаданные ↓ UPC/ISRC Matcher → сопоставляет данные из нескольких источников ↓ Redis (кеш) + PostgreSQL (аналитика)
Ключевое архитектурное решение — разделить парсинг ссылки и поиск метаданных. URL Parser только определяет платформу и извлекает ID. Platform Resolver идёт за данными. Matcher сопоставляет и проверяет.
Парсер ссылок: 10 платформ — 10 форматов
Это первое, что пришлось решить. Каждая платформа имеет свой формат URL, причём у большинства — несколько вариантов:
import re from dataclasses import dataclass from enum import Enum class Platform(Enum): SPOTIFY = "spotify" APPLE_MUSIC = "apple_music" YANDEX_MUSIC = "yandex_music" YOUTUBE_MUSIC = "youtube_music" DEEZER = "deezer" TIDAL = "tidal" SOUNDCLOUD = "soundcloud" VK_MUSIC = "vk_music" ZVUK = "zvuk" AMAZON_MUSIC = "amazon_music" @dataclass class ParsedLink: platform: Platform content_type: str # "album", "track", "artist" content_id: str PATTERNS = [ # Spotify (r"open\.spotify\.com/(album|track)/([a-zA-Z0-9]+)", Platform.SPOTIFY), # Apple Music (r"music\.apple\.com/.+/(album|song)/.+?/(\d+)", Platform.APPLE_MUSIC), # Яндекс Музыка (r"music\.yandex\.\w+/album/(\d+)(?:/track/(\d+))?", Platform.YANDEX_MUSIC), # YouTube Music (r"music\.youtube\.com/watch\?v=([a-zA-Z0-9_-]+)", Platform.YOUTUBE_MUSIC), # Deezer (r"deezer\.com/.+/(album|track)/(\d+)", Platform.DEEZER), # Tidal (r"tidal\.com/.+/(album|track)/(\d+)", Platform.TIDAL), # VK Музыка (r"vk\.com/music/album/(-?\d+_\d+)", Platform.VK_MUSIC), # Звук (r"zvuk\.com/(release|track)/(\d+)", Platform.ZVUK), # Amazon Music (r"music\.amazon\.\w+/.+/(albums|tracks)/([A-Z0-9]+)", Platform.AMAZON_MUSIC), ]
Отдельная боль — шорт-ссылки. Spotify использует spotify.link/..., YouTube — youtu.be/..., Яндекс Музыка — укороченные ссылки через music.yandex.ru/.... Перед парсингом нужно развернуть шорт-ссылку:
async def resolve_short_url(url: str) -> str: async with aiohttp.ClientSession() as session: async with session.head( url, allow_redirects=True, timeout=5 ) as resp: return str(resp.url)
Spotify API: UPC есть, но достать непросто
Spotify Web API — главный источник. UPC хранится в объекте альбома в поле external_ids.upc, ISRC — в объекте трека в external_ids.isrc.
{ "external_ids": { "upc": "886445413816" }, "name": "Random Access Memories", "label": "Columbia", "release_date": "2013-05-17", "tracks": { "items": [ { "name": "Give Life Back to Music", "external_ids": {"isrc": "USCO11300095"} } ] } }
Проблема 1: UPC иногда не возвращается. Периодически Spotify API отдаёт объект альбома без поля upc в external_ids. Баг известен сообществу, Spotify его чинили, но он возвращается. Решение — retry с параметром market (перебираю US, GB, DE, JP), потому что для разных рынков API может вернуть разные данные.
Проблема 2: поиск по UPC нестабилен. API поддерживает фильтр upc: в поисковом эндпоинте, но на практике работает через раз. Для некоторых UPC возвращает пустой результат, хотя альбом существует. Особенно плохо с региональными релизами.
Проблема 3: rate limits. 180 запросов в минуту на приложение. Один пользовательский запрос может превратиться в 3–5 API-вызовов (поиск + получение альбома + retry по рынкам + ISRC для каждого трека). Redis-кеш с TTL 24 часа спасает:
async def get_album_data(album_id: str) -> dict | None: cache_key = f"album:{album_id}" cached = await redis.get(cache_key) if cached: return json.loads(cached) for market in ["US", "GB", "DE", "JP"]: album = await spotify.get_album(album_id, market=market) upc = album.get("external_ids", {}).get("upc") if upc: result = { "upc": upc, "name": album["name"], "artist": album["artists"][0]["name"], "label": album.get("label"), "release_date": album.get("release_date"), "tracks": extract_isrc_list(album), } await redis.set(cache_key, json.dumps(result), ex=86400) return result return None
Яндекс Музыка и VK: без официального API
Если Spotify и Apple Music хотя бы имеют документированные API, то Яндекс Музыка и VK Музыка — это совершенно другая история. Официального публичного API нет. Приходится работать с недокументированными эндпоинтами.
Яндекс Музыка имеет внутренний API, который используется мобильным приложением. Формат запросов обратно-инженерен сообществом. UPC-код хранится в метаданных альбома, но отдаётся не всегда. Для получения ISRC нужно запрашивать каждый трек отдельно.
Главная проблема: эти API могут измениться в любой момент без предупреждения. За три месяца работы бота Яндекс Музыка дважды меняла формат ответов — приходилось экстренно обновлять парсеры.
Мульти-версии: один трек — несколько UPC
Неочевидная фича, которая оказалась самой полезной: обнаружение всех версий релиза. Один и тот же трек может быть выпущен как сингл, как часть альбома и как часть сборника — и у каждого варианта будет свой UPC-код.
Это критически важно при переходе между дистрибьюторами: музыканту нужно знать все UPC-коды всех версий, иначе часть стримов «потеряется».
Бот находит все версии через поиск по ISRC трека: если ISRC один и тот же, а UPC-коды разные — значит, это перезалив или другая версия релиза. Пользователю отображаются все найденные варианты.
Полный режим: UPC + ISRC + BPM
Команда /full отдаёт максимум информации о релизе:
UPC-код релиза (и всех его версий)
ISRC-коды всех треков
Лейбл и дата релиза
Жанр
BPM каждого трека (через Spotify Audio Features API)
BPM — бонусная фича, но музыканты и диджеи её оценили. Spotify Audio Features API отдаёт tempo для каждого трека, точность обычно достаточная.
Обработка пользовательского ввода
Пользователи вводят запросы в непредсказуемых форматах. Коллекция за первый месяц:
"дайте код моего последнего релиза" "upc Моргенштерн" "https://open.spotify.com/album/4LH4d3cOWNNsVw41Gqt2kv" "886445413816" "как найти upc код релиза" "https://music.yandex.ru/album/12345"
Бот обрабатывает все варианты: ссылки парсит через URL Parser, чистые числа из 12–13 цифр воспринимает как UPC-код и ищет по нему, текстовые запросы ищет как название артиста/альбома и предлагает варианты inline-кнопками.
Что я бы сделал иначе
MusicBrainz с первого дня. Открытая база данных с UPC-кодами (там они называются «barcode»). Покрытие инди-релизов хуже, чем у Spotify, но как третий источник для перекрёстной проверки — стоило добавить сразу.
Message queue вместо синхронной обработки. При пиковых нагрузках (когда кто-то делает тред в Twitter со ссылкой на бота) все запросы идут одновременно, и бот начинает упираться в rate limits всех платформ сразу. Очередь с приоритетами решила бы проблему элегантнее.
Мониторинг API-изменений. Недокументированные API Яндекс Музыки и VK ломаются без предупреждения. Нужен health-check, который раз в час проверяет, что все платформы отвечают корректно, и алертит в Telegram, если что-то сломалось.
Результаты
Бот работает три месяца. Цифры:
~500 уникальных пользователей в месяц
Среднее время ответа — 1.5 секунды (с кешем — 0.3с)
Процент успешных поисков — 89%
Самый частый сценарий — поиск UPC-кода последнего релиза по ссылке из Spotify
Самая популярная платформа среди пользователей — Яндекс Музыка (43%), затем Spotify (31%)
Бот доступен в Telegram: @upc_music_bot
Технические выводы
Работа с музыкальными API — это постоянная борьба с нестабильностью и фрагментацией. У каждой платформы свой формат данных, свои ограничения, свои баги. Единого стандарта нет, и UPC-код, который должен быть универсальным идентификатором, на практике спрятан за десятью разными API.
Главный архитектурный урок: для любого агрегатора, зависящего от внешних API, нужно минимум два источника данных на каждый тип информации и агрессивный кеш. Один API обязательно сломается — вопрос когда, а не если.
Второй урок: недокументированные API — это техдолг с первого дня. Они работают, пока работают. Закладывайте время на экстренные фиксы в ваш roadmap.
Если делаете что-то похожее — начинайте с Spotify API (лучшая документация) и iTunes Search API (бесплатный, без авторизации), а остальные платформы добавляйте по мере необходимости.
Если вы музыкант и вам нужно быстро найти UPC-код вашего релиза — попробуйте бота: t.me/upc_music_bot. Работает с Яндекс Музыкой, Spotify, Apple Music и ещё семью платформами.
Если вы разработчик и работали с музыкальными API — расскажите про свои грабли в комментариях.
