Spotify 검색에서 cover · live · remix · karaoke 우회 잡기 — POP-SPOT v2.14 음악 검색 정확도
v1.3 의 Spotify 5단계 폴백 끝에서 상위 결과가 cover / live / remix / karaoke / instrumental 로 잡힐던 케이스 차단. 제목 검증 5 단계 (원곡 키워드 포함 / 변형 키워드 불포함) 을 원곡 키워드 매칭 점수와 결합.
v1.3 이후 음악 매칭 정확도는 "원곡을 잘 잡으면 끝" 이라 생각했으나, 실제로는 cover · live · remix · karaoke 같은 변형이 상위로 올라올 때가 제법 있었다. 해결을 "검색 키워드" 가 아닌 "결과 제목 검증" 으로.
이 글에서 다루는 것
모르는 단어 한 줄로
| 용어 | 한 줄 설명 |
|---|---|
| cover | 다른 가수가 원곡을 리메이크한 버전. 제목에 있으면 보통 "... (Cover by ...)" 계열 |
| live | 원곡의 라이브 공연 녹음. "... - Live" 같이 붙음 |
| remix | 원곡을 DJ 가 편곡한 별도의 트랙. "... (Remix)" |
| karaoke | 반주만 있는 MR 트랙. 원곡 가수의 녹음 아님 |
| instrumental | 가사 없이 연주만 있는 버전 |
무엇이 바뀌었나
| 항목 | v2.13 | v2.14 |
|---|---|---|
| Spotify 첫 결과 | 그대로 채택 | 5 가지 제목 검증 점수 계산 후 최고점 채택 |
| cover/live/remix 변형들 | 그대로 노출 | 점진적 감점 처리 |
| 원곡 키워드 매칭 | 없음 | 키워드 수 / 전체 키워드 수 점수 |
왜 이렇게 했음
v1.3 의 5 단계 폴백과의 차이 — v1.3 은 "검색어를 변형·보강" 하면서 폴백. "아이유 좋은날" → "IU Good Day" → YouTube Suggest 이런 식. 그래도 Spotify 가 돌려주는 결과 자체가 cover 면 5 단계 끝까지 가도 어쩔 수 없음. v2.14 는 "구한 결과의 제목" 을 계산·판단한다.
원곡 키워드 점수 — 검색어가 "IU 좋은날" 면, 제목에 "IU" · "좋은날" 가 모두 들어가야 고점. 한 개만 들어간 제목은 감점. cover · live · remix · karaoke · instrumental 이 제목에 들어가면 감점.
점진적 제거 (= 점수 감점) — "cover 포함이면 쓰지 않음" 식의 단호한 차단은 원곡이 cover 버전밖에 없는 경우에 팝업과 매칭할 곡이 안 남는다. 결과가 cover 이면 점수는 낮추되, 다른 후보가 없으면 채택 가능.
Spotify 첫 결과를 믿지 않는다 — 검색 엔진은 "쿼리 일치" + "인기" 로 순위를 구성한다. 특히 아이돌 곡은 cover 변형이 원곡보다 재생수가 높을 수 있어 첫 결과로 올라온다. 구체적 관점의 점수화가 필요.
코드로는 어떻게 (필요한 부분만)
파일: popspot-backend/src/main/java/com/popspot/service/music/SpotifySearchService.java (v1.3 이후 계속 강화)
// v2.14 — 제목 검증 점수화 로직. SpotifySearchService 안
private static final List<String> BAD_KEYWORDS = List.of(
// ^^^^^^^^^^^^ ^^^^^^ Java 9+ 불변 리스트 팩토리
"cover", "live", "remix", "karaoke",
// 원곡 대신 다른 가수·라이브·DJ 편곡·반주(MR) 버전
"instrumental", "acoustic version", "sped up", "slowed"
// 연주만·언플러그드·빠르게 편집·느리게 편집 (틱톡 의 유행 버전)
);
private double scoreTitle(String resultTitle,
List<String> queryKeywords) {
String t = resultTitle.toLowerCase();
// ^^^^^^^^^^^ 소문자 통일 — "Cover"·"COVER"·"cover" 모두 동일하게 검출
long matched = queryKeywords.stream()
// ^^^^^^^ 원곡 키워드 중 제목에 포함되는 개수
.filter(k -> t.contains(k.toLowerCase()))
.count();
long bad = BAD_KEYWORDS.stream()
// ^^^ 변형 키워드 중 제목에 포함된 개수
.filter(t::contains).count();
return (double) matched / queryKeywords.size()
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 정규화 점수 — 원곡 키워드 포함 비율 (0.0 ~ 1.0)
// 예: 키워드 수 2개 중 1개 포함 → 0.5
- bad * 0.3;
// ^^^^^^^^^ 변형 키워드 1개당 0.3점 감점
// "cover" 포함 → -0.3 / "cover live" 예 → -0.6
// 완전 차단은 아님 — 원곡이 cover 밖에 없으면 소소차적으로 선택 가능
}
public Optional<Track> pickBest(List<Track> hits, List<String> kw) {
return hits.stream()
.max(Comparator.comparingDouble(
// ^^^ 점수 가장 높은 하나만 선택
t -> scoreTitle(t.getName(), kw)));
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 위의 점수 함수를 각 트랙에 적용
}
// 결과: cover 가 첫 결과일 수 있고 원곡이 하위 1~2 트랙에 있으면 원곡 채택핵심 파일: popspot-backend/.../service/music/SpotifySearchService.java, popspot-backend/.../service/music/MusicQueryNormalizationService.java
직접 보는 법
음악 탭에서 "아이유 좋은날" 검색 → 첫 결과가 원곡 스튜디오 버전으로 올라오는 걸 확인. v2.13 이전에 cover 가 먼저 올라오던 곡들도 테스트해보면 원곡이 제대로 걸린다.
교훈
외부 검색 API 는 대체로 "자주 찾는 것"과 "쿼리 일치" 를 섞어 답을 주며 우리 쿼리의 명확성을 이해하지 못한다. 결과 자체에 구체적 검증 단계가 하나 더 필요.