func.4 objects

進捗しない日記 プログラミングとか

SoundCloud APIと戦いLikesをすべて取得する

普段音楽をSoundCloudで聞いているわけなんですが、これのLikesに登録した曲たちのURLが全部わかるとうれしいなということなのでそれをやります。

やりたいこと

大きくは2つあり、

  • 自分のLikesの数の取得
  • すべてのLikesのURLを得る

です。

ここからは割と苦労話が入るので手法だけ知りてーよという場合は下まで飛ばしてまとめをご確認ください。

Likes数の取得

SoundCloudAPIを公開しています。
developers.soundcloud.com

で、これを見るにLikeを取りたいだけならAuthする必要はないので簡単じゃ~んとか思っていると一番最初に困るのはClientIDだと思われます。
こいつはSoundCloudのAppを新しく作るとそいつに対して割り当てられるのですが、なんとSoundCloudは今現在新しいAppの登録を凍結しています。

なので頑張って各自どうにかしましょう*1


ClientIDはまぁ得たものとして話を進めると、次に困るのはUserIDでしょう。

これはSoundCloudに登録している各ユーザーに対して一意なIDなわけですが、パッと得ることができません。
おそらく最も行儀の良い方法は、

  1. SoundCloudにログイン
  2. Settingsに移動
  3. Contentタブに移動
  4. RSS FeedのURLの中のsoundcloud:users: 以下の数字を確認

だと思います。
私は行儀が悪いのでログインした後トップページのコード内のJSを見てapi.soundcloud.com/users/以下を確認して得ました。まぁ数字が分かればなんでも良いです。


で、これが分かると
https://api.soundcloud.com/users/{UserID}?client_id=YOUR_CLIENT_ID
にアクセスするだけでとりあえずユーザー情報がJSONで得られます。
この中にpublic_favorite_countがあるのでそれにアクセスすればLikesが得られます!

……という幸せな終わりを目指していたのですが、これによって得られるLikesの数はどうもサイト上のLikesの数と一致しません。勝手な推測ですが、おそらくLikeしたあとでその曲が消えた場合、ブラウザ上では反映される一方このAPIには反映されていないのではと思っています。

ここで、そもそもサイト上ではこのAPIを使っているはずでそれとこれが一致しないのは何故なのか?という疑問が生まれます。

で、確認するためSoundCloudトラフィックを見たところ、なんと今SoundCloudはこのAPIを使っていないらしく、アクセスしているのは
api-v2.soundcloud.com
でした。
そしてこのAPI-V2はUndocumentedです。つらい。

とりあえず調べた結果、
https://api-v2.soundcloud.com/users/{UserID}?client_id=YOUR_CLIENT_ID
JSONが得られ、その中にlikes_countがあるのでそれを得ればOKです。これはサイト上の情報と一致します。

全てのLikeした曲のURLを取得

とりあえずAPI-V2の存在は一旦忘れ、Documentedな方法でURLを得ようと考えました。
ドキュメントいわく、Likes一覧は
https://api.soundcloud.com/users/{UserID}/favorites?client_id=YOUR_CLIENT_ID
にGETを投げつければJSONで得られます。

が、こいつはそれほど多くの曲リストを同時に返してくれません。
私はLikesが500を超えていて、それを一気には返してくれないということがわかりました(負荷を考えればそれはそう)

ちょっとググったところ、?limit=と?offset=というパラメータが有効っぽい記事を見つけ、なるほどねということで
https://api.soundcloud.com/users/{UserID}/favorites?limit=200&offset=300&client_id=YOUR_CLIENT_ID
みたいにアクセスするとこれは死にました。

これについてもう少し調べたところ、どうやら数年前にoffsetパラメータは殺されてしまったらしく、offset=200までしか動かないようです。
そして現在はlinked_partitioning=1を追加すると帰ってくるJSONの中にnext_hrefという要素があるらしく、これにURLが入っているのでそれを使ってページングするらしいです。なるほどね。

なので、結局アクセスするのは
https://api.soundcloud.com/users/{UserID}/favorites?limit=200&linked_partitioning=1&client_id=YOUR_CLIENT_ID
で、next_hrefがある限り繰り返しアクセスすることで全部得る という感じになりました。

そしてここでまた変なことが起き、これで得たURLの数がapi-v1で得たfavoriteの数ともapi-v2で得た数とも一致しませんでした。俺は一体何を信じれば良いんだ。

概ねやりたいことができたのでここで力尽きました。これに関して有用な情報(あるいはapi-v2で試した結果)がある場合ぜひ教えていただきたいです。

まとめ

Likesの取得

そのままコピペしても動かないので注意(UserIDとかClientIDとか)

http = urllib3.PoolManager(
    cert_reqs='CERT_REQUIRED',
    ca_certs=certifi.where())

profile_url = "https://api-v2.soundcloud.com/users/{UserID}?client_id={ClientID}"
source = http.request('GET', profile_url)
raw = source.data.decode('utf-8')
pyson = json.loads(raw)
print(pyson["likes_count"])

全てのLikesのURLの取得

http = urllib3.PoolManager(
    cert_reqs='CERT_REQUIRED',
    ca_certs=certifi.where())

url = "https://api.soundcloud.com/users/"+ str(userID) + "/favorites?limit=" + str(limit) + "&linked_partitioning=1&client_id=str(CID)"
remoteURLList = []
while repeat:
    source = http.request('GET', url)
    raw = source.data.decode('utf-8')
    pyson = json.loads(raw)
    for urlData in pyson["collection"]:
        remoteURLList.append(urlData['permalink_url'])
    if "next_href" in pyson:
        url = pyson["next_href"]
    else:
        break
print(remoteURLList)

*1:これは全く関係ない話なのですが、Youtube-dlやscdlなどのSoundCloud対応のダウンロードサービスのいくつかはgithubで公開されています。そしてこれらは内部でSoundCloudAPIにアクセスする必要があります