Spotify APIで好きなアーティストのインスト楽曲プレイリストを作成する
これは何か
pythonでSpotify APIを利用し、指定したアーティストのインスト楽曲を集めたプレイリストを自動で作成する方法について説明します。
※Spotify APIを利用するにはspotifyのサブスクに加入している必要があります。
動機
Spotifyにはユーザが作成したプレイリストを他のユーザが利用できる機能があります。私は、よく他のユーザの方々が作ってくださっているインスト楽曲(ボーカルの入っていない楽曲)のプレイリストを聞きながら作業をします。
しかし、アーティストの楽曲をもれなく集め、また最新の楽曲をフォローしているプレイリストはあまり見つかりません。
これは、そもそもアーティストの全楽曲から特定の条件を満たす楽曲をもれなく集めることが大変な作業であり、さらに新しい楽曲がリリースされるたびにその楽曲をプレイリストに追加していくのが大変な作業であるためと考えられます(リリース日などを考慮してソートしようとすると、更に労力がかかります)。
そこで、Spotify APIを利用して、特定のアーティストのインスト楽曲を自動で収集してプレイリストを作成することができないかと考え、実際にpythonとspotipyライブラリで実装を行いました。spotifyのサブスクに加入しており、pythonについての一定の知識があれば、自分の好きなアーティストの楽曲のプレイリストを作成できます。
現在は、楽曲の更新に応じて自動でスクリプトが実行されるようにはなっていません。たまに手動で実行して、その時点の最新のインスト楽曲を集めたプレイリストを作成する、という用途を想定しています。個人的に利用するプレイリストを作成するのが目的なので、これで十分という判断です。
インスト楽曲のプレイリストだけでなく、ボーカルの入った楽曲のみのプレイリストの作成などもできます。
前提
作成したいのは学マスのインスト楽曲を集めたプレイリストです。以下のような特徴があります。
- アーティスト(初星学園)自身がインスト楽曲をリリースしている。
- インスト楽曲には楽曲名に"Instrumental"というキーワードが含まれている。
要件
上の前提を踏まえ、以下の要件でスクリプトを作成しました。
- 指定したアーティストの楽曲のみを収集する。
- 楽曲名に含まれるキーワード(例:"Instrumental")でフィルタリングを行う。
重複処理
- 名前が完全に同じ楽曲の別バージョン(ボーカルが異なる、など)が存在する場合は、リリース日が古いものを残す。
ソート
- プレイリスト内の楽曲は、リリース日の降順(最新のものが先頭)となるようにソートする。
プレイリストの作成/更新
- プレイリストの名前を指定し、もし同名のプレイリストが存在する場合は、既存のプレイリストを更新する。
プレイリストの更新を行う際に、既存のプレイリストとの差分のみ更新を行うことはせず、毎回全ての楽曲を削除し、新たに追加する形としました。これは、差分更新を行う場合に楽曲が意図したソート順になることを保証するのがやや面倒だからです。
ただし、これにより、プレイリストを更新するたびにプレイリスト内の楽曲の更新日時がリセットされることになります。(既存の楽曲は更新日時がリセットされないほうが望ましくはあるのですが、自分が聴くプレイリストを作るのが目的なので、気にしないことにします)

