はじめに
大学の授業で、入力されたアーティストと近しいアーティストのネットワーク図を作成するシステムを作ったので、今回は個人的な趣味で、そのシステムを発展させてWebアプリケーションを開発しました。その際の備忘録を残します。
part2では、フロントエンド側の実装についてまとめます。
Webアプリケーションの大まかな流れ
- 初期画面で入力フォームを表示し、ユーザにアーティスト名を入力させる
- 入力されたアーティストに対して、Spotipyのartist_related_artists()を使用して、そのアーティストと関連のあるアーティスト情報を取得する
- networkxライブラリを使用して、関連のあるアーティストを芋づる式にノードで結んでネットワーク図を作り、ページランクの値が高い順に取り出す(この時、人気曲の情報も取得する)
- リザルト画面で関連のあるアーティスト10人と取得した人気曲の情報を表示する
今回の備忘録では、1.と4.のフロントエンド側の実装についてまとめます。
ディレクトリ構造
前回記事で紹介したapp.py
と今回記述するindes.html
、result.html
、error.html
、style.css
のディレクトリ構造は以下の通りです。
{プロジェクト名}/
├── app.py
├── static/
│ └── style.css
└── templates/
└── index.html
└── result.html
└── error.html
app.py
はルートディレクトリ直下に、indes.html
、result.html
、error.html
の3つはtemplates
ディレクトリに、style.css
はstatic
ディレクトリに格納しています。
フロントエンド側の実装
初期画面の実装
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
<title>Artists Recommender</title>
</head>
<body>
<div class="container">
<h1>Welcome to Artists Recommender</h1>
<form action="/" method="POST">
<label for="artist_name">
あなたの好きなアーティストと関連度(*)の高いアーティストを10人推薦します。<br>
アーティスト名を<strong>ローマ字</strong>で入力して下さい。<br><br>
<div class="upper-note">
(例) 嵐 >> ARASHI <br>
平井堅 >> Ken Hirai<br>
</div>
</label>
<div class="input-wrapper">
<input type="text" id="artist_name" name="artist_name">
</div>
<div class="button-wrapper">
<input type="submit" value="OK" class="button1">
</div>
<br>
<div class="under-note">
*「関連度」は、Spotifyコミュニティの視聴履歴の分析に基づいています。
</div>
</form>
</div>
</body>
</html>
この画面は、ユーザーが初めてページにアクセスしたときや、フォームを送信せずにページをリロードしたときに表示するHTMLテンプレートです。
<input type="text" id="artist_name" name="artist_name">
は、ユーザの入力フォームとなっていて、文字列を入力することができます。
<input type="submit" value="OK" class="button1">
は、送信ボタンとなっていて、サーバにPOSTリクエストを送ります。
リザルト画面(正常画面)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
<title>Artist Network Result</title>
</head>
<body>
<div class="container">
<h1>以下が「{{ artist_name }}」と関連度の高いアーティストです</h1>
<div class="note">※注 記載のリリース年はアルバム収録年です。</div>
<ul>
{% for artist in top_artists_list %}
{% if loop.index == 1 %}
<li><div class="first_order">{{ loop.index }}st. {{ artist }} </div><br><br>
{% for track_info in top_tracks_dict[artist] %}
{{ track_info['name'] }}({{ track_info['release_date'] }})<br>
{% if track_info['image_url'] %}
<img src="{{ track_info['image_url'] }}" alt="{{ track_info['name'] }}のアルバムアート" width="100" height="100"><br>
{% else %}
<p>画像なし</p><br>
{% endif %}
{% endfor %}</li>
{% elif loop.index == 2 %}
<li><div class="second_order">{{ loop.index }}nd. {{ artist }} </div><br><br>
{% for track_info in top_tracks_dict[artist] %}
{{ track_info['name'] }}({{ track_info['release_date'] }})<br>
{% if track_info['image_url'] %}
<img src="{{ track_info['image_url'] }}" alt="{{ track_info['name'] }}のアルバムアート" width="100" height="100"><br>
{% else %}
<p>画像なし</p><br>
{% endif %}
{% endfor %}</li>
{% elif loop.index == 3 %}
<li><div class="third_order">{{ loop.index }}rd. {{ artist }} </div><br><br>
{% for track_info in top_tracks_dict[artist] %}
{{ track_info['name'] }}({{ track_info['release_date'] }})<br>
{% if track_info['image_url'] %}
<img src="{{ track_info['image_url'] }}" alt="{{ track_info['name'] }}のアルバムアート" width="100" height="100"><br>
{% else %}
<p>画像なし</p><br>
{% endif %}
{% endfor %}</li>
{% else %}
<li><div class="other_order">{{ loop.index }}th. {{ artist }} </div><br><br>
{% for track_info in top_tracks_dict[artist] %}
{{ track_info['name'] }}({{ track_info['release_date'] }})<br>
{% if track_info['image_url'] %}
<img src="{{ track_info['image_url'] }}" alt="{{ track_info['name'] }}のアルバムアート" width="100" height="100"><br>
{% else %}
<p>画像なし</p><br>
{% endif %}
{% endfor %}</li>
{% endif %}
{% endfor %}
</ul>
<a href="/" class="button2">戻る</a>
</div>
</body>
</html>
この画面は、初期画面でユーザがSpotifyデータベースに存在するアーティスト名を入力し、前回記事で紹介したapp.py
が正常に動作した時(top_artists_list
が空でない場合)に表示するHTMLテンプレートです。
順位の表示の部分で、1位は1の後ろに「.st」、2位は「.nd」、3位は「.rd」、4位から10位は「.th」となるように記述しました。
top_tracks_dict
辞書は、キーにartist
を入れると、値としてそのアーティストの人気曲の3つの情報(「曲名」・「アルバム収録年」・「アルバム画像」)を返す辞書なので、それらを表示するように記述しています。
<a href="/" class="button2">戻る</a>
は、初期画面に戻るボタンです。
リザルト画面(エラー画面)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
<title>Artist Not Found</title>
</head>
<body>
<div class="container">
<h1>入力されたアーティスト名は対応しておりません</h1>
<p>「{{ artist_name }}」はSpotifyのデータベースに存在しないか、対応していません。</p>
<p>ローマ字で入力するか、他のアーティスト名を入力してください。</p>
<a href="/" class="button2">戻る</a>
</div>
</body>
</html>
この画面は、初期画面でユーザがSpotifyデータベースに存在しないアーティスト名を入力し、前回記事で紹介したapp.py
でtop_artists_list
が空(つまり、関連アーティストが見つからない)場合に表示するHTMLテンプレートです。
CSSの実装
@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap');
body {
font-family: 'Roboto', sans-serif;
background-color: #f0f0f0;
margin: 0;
padding: 0;
}
.container {
max-width: 800px;
margin: auto;
padding: 20px;
padding-right: 20px;
text-align: center;
}
.upper-note {
margin-right: 80px;
}
.under-note {
text-align: left;
display: inline-block;
margin-top: 30px;
}
input[type="text"] {
width: 300px;
padding: 10px;
margin-top: 10px;
margin-right: -200px;
}
input[type="submit"] {
padding: 10px 20px;
background-color: #007bff;
color: white;
border: none;
cursor: pointer;
margin-top: 10px;
margin-left: -100px;
}
input[type="submit"]:hover {
background-color: #0056b3;
}
.input-wrapper {
display: inline-block;
width: 50%;
margin-right: 50px;
}
.button-wrapper {
display: inline-block;
width: 28%;
}
ul {
list-style-type: none;
padding: 0;
}
li {
margin-bottom: 10px;
background-color: #ffffff;
padding: 10px;
border-radius: 5px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.button2 {
padding: 10px 20px;
background-color: #007bff;
color: white;
border: none;
cursor: pointer;
}
.button2:hover {
background-color: #0056b3;
}
.first_order{
color:#dbb400;
font-size: 35px;
font-family: 'Roboto', sans-serif;
}
.second_order{
color:#9fa0a0;
font-size: 30px;
font-family: 'Roboto', sans-serif;
}
.third_order{
color:#c47022;
font-size: 25px;
font-family: 'Roboto', sans-serif;
}
.other_order{
font-size: 20px;
font-family: 'Roboto', sans-serif;
}
/* スマホ向けのスタイル */
@media (max-width: 768px) {
.upper-note {
margin-right: 70px;
}
.input-wrapper, .button-wrapper {
display: block;
width: 100%;
text-align: center;
}
input[type="text"] {
width: calc(100% - 20px);
margin: 10px auto;
}
input[type="submit"] {
width: calc(100% - 20px);
margin: 10px auto;
}
}
初期画面とリザルト画面の装飾部分の実装です。フォントはGoogleapiからRoboto
をインポートして使用しています。
中央揃えで表示させたかったので、基本的にtext-align: center;
で記述しています。(一部見た目上の都合で、text-align: left; display: inline-block;
として、左揃えになるように記述しているところもあります。)
PCだけでなく、スマホにも対応したWebアプリケーションを開発したかったので、後半部分ではスマホに対応したCSSを実装しています。
margin
の設定方法が分からなかったので、マイナスの値になってしまっています。
実際の画面
以下のように表示されます。
初期画面(PC版)
初期画面(スマホ版)
リザルト画面(正常)
リザルト画面(エラー)
「いきものがかり」と入力した場合
エラー原因:ローマ字で入力しなければならない
最後に
今回は、Webアプリケーションのフロントエンド側の実装についてまとめました。次回以降では、.envファイルを用いた環境変数の管理方法やGit、Dockerの操作方法などを書こうと思います。
参考文献
以下の記事を参考にしました。ありがとうございました。