蛇ノ目の記

技術のことも。そうでないことも。

PyCon mini Sapporo 2019で登壇した - Simple-Spotify: Pythonでシンプルに楽曲データを扱うライブラリ

5月11日に札幌で開催されたPyCon mini Sapporo 2019で登壇した。

写真撮る人がめっちゃ上手くて影とかいい感じになっててすごい。正装(オサカナTシャツ)もばっちり写っている。

テーマは3ヶ月くらい前から作っていたSpotify Web APIのラッパーライブラリSimple-Spotify。懇親会ではUKロック好きな@shinyorkeとSimple-Spotifyのいい感じな使い方の話で盛り上がった。興味を持ってもらえてとても嬉しかった。

github.com

developer.spotify.com

Spotify Web APIのラッパーライブラリとしてはSpotipyがあるが、requestsに依存しているそれとは異なりSimple-SpotifyはPure Pythonで実装している。 また、SpotipyがAPIのレスポンスを辞書形式で扱っているのに対してSimple-Spotifyではクラスとして扱っている。

Simple-SpotifyとSpotipyの違いを見比べてみる。ここからは発表にはない話になる。

Spotipy

import spotipy
sp = spotipy.Spotify()

results = sp.search(q='weezer', limit=20)
for i, t in enumerate(results['tracks']['items']):
    print ' ', i, t['name']

Simple-Spotify

from simple_spotify.api import Spotify
from simple_spotify.authorization import ClientCredentialsFlow

res = ClientCredentialsFlow.token_request(CLIENT ID, CLIENT_SECRET)
auth = ClientCredentialsFlow(**res)
sp = Spotify(auth)

results = sp.search(q='sora tob sakana', market='JP')
for album in results.albums.items:
    print(album.name)
sora tob sakana
World Fragment Tour
New Stranger
alight ep
cocoon ep
魔法の言葉
夜空を全部
アルファルド

sora tob sakanaはいいぞ。

Spotipyでは認証を行っていないんだけど、Web APIのドキュメントにはすべてのリクエストは認証が必要、と書いてあるのでサンプルコードが古い可能性がある。

Authentication

All requests to Web API require authentication.

https://developer.spotify.com/documentation/web-api/

認証を含めなければ行数はほぼ同じ。 検索結果(results)オブジェクトにアルバム(albums)属性があって、さらにそれぞれのアルバム(items)がある、というのはそこそこ直感的でないかと思っている。

認証について言えば、認証用のエンドポイントからのレスポンスを一度受け取ってから、それをアンパックして認証クラスに渡すというのはちょっとわかりにくい流れになってしまった。 本当のところを言えば、認証用クラスにClient IDとClient Secretを渡せばOKという風にしたかったのだけど、もう一つの認証をWebアプリ上で使えるようにする関係でそれと同じ流れにした。

これがFlaskで作成したSimple-Spotifyを使った簡単なWebアプリ。

@app.route('/')
def top():
    if 'response' in session.keys():
        auth = AuthorizationCodeFlow(**session['response'])
        sp = Spotify(auth)
        user = sp.get_current_user_profile()
        top_artists = sp.get_users_top('artists', time_range='medium_term')
        artists = top_artists.items
        top_tracks = sp.get_users_top('tracks', time_range='medium_term')
        tracks = top_tracks.items

        return render_template('index.html', name=user.display_name, artists=artists, tracks=tracks)
    else:
        seed = ''.join(random.choices(string.ascii_letters + string.digits, k=32))
        url = access_authorize_page(CLIENT_ID, REDIRECT_URI, SCOPES, seed)
        return render_template('index.html', auth_url=url)


@app.route('/callback/')
def callback():
    """
    callback
    """
    session['response'] = AuthorizationCodeFlow.token_request(
        CLIENT_ID, CLIENT_SECRET, REDIRECT_URI, request.args['code'])
    return redirect(url_for('top'))

