Spotify でワードクラウドを作って自己紹介できるアプリを Flask でつくった話

皆さん、Spotify を使っていますか？
私はほぼ毎日使っています。レコメンドシステムがすごいですよね。
実は Spotify は[様々な API](https://developer.spotify.com/documentation/web-api/reference-beta/) を提供していて、その中には **Personalization API** という、**自分のお気に入りのアーティストや曲の情報を取得する API** もあるのです。この API を使えば、自分がどんなアーティストが好きかを自己紹介するのに使えます！

この画像を見てください。

![example.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233069/146b3cc4-17ef-8f99-8431-ff31f00d501b.png)

これは私のお気に入りのアーティストから作成された**ワードクラウド**です。ワードクラウドとは、文章中で出現頻度が高い単語を選び出し、その頻度に応じた大きさで図示する手法です。今回は、API で取得されたアーティストのデータのうち上位にあったものをより大きな文字にしています。

というわけで、こんなワードクラウドを自動で作る Web アプリを作ってみました。↓↓↓

https://spotify-word.cloud/

Spotify のアカウントでログインするだけで、自動でワードクラウドが作成されます。また、作った画像をこんな風にツイートすることもできます。

![スクリーンショット 2020-04-30 16.31.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233069/52b7aea1-4c66-665d-8288-0589a8cad50d.png)

色々な人のワードクラウドが見てみたいので、是非皆さんにも使っていただければと思います。
以下、このアプリを Flask で作る上でのポイントについて述べます。ソースコードは

https://github.com/HelloRusk/spotify-wordcloud

のようになっています。

# ワードクラウドの作成は Python の word_cloud で

ワードクラウドを作るライブラリとして、Python の [word_cloud](https://github.com/amueller/word_cloud) モジュールを使うか、D3.js の D3-cloud というプラグインを使うかで少し悩みました。D3.js の場合、クライアントサイドの JavaScript で描画できるというメリットはありましたが、word_cloud の方が配色がきれいだと感じたので、結局サーバーサイドでワードクラウドを作成する API を立てることにしました。
ちなみに word_cloud の配色の指定は、matplotlib のカラーマップの指定と全く同じです。

ワードクラウドの作成は

```python
data = spotify.get("/v1/me/top/artists?limit=50").json()

for i, item in enumerate(data['items']):
    for k in range((50-i)//10):
        text += item['name']
        text += " "

wc = WordCloud(font_path='/app/.fonts/ipaexg.ttf', width=1024, height=576, colormap='cool', stopwords=set()).generate(text)
```

このような感じでやっています。

- 上位のアーティスト名ほど、ワードクラウドの元となる文字列により多く加えて頻度を高くする
- 日本語が文字化けしないように日本語フォントを指定してあげる
- デフォルトだと接続詞や be 動詞のような頻度が高いが意味があまりない単語（自然言語処理界隈で**ストップワード**と呼ばれている）はワードクラウドで使わないようにされているものの、アーティスト名の中のストップワードは重要であり使用したいため、`stopwords` は空集合にする

などの工夫を入れています。

**2021年1月現在、もう少し細かいロジックを加えています。詳しくはソースコードをご覧ください。**

# Spotify の認証は flask_dance で

そもそも Flask でウェブアプリを作るのが初めてだったのですが、この [flask_dance](https://flask-dance.readthedocs.io/en/latest/) は分かりやすい API と豊富なプロバイダが提供されており、使う上で安心しました。

例えば Spotify に関しては

https://flask-dance.readthedocs.io/en/latest/providers.html#module-flask_dance.contrib.spotify

にある通りにメソッドを呼び出せばよく、

```python
app.secret_key = environ.get("SECRET_KEY")
app.config["SPOTIFY_OAUTH_CLIENT_ID"] = environ.get("SPOTIFY_OAUTH_CLIENT_ID")
app.config["SPOTIFY_OAUTH_CLIENT_SECRET"] = environ.get("SPOTIFY_OAUTH_CLIENT_SECRET")
spotify_bp = make_spotify_blueprint(scope="user-top-read")
app.register_blueprint(spotify_bp, url_prefix="/login")
```

このような感じになりました。
ちなみに、Spotify API を呼び出す際に注意せねばならないのは**スコープ**です。今回使いたい、お気に入りのアーティスト・曲を取得する API では `user-top-read` というスコープを指定する必要があるので
 
```python
spotify_bp = make_spotify_blueprint(scope="user-top-read")
```

となっているのです。

# 画像つきツイートは tweepy で

2020年4月現在、画像付きツイートを Twitter で行う流れは、まず

```
POST https://upload.twitter.com/1.1/media/upload.json
```

で画像をアップロードして `media_id` を得た後、通常のツイート投稿 API

```
POST https://api.twitter.com/1.1/statuses/update.json
```

に `media_id` をパラメータで含めるという、二段構成になっています。

最初は Twitter の認証も flask_dance で行うつもりだったのですが、Twitter への画像アップロードの方が上手くいかず、断念しました。。。（エントリポイントが違うから上手くいかなかったような気がしますが、原因は謎でした）

結局、Python の Twitter API ラッパーとして昔から定評のある [tweepy](https://github.com/tweepy/tweepy) を使って解決しました。以下のように2行で画像付きツイートができます。

```python
res = api.media_upload(filename)
api.update_status("Spotifyで自己紹介！\n#Spotify_Wordcloud\nhttps://spotify-wordcloud.herokuapp.com/", media_ids=[res.media_id])
```

余談ですが、Twitter のような変化の激しい API だと、ちょっとメンテされていない間にライブラリが陳腐化しそうなので、怖いですね。。。tweepy は現在もきちんとメンテされているようなのでよかったです。

**2021年1月現在、OGP画像でツイートするようにしたため、Twitter認証は不要になりました。**

# セキュリティ面の対策は flask-talisman や flask-seasurf で

[flask-talisman](https://github.com/GoogleCloudPlatform/flask-talisman) は主に HTTP ヘッダ関連のセキュリティ対策（Content Security Policy とか）を行うやつです。
といっても、CSP をあんまり強くしてしまうと Spotify の inline script とかが全部ダメになってしまうので、`unsafe-inline` を入れざるを得なかったのですが。。

[Flask-SeaSurf](https://github.com/maxcountryman/flask-seasurf) は CSRF 対策のライブラリです。今回は外部認証を経由しない POST が無いので、トークンを html 中には入れていないのですが、今後内部的な POST/DELETE を用意する際には CSRF トークンを入れてあげると良さそうです。

他にもセキュリティ的にこうした方がいいんじゃない？みたいなツッコミがあれば是非コメント欄にお願いします。

**2021年1月現在、Flask-SeaSurf ではなく [Flask-WTF](https://flask-wtf.readthedocs.io/en/stable/) を使っています。こちらのライブラリの方がテストが書きやすいです。**

# おわりに

Flask を使うのは初めてだったのですが、1日半程度で自分の欲しかったものが実現できたのでよかったです。

そもそもこのアプリを作った動機として、どんな音楽が好きなの？と尋ねられた際にいまいち上手く答えられなかった、ということがありました。このアプリを使えば、好きな音楽をビジュアルでアピールすることができます。

今後ですが、作成したワードクラウドの画像を S3 のようなストレージに保存できるようにし、これまで作ったワードクラウド一覧をDBから閲覧できるようにすることで、音楽の趣味の変遷を確認できるような機能を実装することを ~~検討しています。~~

**2021年1月現在、Google Cloud Storage に画像を保存できるようにしています。**