PythonのSpotify APIで指定したアーティストの人気曲を取得する

# まえがき
音楽ストリーミングアプリSpotifyから提供されているSpotify API for Pythonの Spotipyを使って、ユーザの指定したアーティストの人気楽曲をSpotifyから取得するというものです。

# 前提条件
事前に必要なものは、[Spotify for Developers](https://developer.spotify.com/)への登録と、そのサイト内での新規アプリ作成です。また、Pythonの環境構築は済んでいるものとします。

# 手順
はじめに、Spotify for DevelopersのDASHBOARDから**Client ID**と**Client Secret**を取得し、メモしておきます。セキュリティ的に問題があるのでこの文字列は第三者に見破られないようにしてください。
![Spotify for Developersの画面](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/592867/b91456f7-6be7-23eb-cf36-0796afa4f811.png)
上の画像で赤線で隠してある部分がClient IDで青のそれがClient Secretです。

この２つのトークンから、以下のコードのようにしてAPIへのアクセス権を得ます。

```Python:sample_spotipy.py（一部）
import spotipy

client_id = '自分のClient ID'
client_secret = '自分のClient Secret'

client_credentials_manager = spotipy.oauth2.SpotifyClientCredentials(client_id, client_secret)
spotify = spotipy.Spotify(client_credentials_manager=client_credentials_manager)
```
さて、今回書くコードは``(ユーザが指定した歌手名)``から``その歌手の人気曲``を得るものです。
Spotipyの[WEB APIリファレンス](https://developer.spotify.com/documentation/web-api/reference/#category-artists)によると、内部ではアーティストのidを指定することでtop-tracksというものを取得することができます。

```JSON:top-tracksの仕様
{
  "tracks": [ {
    "album" : {
      "album_type" : "album",
      "artists" : [ {
        "external_urls" : {
          "spotify" : "https://open.spotify.com/artist/43ZHCT0cAZBISjO8DG9PnE"
        },
        "href" : "https://api.spotify.com/v1/artists/43ZHCT0cAZBISjO8DG9PnE",
        "id" : "43ZHCT0cAZBISjO8DG9PnE",
        "name" : "Elvis Presley",
        "type" : "artist",
        "uri" : "spotify:artist:43ZHCT0cAZBISjO8DG9PnE"
      } ],
      "available_markets" : [ "AD", "AR", "AT", "AU", "BE", "BG", "BO", "BR", "CA", "CH", "CL", "CO", "CR", "CY", "CZ", "DE", "DK", "DO", "EC", "EE", "ES", "FI", "FR", "GB", "GR", "GT", "HK", "HN", "HU", "ID", "IE", "IS", "IT", "JP", "LI", "LT", "LU", "LV", "MC", "MT", "MX", "MY", "NI", "NL", "NO", "NZ", "PA", "PE", "PH", "PL", "PT", "PY", "SE", "SG", "SK", "SV", "TR", "TW", "US", "UY" ],
      "external_urls" : {
        "spotify" : "https://open.spotify.com/album/7xe8VI48TxUpU1IIo0RfGi"
      },
      "href" : "https://api.spotify.com/v1/albums/7xe8VI48TxUpU1IIo0RfGi",
      "id" : "7xe8VI48TxUpU1IIo0RfGi",
      "images" : [ {
        "height" : 640,
        "url" : "https://i.scdn.co/image/4295b5ff74d4f944367144acbe616b6f62d20b17",
        "width" : 640
      }, {
        "height" : 300,
        "url" : "https://i.scdn.co/image/203104e5843248c700b078f391d4bc759c5d7f47",
        "width" : 300
      }, {
        "height" : 64,
        "url" : "https://i.scdn.co/image/0c0a172373b0211c590b241270d05b70889075a1",
        "width" : 64
      } ],
      "name" : "Blue Hawaii",
      "type" : "album",
      "uri" : "spotify:album:7xe8VI48TxUpU1IIo0RfGi"
    },
    "artists" : [ {
      "external_urls" : {
        "spotify" : "https://open.spotify.com/artist/43ZHCT0cAZBISjO8DG9PnE"
      },
      "href" : "https://api.spotify.com/v1/artists/43ZHCT0cAZBISjO8DG9PnE",
      "id" : "43ZHCT0cAZBISjO8DG9PnE",
      "name" : "Elvis Presley",
      "type" : "artist",
      "uri" : "spotify:artist:43ZHCT0cAZBISjO8DG9PnE"
    } ],
    "available_markets" : [ "AD", "AR", "AT", "AU", "BE", "BG", "BO", "BR", "CA", "CH", "CL", "CO", "CR", "CY", "CZ", "DE", "DK", "DO", "EC", "EE", "ES", "FI", "FR", "GB", "GR", "GT", "HK", "HN", "HU", "ID", "IE", "IS", "IT", "JP", "LI", "LT", "LU", "LV", "MC", "MT", "MX", "MY", "NI", "NL", "NO", "NZ", "PA", "PE", "PH", "PL", "PT", "PY", "SE", "SG", "SK", "SV", "TR", "TW", "US", "UY" ],
    "disc_number" : 1,
    "duration_ms" : 179773,
    "explicit" : false,
    "external_ids" : {
      "isrc" : "USRC16101350"
    },
    "external_urls" : {
      "spotify" : "https://open.spotify.com/track/44AyOl4qVkzS48vBsbNXaC"
    },
    "href" : "https://api.spotify.com/v1/tracks/44AyOl4qVkzS48vBsbNXaC",
    "id" : "44AyOl4qVkzS48vBsbNXaC",
    "name" : "Can't Help Falling in Love",
    "popularity" : 70,
    "preview_url" : "https://p.scdn.co/mp3-preview/26e409b39a2da6dc18fab61020c90be2938dc0e9",
    "track_number" : 5,
    "type" : "track",
    "uri" : "spotify:track:44AyOl4qVkzS48vBsbNXaC"
  }, {
...
  } ]
}
```

まずは指定した文字列で表される歌手のSpotify内でのIDを返す関数を作ってみます。
さきほどの[WEB APIリファレンス](https://developer.spotify.com/documentation/web-api/reference/#category-search)からspotify.searchによって得られるオブジェクトは

```JSON:search
{
    "artists": {
        "href": "https://api.spotify.com/v1/search?query=tania+bowra&offset=0&limit=20&type=artist",
        "items": [
            {
                "external_urls": {
                    "spotify": "https://open.spotify.com/artist/08td7MxkoHQkXnWAYD8d6Q"
                },
                "genres": [],
                "href": "https://api.spotify.com/v1/artists/08td7MxkoHQkXnWAYD8d6Q",
                "id": "08td7MxkoHQkXnWAYD8d6Q",
                "images": [
                    {
                        "height": 640,
                        "url": "https://i.scdn.co/image/f2798ddab0c7b76dc2d270b65c4f67ddef7f6718",
                        "width": 640
                    },
                    {
                        "height": 300,
                        "url": "https://i.scdn.co/image/b414091165ea0f4172089c2fc67bb35aa37cfc55",
                        "width": 300
                    },
                    {
                        "height": 64,
                        "url": "https://i.scdn.co/image/8522fc78be4bf4e83fea8e67bb742e7d3dfe21b4",
                        "width": 64
                    }
                ],
                "name": "Tania Bowra",
                "popularity": 0,
                "type": "artist",
                "uri": "spotify:artist:08td7MxkoHQkXnWAYD8d6Q"
            }
        ],
        "limit": 20,
        "next": null,
        "offset": 0,
        "previous": null,
        "total": 1
    }
}
```
のようになっているので、``(searchで返された結果)→artists→items→の０番目要素→id``で辿っていくのが良さそうです。

:::note warn
注意
itemsの0番目要素だからと言って必ず想定したアーティストが返されるとは限らず、さらにはIndexErrorを発生する場合があります。
:::


```Python:sample_spotipy.py（一部）
def getIdByArtist(artist_name):
    results = spotify.search(q="artist:" + artist_name, type="artist")
    items = results["artists"]["items"]
    artist = items[0]
    artist_id = artist["id"]
    return artist_id
```

次に得られたIDからtop-tracksの情報を取得します。Spotipyでは``spotify.artist_top_tracks()``関数の引数として調べたいアーティストのIDを渡すことで最大10個のtrackオブジェクトが返されます。

```Python:sample_spotipy.py（一部）
def getTopSongs(artist_name):
    num = 10  # 調べたい楽曲数(最大10)
    try:
        search_id = getIdByArtist(artist_name)
        artist_top_tracks = spotify.artist_top_tracks(search_id, country="JP")["tracks"]

        print(artist_name + f" Top{num} Songs")

        for i in range(num):
            print(str(i + 1) + ". " + artist_top_tracks[i]["name"])

    except IndexError:
        print("IndexError has occurred!")
    except AttributeError:
        print("AttributeError has occurred!")

```
IndexErrorやAttributeErrorは正しく検索が行えなかったときに起こることがあります。
ここまででコードが書けたので、試しにMr.Children、ヨルシカ、oasisの人気曲ベスト10を出力するテストコードを書いてみます。

```Python:sample_spotipy.py（一部）
if __name__ == '__main__':
    getTopSongs("Mr.Children")
    print()
    getTopSongs("ヨルシカ")
    print()
    getTopSongs("oasis")
```

実行してみると以下のような出力が得られます。(2021/7/18現在)

```txt:出力
Mr.Children Top10 Songs
1. HANABI
2. Tomorrow never knows
3. innocent world
4. 足音 ~Be Strong
5. 名もなき詩
6. Sign
7. シーソーゲーム~勇敢な恋の歌~
8. しるし
9. Brand new planet
10. 抱きしめたい

ヨルシカ Top10 Songs
1. ただ君に晴れ
2. 春泥棒
3. 又三郎
4. だから僕は音楽を辞めた
5. 花に亡霊
6. 言って。
7. ヒッチコック
8. 風を食む
9. 嘘月
10. 藍二乗

oasis Top10 Songs
1. Wonderwall
2. Don't Look Back in Anger
3. Champagne Supernova
4. Stop Crying Your Heart Out
5. Live Forever
6. Supersonic
7. Stand by Me
8. Half the World Away
9. Whatever
10. She's Electric
```

無事にTop-Tracksが出力できました。

以下、サンプルコード全文です。

```Python:sample_spotipy.py
import spotipy

client_id = '自分のClient ID'
client_secret = '自分のClient Secret'

client_credentials_manager = spotipy.oauth2.SpotifyClientCredentials(client_id, client_secret)
spotify = spotipy.Spotify(client_credentials_manager=client_credentials_manager)

def getIdByArtist(artist_name):
    results = spotify.search(q="artist:" + artist_name, type="artist")
    items = results["artists"]["items"]
    artist = items[0]
    artist_id = artist["id"]
    return artist_id


def getTopSongs(artist_name):
    num = 10  # 調べたい楽曲数(最大10)
    try:
        search_id = getIdByArtist(artist_name)
        artist_top_tracks = spotify.artist_top_tracks(search_id, country="JP")["tracks"]

        print(artist_name + f" Top{num} Songs")

        for i in range(num):
            print(str(i + 1) + ". " + artist_top_tracks[i]["name"])

    except IndexError:
        print("IndexError has occurred!")
    except AttributeError:
        print("AttributeError has occurred!")


if __name__ == '__main__':
    getTopSongs("Mr.Children")
    print()
    getTopSongs("ヨルシカ")
    print()
    getTopSongs("oasis")

```

# 問題点
指定したアーティストからSpotifyのアーティストIDの取得をかなり簡単にしているので、一字一句間違えずに指定しているのに思ったアーティストとは違うものがヒットする場合や、日本語ではなく英語やローマ字表記にしないと見つからない問題があります。