Spotifyへのログインをしてからアクセストークンを受け取るのだけど、アクセストークンの取得をtop()で行ってしまうとリロードやページ遷移の度にトークンを取りに行こうとしてAPIからエラーが返ってきてしまった。これを避けるためにコールバックでアクセストークンなどを受け取って、セッションに格納。top()でそれを使うという流れにした。 これには認証方法によってSimple-Spotifyの使い方に違いが出てしまうのを避けたかったという思いがある。

と言い訳めいたことを書いてから、実際にSimple-Spotifyの使い方などを書きたいと思うのだけど寒いのでしばらくしてから続きを書く。 なにしろこんなところで書いているので風が冷たい。

みんなのPython勉強会#44を企画・登壇した - 正規表現の話

4/10に開催されたみんなのPython勉強会#44(stapy)を企画・登壇した。

平成が終わる前に書かないと、と思いつつここまで先延ばしにしてしまった。でもまだ平成なのでギリギリセーフということでどうかひとつ。

startpython.connpass.com

平成最後のstapyということで平成生まれである自分が主導し、自分を含めた6人の平成生まれPythonistaによるショートトーク会を企画した。

登壇してくれた皆さんありがとうございました。

今回の内容はPythonにおける正規表現\w (any word character) の振る舞い。

GitPitch Presents: github/NaoY-2501/GitPitch-Slides

Modern Slide Decks for Developers on Linux, OSX, and Windows 10. Preview and present offline. Publish and share online.

以下のリポジトリ\wUnicodeのLetter, Numberカテゴリのうちどれだけの文字にマッチするか、Punctuationカテゴリではアンダースコア以外にマッチしないかを確認した。

GitHub - NaoY-2501/stapy44_re_unicode_checker

Letterカテゴリの中でもマッチしない文字がある。

https://www.fileformat.info/info/unicode/category/ ではLetterカテゴリに分類されているが、unicodedataモジュールで確認するとCn(Other or not assigned)となっている。

>>> import unicodedata
>>> unicodedata.category('\ua7ba')
'Cn'

つまり、公式ドキュメントにある通りであることがわかる。

ユニコードパターンに対しては、 \w は unicodedata モジュールで提供されている Unicode データベースで letters としてマークされている全ての文字とマッチします。

正規表現 HOWTO — Python 3.7.3 ドキュメント

Special Thanks

Thank you for speakers !!

nikkie(@ftnext): P(ython)&I 〜最初の落とし穴を避け、成功体験を積むために〜

山下 卓将(@xxx_boy): Python と挑んだtitanic ~101回目のsubmit~

大島和輝(@shimakaze_soft): FlaskとDjango以外のAPI開発の選択肢

みずき(@mizzsugar0425): DjangoとPyramidで同じアプリ作った話

清原 弘貴(@hirokiky): 無題 (僕の平成について話します)

pyhack冬山合宿に行ってきた - SpotifyチャートにTF-IDFを使ってみた話(概要編)

1/19(金)~1/21(日)にかけてpyhack冬山合宿に行ってきた。

気づいたらもう3月。記事を書くのがかなり遅れてしまった。

pyhack.connpass.com

@takanoryによるTogetterまとめはこちら。Python mini Hack-a-thon 雪山合宿 2019 - Togetter

昨年はsiraphのライブがあったので2日目の昼に帰ってきたんだけど、今年は3日間通しで参加した。

合宿でやったこと

Spotifyのチャートに対して、文書の特徴量を求めるのに使われるTF-IDFを使って国ごとに特徴的な曲を分析してみる、ということをやってみた。

グローバルチャートに対する国ごとにユニークな曲を抽出するなら、グローバルチャートと各国チャートの差集合を取るだけでいいのでは、という向きもあると思うけど、まあ思いつきでやったことなので多めに見てほしい。

Spotify Charts

Spotify Chartsでグローバル・各国の人気曲チャートが公開されている。チャートには以下の2種類がある。

  • Top 200: ストリーミング数による集計。上位200曲が公開されている。

  • Viral 50: SNS上での人気を基にした集計。上位50曲が公開されている。

Viral 50の集計に関しては以下のフォーラムで議論されているが、おそらくソーシャルメディアを分析したものとのこと。