プレイリストを更新すると、全楽曲の更新日時がリセットされる
手順
手順を簡単に説明します。
Spotify Developerアカウントの作成
Spotify APIを利用するために、まずはSpotify Developerアカウントを作成します。
- https://developer.spotify.com/ でアカウントを作成してください。
- ダッシュボード https://developer.spotify.com/dashboard にアクセスし、Create appをクリック
- App nameとApp descriptionに任意の名前と説明を入力
- Redirect URIには
http://127.0.0.1:8080/を追加する(スクリプトをローカルで実行する場合、これで十分です) - Saveをクリック
- Client IDとClient Secretが取得できます。
スクリプトの準備
$ mkdir spotify-playlist-creator
$ cd spotify-playlist-creator
$ touch create_playlist_with_keyword.py
作成したcreate_playlist_with_keyword.pyに以下のコードをコピーしてください。もし、既存のコードで対応できないような条件やソート方法を適用したい場合、このコードをカスタマイズしてみてください。
create_playlist_with_keyword.py
from dataclasses import dataclass
import sys
from typing import Dict, List, Any
import json
from dotenv import load_dotenv
import spotipy
from spotipy.oauth2 import SpotifyOAuth
load_dotenv() # load environment variables from .env file
SCOPE = "playlist-modify-public playlist-modify-private"
@dataclass
class Config:
artist_name: str
playlist_name: str
playlist_description: str
keyword: str
include_keyword: bool
def load_config(path: str) -> Config:
try:
with open(path, 'r', encoding='utf-8') as f:
data = json.load(f)
return Config(**data)
except Exception as e:
raise e
def main():
assert len(sys.argv) == 2, "Usage: python create_playlist_with_keyword.py <config_file_path>"
config_file_path = sys.argv[1]
config = load_config(config_file_path)
sp = spotipy.Spotify(auth_manager=SpotifyOAuth(
scope=SCOPE
))
user_info = sp.current_user()
user_id = user_info['id']
# search artist
artist_results = sp.search(q=f'artist:{config.artist_name}', type='artist')
items = artist_results['artists']['items']
if not items:
print(f'No artist found for {config.artist_name}')
return
artist_id = items[0]['id']
print(f"artist ID: {artist_id}")
# fetch tracks of the target artist
target_tracks = fetch_tracks_with_keyword(
sp=sp,
artist_id=artist_id,
keyword=config.keyword,
should_include_keyword=config.include_keyword,
country='JP',
keep_older=True
)
# sort tracks by release date
target_tracks = sort_tracks_by_release_date(target_tracks, newest_first=True)
track_uris = [track.uri for track in target_tracks]
if not track_uris:
print("No tracks found. Exiting.")
return
playlist_id = find_or_create_playlist(sp, user_id, config)
existing_tracks_uris = get_playlist_track_uris(sp, playlist_id)
# compare existing tracks with new tracks
# and exit if there are no diffs
cur_track_uris_set = set(existing_tracks_uris)
new_track_uris_set = set(track_uris)
if cur_track_uris_set == new_track_uris_set:
print("Playlist is already up to date. No changes made.")
return
# update playlist
replace_playlist_tracks(
sp=sp,
user_id=user_id,
playlist_id=playlist_id,
current_track_uris=existing_tracks_uris,
new_track_uris=track_uris
)
@dataclass
class TrackInfo:
name: str
uri: str
release_date: str
def _get_all_pagenated_items(sp: spotipy.Spotify, results: Dict[str, Any]) -> List[Dict[str, Any]]:
"""Helper function to get all items from paginated Spotify results."""
items = results['items']
while results['next']:
try:
results = sp.next(results)
items.extend(results['items'])
except Exception as e:
print(f"Error fetching paginated items: {e}")
break
return items
def fetch_tracks_with_keyword(sp: spotipy.Spotify, artist_id: str, keyword: str, should_include_keyword: bool, country: str = 'JP', keep_older: bool = True) -> List[TrackInfo]:
"""fetch list of tracks including/excluding keyword in their title
Args:
sp: spotipy.Spotify instance
artist_id: Spotify artist ID
keyword: Keyword to search for in track titles
should_include_keyword: If True, include tracks with the keyword; if False, exclude them
country: Country code (default: 'JP')
keep_older: If True, keep older version of duplicate track names
Returns:
tracks: List[TrackInfo]
A list of TrackInfo objects.
"""
track_dict = {}
album_types = ['album', 'single']
all_albums = []
for album_type in album_types:
try:
results = sp.artist_albums(artist_id, album_type=album_type, country=country, limit=50)
all_albums.extend(_get_all_pagenated_items(sp, results))
except Exception as e:
print(f"Error fetching {album_type}s: {e}")
exit(1)
print(f"Total albums/singles fetched: {len(all_albums)}")
album_ids = [album['id'] for album in all_albums]
for i in range(0, len(album_ids), 20):
batch_ids = album_ids[i:i + 20]
try:
albums_with_tracks = sp.albums(batch_ids)['albums']
except Exception as e:
print(f"Error fetching albums with tracks: {e}")
continue
for album in albums_with_tracks:
if not album:
continue
for track in album['tracks']['items']:
track_name = track['name']
if should_include_keyword and keyword not in track_name:
continue
if not should_include_keyword and keyword in track_name:
continue
should_add_or_update = False
if track_name in track_dict:
existing_date = track_dict[track_name].release_date
new_date = album['release_date']
if (keep_older and new_date < existing_date) \
or (not keep_older and new_date > existing_date):
should_add_or_update = True
else: # New track
should_add_or_update = True
if should_add_or_update:
track_info = TrackInfo(
name=track_name,
uri=track['uri'],
release_date=album['release_date']
)
track_dict[track_name] = track_info
print(f"Added/Updated track: {track_name} - {album['name']} ({album['release_date']})")
track_list = list(track_dict.values())
return track_list
def sort_tracks_by_release_date(tracks: List[TrackInfo], newest_first: bool = True) -> List[TrackInfo]:
"""Sort tracks by their release date.
Args:
tracks: List of TrackInfo objects.
newest_first: If True, sort by newest first; otherwise, oldest first.
Returns:
Sorted list of TrackInfo objects.
"""
return sorted(tracks, key=lambda x: x.release_date, reverse=newest_first)
def find_or_create_playlist(sp: spotipy.Spotify, user_id: str, config: Config) -> str:
"""Find an existing playlist by name or create a new one if it doesn't exist.
Args:
sp: spotipy.Spotify instance
user_id: Spotify user ID
playlist_name: Name of the playlist to find or create
Returns:
Playlist ID of the found or created playlist.
"""
results = sp.current_user_playlists()
all_playlists = _get_all_pagenated_items(sp, results)
print(f"users' total playlists: {len(all_playlists)}")
for playlist in all_playlists:
if playlist['name'] == config.playlist_name:
playlist_id = playlist['id']
print(f"✅ Found existing playlist '{config.playlist_name}' (ID: {playlist_id}).")
return playlist_id
playlist = sp.user_playlist_create(
user=user_id,
name=config.playlist_name,
public=True,
description=config.playlist_description
)
playlist_id = playlist['id']
print(f"✅ Created new playlist '{config.playlist_name}' (ID: {playlist_id}).")
return playlist_id
def get_playlist_track_uris(sp: spotipy.Spotify, playlist_id: str) -> List[str]:
"""Get all tracks from a playlist.
Args:
sp: spotipy.Spotify instance
playlist_id: Spotify playlist ID
Returns:
List of track URIs in the playlist.
"""
all_tracks: List[Dict[str, Any]] = []
results = sp.playlist_items(playlist_id)
all_tracks.extend(_get_all_pagenated_items(sp, results))
track_uris = []
for item in all_tracks:
# is_local: True if the track is user's local file
if item['track'] and not item['is_local']:
track_uris.append(item['track']['uri'])
return track_uris
def replace_playlist_tracks(sp: spotipy.Spotify, user_id: str, playlist_id: str, current_track_uris: List[str], new_track_uris: List[str]) -> None:
"""Replace all tracks in a playlist with new tracks.
Args:
sp: spotipy.Spotify instance
user_id: Spotify user ID
playlist_id: Spotify playlist ID
current_track_uris: List of current track URIs in the playlist
new_track_uris: List of new track URIs to add to the playlist
"""
# 1. Remove all existing tracks
if current_track_uris:
chunk_size = 100
for i in range(0, len(current_track_uris), chunk_size):
tracks_chunk = current_track_uris[i:i + chunk_size]
sp.user_playlist_remove_all_occurrences_of_tracks(
user=user_id,
playlist_id=playlist_id,
tracks=tracks_chunk
)
print(" deleted all existing tracks.")
# 2. Add new tracks
if new_track_uris:
chunk_size = 100
for i in range(0, len(new_track_uris), chunk_size):
tracks_chunk = new_track_uris[i:i + chunk_size]
sp.playlist_add_items(
playlist_id=playlist_id,
items=tracks_chunk
)
print(f"✅ Updated playlist. Current number of tracks: {len(new_track_uris)}")
if __name__ == "__main__":
main()
必要なライブラリのインストール
$ pip install spotipy dotenv
spotipyはpythonでSpotify APIを利用するためのライブラリ、dotenvは、環境変数を.envファイルから読み込むためのライブラリです。
環境変数の設定
プロジェクトルートに.envファイルを作成してください。
$ touch .env
前の手順で取得したClient IDとClient Secretを.envファイルに設定してください。
SPOTIPY_CLIENT_ID="your_client_id"
SPOTIPY_CLIENT_SECRET="your_client_secret"
SPOTIPY_REDIRECT_URI="http://127.0.0.1:8080/"
REDIRECT_URIは、Spotify Developerダッシュボードで設定したものと同じにしてください。初回実行の認証に使用されます。
作成するプレイリストの設定
作成するプレイリストの設定はjsonファイルに記載します。
$ touch my_playlist.json
以下のように記載します。自分の作りたいプレイリストに合わせて適宜書き換えてください。
{
"artist_name": "初星学園",
"playlist_name": "学マス - Instrumental",
"playlist_description": "初星学園のインスト楽曲集",
"keyword": "Instrumental",
"include_keyword": true
}
-
artist_nameは、楽曲を収集したいアーティスト名です。 -
playlist_nameは、作成するプレイリストの名前です。同名のプレイリストが存在する場合は、既存のプレイリストを更新します。 -
playlist_descriptionは、作成するプレイリストの説明文です。 -
keywordは、プレイリスト内の楽曲が含むべき/含むべきでない単語です。 -
include_keywordがtrueの場合、楽曲名にkeywordを含む楽曲を収集します。falseの場合、keywordを含まない楽曲を収集します。
スクリプトの実行
以下のコマンドでスクリプトを実行します。
$ python create_playlist_with_keyword.py my_playlist.json
スクリプトの実行が成功すれば、指定したアーティストのインスト楽曲を集めたプレイリストが作成されます。spotifyアプリやWebプレイヤーで確認してください。
初回は認証のためにブラウザが起動するかもしれません。ログインして認証を許可してください。
まとめ
spotify APIを利用し、特定のアーティストの楽曲を収集してプレイリストを作成する方法について説明しました。上の手順を踏むことで、自分だけのプレイリストを簡単に作成できるようになります。
改善点
- 全てのアーティストがインスト楽曲をリリースしているわけではないため、他のアーティストが公開するインストカバー楽曲を収集できるようにする。
- spotify artist IDを使ってアーティストを直接指定できるようにする(今は名前で指定している)
Discussion