コラム

2020.12.18

【API・データ検証奮闘記】#4.音楽系データシリーズ 3部作 第2部:AppleMusic

本コラムでは、APIやデータに関連する言語などを無邪気に触ってみた備忘録として、ライトに記載していきます!(連載記事はこちら
「ちょっと違くない」「他にいい方法あるのに」といったご意見もあるかと思いますが、何卒お手柔らかに!!

<プロフィール>
富松 良介
2017年、株式会社サイバー・コミュニケーションズ(CCI)入社。Oracle Bluekai・Treasure Data等のDMPや、AWS・GCP等のPublicCloud領域を担務。2019年6月よりデータの利活用を推進するコンサルティング会社「株式会社DataCurrent」に出向し、事業会社の基盤構築・運用や自社ソリューション開発を担当。

※音楽系データシリーズ 3部作 他記事はこちら
第1部:Spotify 編
第3部:YouTube 編

●AppleMusicランキング取得やってみた

ボス
ボス
AppleMusicのランキングデータが欲しい。
モブ
モブ
かしこまり!!

AppleMusicのデータ取得の手順から取得した結果までをご紹介致します。

対象 AppleMusic ランキング
URL: https://spotifycharts.com/regional/jp/daily/latest

ステップ1. どうやって取得できるか調査してみた

モブ1
モブ1
API!!
モブ2
モブ2
スクレイピング!!
モブ3
モブ3
ツール!!
モブ
モブ
APIで取得することにしよう!!
ボス
ボス
それでやってくれ。

●ステップ2. データ取得してみた

早速Pythonでデータを取得してみる。

from datetime import date, datetime, timedelta, timezone
import jwt
import requests
import json
import pandas as pd
from requests.exceptions import HTTPError
import os
import time
import re


os.environ["secret_key"] = """-----BEGIN PRIVATE KEY-----
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXX
-----END PRIVATE KEY-----"""
os.environ["key_id"] = 'XXXXXXXXXX'
os.environ["team_id"] = 'XXXXXXXXXX'


class AppleMusic(object):
    def __init__(self, secret_key, key_id, team_id, proxies=None, requests_session=True, max_retries=10, requests_timeout=None, session_length=4000):
        self.proxies = proxies
        self._secret_key = secret_key
        self._key_id = key_id
        self._team_id = team_id
        self._alg = 'ES256'
        self.token_str = ""
        self.generate_token(session_length)
        self.root = 'https://api.music.apple.com/v1/'
        self.max_retries = max_retries
        self.requests_timeout = requests_timeout
        if requests_session:
            self._session = requests.Session()
        else:
            self._session = requests.api

    def generate_token(self, session_length):
        headers = {
            'alg': self._alg,
            'kid': self._key_id
        }
        payload = {
            'iss': self._team_id,
            'iat': int(datetime.now().timestamp()),
            'exp': int((datetime.now() + timedelta(hours=session_length)).timestamp())  # expiration time
        }
        token = jwt.encode(payload, self._secret_key, algorithm=self._alg, headers=headers)
        self.token_str = token.decode()

    def _auth_headers(self):
        if self.token_str:
            return {'Authorization': 'Bearer {}'.format(self.token_str)}
        else:
            return {}

    def _call(self, method, url, params):
        if not url.startswith('http'):
            url = self.root + url
        headers = self._auth_headers()
        headers['Content-Type'] = 'application/json'

        r = self._session.request(method, url,
                                  headers=headers,
                                  proxies=self.proxies,
                                  params=params,
                                  timeout=self.requests_timeout)
        r.raise_for_status()
        return r.json()

    def _get(self, url, **kwargs):
        retries = self.max_retries
        delay = 1
        while retries > 0:
            try:
                return self._call('GET', url, kwargs)
            except HTTPError as e: 
                retries -= 1
                status = e.response.status_code
                if status == 429 or (500 <= status < 600):
                    if retries < 0:
                        raise
                    else:
                        print('retrying ...' + str(delay) + ' secs')
                        time.sleep(delay + 1)
                        delay += 1
                else:
                    raise
            except Exception as e:
                print('exception', str(e))
                retries -= 1
                if retries >= 0:
                    print('retrying ...' + str(delay) + 'secs')
                    time.sleep(delay + 1)
                    delay += 1
                else:
                    raise

    def charts(self, storefront, chart=None, types=None, l=None, genre=None, limit=None, offset=None):
        url = self.root + 'catalog/{}/charts'.format(storefront)
        if types:
            type_str = ','.join(types)
        else:
            type_str = None
        return self._get(url, types=type_str, chart=chart, l=l, genre=genre, limit=limit, offset=offset)

if __name__ == '__main__':

  secret_key = os.environ["secret_key"]
  key_id = os.environ["key_id"]
  team_id = os.environ["team_id"]

  am = AppleMusic(secret_key, key_id, team_id)
  charts_list = []

  for offset in range(100):
    results = am.charts(storefront='jp', chart=None, types=["songs"], l=None, genre=None, limit=1, offset=offset)

    charts_list.append([
                        offset+1,
                        results["results"]["songs"][0]["name"],
                        results["results"]["songs"][0]["data"][0]["attributes"]["artistName"],
                        results["results"]["songs"][0]["data"][0]["attributes"]["name"],
                        results["results"]["songs"][0]["data"][0]["attributes"]["albumName"],
                        results["results"]["songs"][0]["data"][0]["attributes"]["genreNames"]
    ])

  result_df = pd.DataFrame(
  charts_list,
  index=None,
  columns=['position',
          'chart_type',
          'artist_name',
          'track_name',
          'album_name',
          'genre_names'
          ])
  JST = timezone(timedelta(hours=+9), 'JST')
  result_df['date'] = datetime.now(JST).strftime("%Y-%m-%d")

ステップ3. 取得結果

※音楽系データシリーズ 3部作 他記事はこちら
第1部:Spotify 編
第3部:YouTube 編

最後に

弊社では、定点的なリサーチやトレンドの分析をおこなっています。性別や年代等の属性を検索トレンドのダッシュボード提供等様々なパッケージをご用意しておりますので、お気軽にお問い合わせください。

本データに関するお問い合わせは下記にて承ります。
株式会社DataCurrent
info@datacurrent.co.jp