https://community.spotify.com/t5/Content-Questions/Viral-50-Playlists-How-do-they-work/td-p/1627778

今回は2種類のチャートそれぞれを対象とした。

データの取得

前回の記事で紹介したSpotify APIではチャートの取得ができないため、Spotify Chartsを使うことにした。

Spotify Charts Top200, Viral 50のWebページをスクレイピングしてチャートを取得した。

URLはTop 200ならhttps://spotifycharts.com/{{ country code }}/, Viral 50ならhttps://spotifycharts.com/viral/{{ country_code }}/とシンプルな作りになっているのでスクレイピングが容易。

{{ country_code }}ISO 3166-1 alpha-2 - Wikipediaに基づいている。ただし、グローバルチャートの場合はglobalとなる。

国によってはTop 200やViral 50が取得できない場合がある。このとき、レスポンスヘッダに'Content-Encoding'が含まれる。これを考慮しておかないとチャートの取得でコケるので注意が必要。

TF-IDF

TF-IDFは文書の特徴量として使われる指標。文書中に出現する単語の重要度を評価できる。

TFとIDFは以下のように説明できる。

  • TF(Term Frequency): 文書dにおける単語tの出現頻度

  • IDF(Inverse Document Frquency): 逆文書頻度。この値が高ければ単語tの出現頻度が低いということになる。

TF-ICF

TF-IDFが文書の特徴量なら、今回考えるのはトラックの特徴量だろうということで、TF(Track Frequency), ICF(Inverse Chart Frequency)と呼び替えることにする。


実際のコードの話は別の記事で。

SpotifyのAPIを使ってみた

最近Spotifyを契約し始めたのだけど、Spotifyには曲やアルバム、プレイリスト、アーティストの情報を取得したりできるAPIがあるとのことなので試してみた。

Spotipyというラッパーライブラリがあるようだけど、今回はそれを使わずにrequestsを使ってAPIを使ってみる。

アプリの登録

APIを使うにはFreeかPremiumのユーザーである必要がある。登録はhttps://www.spotify.com/jp/から。

ユーザー登録したらダッシュボードページにログインして、アプリ登録をする。

f:id:Nao_Y:20190117234242p:plain

作成したアプリをクリックする。

f:id:Nao_Y:20190117234313p:plain

Client IDSHOW CLIENT SECRETを押して表示されるClient Secretを控えておく。

認証フロー

Spotify APIには3つの認証フローがある。

  • Refreshable user authorization: Authorization Code Flow

長期間稼働するアプリ向けの認証。エンドユーザーは一度だけアプリがリソースにアクセスすることを許可する。アプリにはリフレッシュ可能なアクセストークンが提供される。バックエンドなど安全な場所から実行する。

  • Temporary user authorization: Implicit Grant

JavaScriptを使用するため、リソース所有者のブラウザ上で動作するアプリ向けの認証。

  • Refreshable app authorization: Client Credentials Flow

サーバー間認証で使用される。エンドユーザーのリソースにアクセスしないエンドポイントのみ使用可能。

参考:

アクセストークンの取得

今回はClient Credentials Flowを使う。この認証方法ではClient IDとSecret Keyを使ってAccess Tokenを取得する。

  • Endpoints: POST https://accounts.spotify.com/api/token

  • Request Body Parameter: grant_type(client_credentialsを設定する)

  • Header Parameter: Basic <base64 encoded client_id:client_secret>

import requests

CLIENT_ID = 'YOUR CLIENT ID'

CLIENT_SECRET = 'YOUR CLIENT SECRET'

GRANT_TYPE = 'client_credentials'

TOKEN_URL = 'https://accounts.spotify.com/api/token'

body_params = {'grant_type': GRANT_TYPE}

auth = requests.post(TOKEN_URL, data=body_params, auth=(CLIENT_ID, CLIENT_SECRET)).json()

requestsでHTTP Basic auth(Basic認証)のあるURLにアクセスするときはキーワード引数auth(User, Password)とする。

この後、他のAPIで認証をするときにアクセストークンを使うので辞書形式でレスポンスを取得している。

