0
1

自分の好きなアーティストと似ているアーティストを出力するWebアプリケーションの開発 part1

Last updated at Posted at 2024-05-26

はじめに

大学の授業で、入力されたアーティストと近しいアーティストのネットワーク図を作成するシステムを作ったので、今回は個人的な趣味で、そのシステムを発展させてWebアプリケーションを開発しました。その際の備忘録を残します。
part1では、バックエンド側の実装についてまとめます。

Webアプリケーションの大まかな流れ

  1. 初期画面で入力フォームを表示し、ユーザにアーティスト名を入力させる
  2. 入力されたアーティストに対して、Spotipyのartist_related_artists()を使用して、そのアーティストと関連のあるアーティスト情報を取得する
  3. networkxライブラリを使用して、関連のあるアーティストを芋づる式にノードで結んでネットワーク図を作り、ページランクの値が高い順に取り出す(この時、人気曲の情報も取得する)
  4. リザルト画面で関連のあるアーティスト10人と取得した人気曲の情報を表示する

今回の備忘録では、2.と3.のバックエンド側の実装についてまとめます。

バックエンドの開発

初期設定

app.py
from flask import Flask, render_template, request, redirect, url_for
import spotipy
from spotipy.oauth2 import SpotifyClientCredentials
import networkx as nx
import pandas as pd
import os
from dotenv import load_dotenv

app = Flask(__name__)

# .envファイルから環境変数を読み込む
load_dotenv()

# 環境変数からSpotify APIのクライアントIDとシークレットを取得
client_id = os.getenv('SPOTIFY_CLIENT_ID')
client_secret = os.getenv('SPOTIFY_CLIENT_SECRET')

auth_manager = SpotifyClientCredentials(client_id=client_id, client_secret=client_secret)
sp = spotipy.Spotify(auth_manager=auth_manager)
  1. 必要なモジュールをインポートします
  2. app変数にFlaskクラスのインスタンスを格納します
  3. .envファイルから環境変数を取得し、Spotipyを使えるようにします。.envの具体的な扱い方は次回以降の記事で書きます

ネットワーク図作成にあたり必要な関数の作成

app.py
# アーティスト名のURIを取得
def get_artist_uri(name):
    results = sp.search(q='artist:' + name, type='artist')
    if results['artists']['items']:
        artist = results['artists']['items'][0]
        uri = artist['uri']
        return uri
    else:
        return None

# アーティストのURIを入力し,そのアーティストと関連があるアーティスト情報を出力
def get_related_artist_info(uri):
    df = pd.DataFrame()
    for artist in sp.artist_related_artists(uri)['artists']:
        tmp = pd.Series([], name=artist['name'])
        for key in ['popularity', 'uri']:
            tmp[key] = artist[key]
        # アーティストのトップトラックを取得
        top_tracks = sp.artist_top_tracks(artist['uri'])['tracks']
        top_track_names = [track['name'] for track in top_tracks]
        # トップトラックをデータフレームに追加
        tmp['top_tracks'] = ', '.join(top_track_names)
        df = pd.concat([df, pd.DataFrame(tmp).T])           
    return df

Spotipyでは、アーティスト情報はURIを使って表されるので、

  1. アーティスト名のURIを取得する関数
  2. 上記のURIを使って、そのアーティストと関連がある(似ている)アーティスト情報を取得する関数

を作成します。これらの関数を使ってネットワーク図を作ります。

ネットワーク図の作成、関連アーティストと人気曲情報の取得