参考:

アルバムを検索する

  • Endpoints: GET https://api.spotify.com/v1/search

  • Header Fields: Authorization

    • Value: 有効なアクセストークン。Bearer {{access token}}の形式。
  • Query Parameter(必須のものだけ抜粋)

    • q : 検索クエリ

    • type: 検索タイプ。有効な値はalbum, artist, playlist, trackでリストで指定可能

参考:

sora tob sakanaのアルバム・シングルを検索してみる。

header_params = {'Authorization': 'Bearer {}'.format(auth['access_token'])}

query_params = {'q': 'sora tob sakana', 'type': ['album']}

ENDPOINT = 'https://api.spotify.com/v1/search'

res = requests.get(ENDPOINT, headers=header_params, params=query_params)

for each in res.json()['albums']['items']:
    artist = each['artists'][0]
    url = each['external_urls']['spotify']
    print('{}: {} {} ({})'.format(artist['name'], each['name'], each['release_date'], url))
sora tob sakana: sora tob sakana 2016-07-26 (https://open.spotify.com/album/3cJxQS1oi8u2oBu9uhVyEk)
sora tob sakana: cocoon ep 2017-04-11 (https://open.spotify.com/album/467R6azC01pl67nSrLS55x)
sora tob sakana: mahou no kotoba 2016-02-16 (https://open.spotify.com/album/6x0xVz6hdHerGMf2yaMqLj)
sora tob sakana: yozora wo zenbu 2015-10-27 (https://open.spotify.com/album/2FezsFEuQFferFLy4syK4T)

どうしたことか。2017-04-11以降の作品が取得されない。よく見るとタイトルがアルファベット表記になっている。日本国内の設定になっていないっぽい。marketパラメータを指定してみる。このパラメータはISO 3166-1 alpha-2 country codeで表記する。日本はJP

header_params = {'Authorization': 'Bearer {}'.format(auth['access_token'])}

query_params = {'q': 'sora tob sakana', 'type': ['album']}

ENDPOINT = 'https://api.spotify.com/v1/search'

res = requests.get(ENDPOINT, headers=header_params, params=query_params)

for each in res.json()['albums']['items']:
    artist = each['artists'][0]
    url = each['external_urls']['spotify']
    print('{}: {} {} ({})'.format(artist['name'], each['name'], each['release_date'], url))

2017-04-11以降の作品が表示されて、タイトルも日本語表記になった。日本のアーティストを検索するならmarketJPを指定するのがよさそう。海外勢はalight epとかNew Stranger聴けないのかー。

sora tob sakana: sora tob sakana 2016-07-26 (https://open.spotify.com/album/6wy5MYR1ThaC7IuQu3VHHG)
sora tob sakana: New Stranger 2018-07-25 (https://open.spotify.com/album/1ATJgjPZ7Qz0LAUZtMW2Sp)
sora tob sakana: alight ep 2018-05-16 (https://open.spotify.com/album/36w9htd6tgXmNs0iPWte2P)
sora tob sakana: cocoon ep 2017-04-11 (https://open.spotify.com/album/3nN8No2a5ZhzK2xw9RIYQp)
sora tob sakana: アルファルド 2018-11-23 (https://open.spotify.com/album/3nA6Sd5gDIY8xHiVQQ3kvE)
sora tob sakana: 魔法の言葉 2016-02-16 (https://open.spotify.com/album/0UejaOY3nZq4h3fZ2be8lA)
sora tob sakana: 夜空を全部 2015-10-27 (https://open.spotify.com/album/03JuPAwApUfvx61cHvUmOJ)

APIを使ってアルバムを検索することができたので、この辺りで一旦区切りをつけておく。

今後はGET https://api.spotify.com/v1/searchにおけるワイルドカードの使い方(例えば、SawanoHiroyuki[nZk]:~をワイルドカードで検索)やプレイリストの検索を調べてみたい。

2019年ブログ初め

遅ればせながら、あけましておめでとうございます。

早々に「やってみた系」でアイデアが浮かんだんだけど、それより前にブログ初めをしておかねばという謎の義務感に駆られたので簡単に2019年にやりたいことを書いて2019年のブログ初めとする。

以下、目標と願望めいたやつを一緒くたに書きなぐる。

技術

  • 海外ブログの翻訳をやってみる

    • 去年末から着手してる。来月には公開できる、はず。
  • 積読の技術書を読む

    • Web, 機械学習いろいろ積んでる

    • 新しく買う前に読め

  • herokuを使わないWebサービスを作る

    • AWSのサービスをいろいろ使ってやってみたい

    • 2018年は仕事を通してEC2, route53, RDS, S3に触れたので経験を活かしたい

    • nginxもほんの少しわかるようになったのでそれも活かしたい

  • JavaScript覚える

    • そもそもJavaScript全然わからない

    • Vue.jsとか名前聞いたことあるレベルでしかないのでPythonのWebフレームワークと組み合わせる方法を身に付けたい

  • 英語を話す

    • オンライン英会話が気になってるけど、実際どういう感じかわからなくて手が出せてない

    • あと高そう

コミュニティ

  • stapyでイベント企画する

  • PyCon JPスタッフ業(3年目)がんばるぞい

    • コンテンツ盛り上げたい

    • 領収書発行サイトのカイゼン

  • もくもく会の参加率上げる

趣味

  • デジタルに手を出す

    • サブ機としてミラーレスを導入したい
  • 写真で同人誌出してみたい

    • C95で友人が出してたので続きがあるならいっちょ噛みする
  • 百合ポートレートを撮りたい

    • モデルのあてがビタイチない
  • 放置気味のギターを弾く


2019年もよろしくお願いします。

2018年を振り返る

年の瀬なので今年を振り返ってみる。という去年とまったく同じ書き出し。

nao-y.hatenablog.com

仕事

2018年はPythonエンジニアとして働いた初めての一年だった。

技術

Pythonだけでなく、AWS(IAM, S3, RDS, Route53)やnginxを触ることができた。

AWSではIAMユーザーを作ることを覚え、個人で作っているDiscord botでDynamoDBを使うときに応用できた。nginxではバーチャルサーバの基本的な設定に加えて、Let's Encryptによるサーバ証明書、OpenSSLによるクライアント証明書の有効化を覚えた。

執筆

今年の初めに書いたブログで「書く実績を解除する」ことを目標の一つにしていた。

ブログ初めには遅すぎた - 蛇ノ目の記

Software Design9月号の記事を書くという形で達成できた。編集者の方やレビュアーのBPメンバーには本当に感謝。

nao-y.hatenablog.com

本についてはもう一点。「スラスラ読める Pythonふりがなプログラミング」の監修をやらせていただいた。世の中に数多くあるPython入門本の入り口となる一冊で、超入門書といえる。

book.impress.co.jp

親にこの本の監修をしたことを話したところ、これまでプログラミング未経験だった父親が1冊通して読んでプログラミングの便利さを理解できたと言ってくれた。

Pythonコミュニティ

みんなのPython勉強会

1月のイベントで登壇した。

startpython.connpass.com

「Pythonでスタートして、Pythonを仕事にするまでの話」
横山 直敬(ビープラウド)
みんなのPython勉強会がきっかけで勉強を再開し、がっつりPythonをやる会社に入社しました。
Pythonによるキャリア形成の話やビープラウドに入ってからの話をします。

Pythonを独学して自分が面白いと思うことをやっていくうちにPythonが本業になった話をした。LTを目標に勉強する「LT駆動開発」なんて言葉を使っていたら、思いもよらないところで他の人に影響を与えていて本当に驚いた。

GitPitch Presents: github/NaoY-2501/GitPitch-Slides

The Fastest Way from Idea to Presentation for everyone on GitHub, GitLab, and Bitbucket.

PyCon APAC 2018

初の海外カンファレンス参加。そしてシンガポールに初上陸。アレな英語力を総動員して必死にトークを聴き取ろうとがんばっていた。

PyCon APAC 2018 カテゴリーの記事一覧 - 蛇ノ目の記

ここで聴いたFlaskによる機械学習WebアプリのトークはPyQの問題作成に活かすことができた。

blog.pyq.jp

帰国から即みんなのPython勉強会に参加して最速でPyCon APAC 報告LTをやった。

docs.google.com

PyCon JP 2018

2017年に引き続きスタッフとして参加。今年はコンテンツチームとして活動した。

カンファレンスの肝になるコンテンツを自分たちで企画する面白さを実感しつつも、「特に重要なトークの採択を1チームだけでやっていいのかな」という思いもあった。スタッフ全体で決めるのがいいのかな。

nao-y.hatenablog.com

趣味

開発

詳しくはPythonタグをば。

Python カテゴリーの記事一覧 - 蛇ノ目の記

OpenStreetMapのデータを使った位置情報の可視化やQRコードに緊急時の情報を埋め込むWebアプリを作ったりしていた。あとはDockerを使ったPythonスクリプトの実行など。広く浅くといった感じ。

写真

Singapore 2018

Shinjuku Night Scene 2018

実は仕事でも写真を撮っていて、PyQオフィシャルブログで連載している「Pythonエンジニア列伝」のnobolisさん回の写真を担当した。

blog.pyq.jp

ポーカー

テキサスホールデムをやっている。

今年はポーカー仲間ができ、主に高円寺のバーで仲間たちとやっている。

上手い人が多くてよくボコられているけど、その割に自分がなかなか成長してない……。

最近、火曜日のまちこ会行けてなくてすいません(´・ω・`)

音楽

音楽自体はずっといろいろ聴いていたけど、音楽ネタのブログを書き始めた。

C95で買った同人音楽の紹介も近いうちに書きたいな。

今年始めたこと: アイドルを聴き始めた - 蛇ノ目の記

MOP of HEAD/空きっ腹に酒/DALLJUB STEP CLUBのライブでぶち上がった - 蛇ノ目の記


Pythonエンジニアとして過ごす初めての1年が経ち、仕事でPythonを書くことにも慣れてきたけど、「〜完全に理解した」とか言う間もなく「〜ぜんぜんわからない」になるくらいには苦戦しながらやっている。主にDjangoとnginx。Web系なのにWebに弱い(´・ω・`) 経験を積んでなんとかしていきたい。愚者なので経験で学ぶ。

なんか去年より振り返り方が雑な気もするけど、ここらで2018年の振り返りを終わりにする。

2018年はありがとうございました。

2019年もよろしくお願いします。

MOP of HEAD/空きっ腹に酒/DALLJUB STEP CLUBのライブでぶち上がった

12/8にDALLJUB STEP CLUBの新アルバムプレリリースライブに行ってきた。

"エモい"と"ぶち上がる"くらいしか音楽の感想を示す語彙を持ってないので、ライブのまとめ 。

MOP of HEADとDALLJUB STEP CLUBは撮影可なので自分でとった映像。

なお、やたら大きく聴こえる歓声はぶち上がってる自分のもの。

MOP of HEAD

2018/12/09 MOP of HEAD "S.A"

2018/12/08 MOP of HEAD "B.O.B"

ドラムの山下さんが中学の先輩だったりする。というのは少し前にライブハウスで話す機会があって知ったこと。世間って狭い。

空きっ腹に酒

www.youtube.com

名前は少し目にしたことがあったけど、聴くのはこのライブが初めてだった。

他の2バンドとは異なり、ラップを基盤としたロックという雰囲気。

DALLJUB STEP CLUB

2018.12.08 DALLJUB STEP CLUB "犬っぽい(Inuppoi)"

WOZNIAKでも活躍する星優太さんがいるバンド。MOP of HEADと同じくクラブミュージック寄りでMOPよりさらにそっちらしい雰囲気。

めっちゃかっこよかった。少し前にsora tob sanakaとツーマンライブやってたけどそれも行ってればよかった…。

DALLJUBの新アルバムのダイジェストはこちら。

www.youtube.com

あとWOZNIAKでお気に入りのHeptagon。

www.youtube.com