app.py
# 以下,ページランクをもとに上位10個のアーティストを取得する関数
#最初にアーティストネットワークを作成する
#次に,そのネットワークに対してpagerankを計算し,pagerankの上位10個を出力する.
def get_top_artists(artist_name):
    G = nx.Graph()  # ページランク計算用の新しいグラフオブジェクトを生成
    uri = get_artist_uri(artist_name)
    if not uri:
        return None, None
    df = get_related_artist_info(uri)
    G.add_node(artist_name)
    for name in df.index.tolist():
        G.add_node(name)
        G.add_edge(artist_name, name)
    pr = nx.pagerank(G)
    sorted_pr = sorted(pr.items(), key=lambda x: x[1], reverse=True) #sorted_prリストは(アーティスト名, PageRank値)のタプル構造になっている.
    top_artists_list = [artist[0] for artist in sorted_pr[:11]] #artist[0]はsorted_prリストの「アーティスト名」を参照している.
    if artist_name in top_artists_list:  #上位10人の中で入力されたアーティスト名と同じ名前のものが入っていた場合は,除去する
        top_artists_list.remove(artist_name)
    else:   #top_artistリストの1番目は入力されたアーティスト名と同じなので,else文に来たときはpopして除去する.
        top_artists_list.pop()
    
    # アーティストごとに有名曲を5曲取得
    top_tracks_dict = {}
    for artist in top_artists_list:
        uri = get_artist_uri(artist)
        if uri:
            top_tracks = sp.artist_top_tracks(uri)['tracks'] #人気順になってる
            top_tracks_info = []
            for track in top_tracks[:6]:
                track_info = {
                    'name': track['name'],  # 曲名を取得
                    'release_date': track['album']['release_date'].split('-')[0],  # リリース年を取得
                    'image_url': track['album']['images'][0]['url']  # アルバムの画像URLを取得
                }
                top_tracks_info.append(track_info)
            # トップトラック情報をアーティスト名をキーにして辞書に追加
            top_tracks_dict[artist] = top_tracks_info
    
    return top_artists_list, top_tracks_dict

get_artist_uri(name)get_related_artist_info(uri)networkxライブラリを使用してネットワークを作り、その中からページランクの高い順に関連のあるアーティストを10人取り出して、top_artists_list辞書に格納しています。

一般的に、G = nx.Graph()はグローバル変数として関数の外で定義しますが、今回のアプリケーションでそのように定義してしまうと、ユーザが1回目に入力したアーティスト情報のネットワーク結果が2回目以降の入力にも影響を与えてしまいます。今回はそれを防ぐため、関数内に定義しました。これにより、ユーザの入力ごとにget_top_artists(artist_name)を利用してネットワーク図を作成することができ、以降の入力時に影響を与えないようにしています。

また、今回は取り出したアーティストの人気曲を6曲取り出し、それも出力するようにしました。具体的には、artist_top_tracks(uri)['tracks']で人気曲の情報を取得し、「曲名」・「アルバム収録年」・「アルバム画像」をtop_tracks_dict辞書に格納しています。

Webアプリケーションの初期画面とリザルト画面の設定

app.py
#初期画面
@app.route('/', methods=['GET', 'POST'])
def index():
    if request.method == 'POST':
        artist_name = request.form['artist_name']
        return redirect(url_for('result', artist_name=artist_name))
    return render_template('index.html')

#リザルト画面
@app.route('/result/<artist_name>')
def result(artist_name):
    top_artists_list, top_tracks_dict = get_top_artists(artist_name)
    if not top_artists_list:
        return render_template('error.html', artist_name=artist_name)
    return render_template('result.html', artist_name=artist_name, top_artists_list=top_artists_list, top_tracks_dict=top_tracks_dict)

初期画面(index.html)とリザルト画面(result.htmlerror.html)のバックエンド側を上記のように実装しました。HTMLテンプレートの中身は次回以降の記事で書きます。

 初期画面は、以下のように実装しています。
①ユーザーが初めてページにアクセスしたときや、フォームを送信せずにページをリロードしたときindex.htmlテンプレートをレンダリングして表示する。
②クライアント側からPOSTリクエストを受け取ったらartist_nameパラメータを使用してresultという名前のルート(/)にリダイレクトする

 一方で、リザルト画面は、以下のように実装しています。
artist_nameはURLパスから取得され、関数の引数として渡す
top_artists_listが空(つまり、関連アーティストが見つからない)場合、error.htmlテンプレートをレンダリングして表示する。その際、artist_nameをコンテキストとしてテンプレートに渡す。
top_artists_listが空でない場合、result.htmlテンプレートをレンダリングして表示する。その際、artist_nametop_artists_listtop_tracks_dictをコンテキストとしてテンプレートに渡す。

終わりに

今回は、Webアプリケーションのバックエンド側の実装についてまとめました。次回以降では、HTMLテンプレートの中身やCSSの定義.envファイルについてを書こうと思います。

参考文献

以下の記事を参考にしました。ありがとうございました。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1