いものやま。

雑多な知識の寄せ集め

ニコニコ動画のダウンロードツールをRubyで書いてみた。(その2)

Nicovideo Downloaderのコードは読んだので、次は原因探し。

printfデバッグ(笑)

さて、ちゃんとPythonに精通した人とかならデバッガを使って調査をしたりするんだろうけど、残念ながら自分はPythonについてそれほど詳しくない。
なので、あまり褒められた方法ではないけど、ベタにprintfデバッグしてみた。

調べてみると、動画の実際のURLの取得までは出来てるっぽい。
けど、そのアドレスを見てみると、スキームがrtmpe・・・?
えっ、rtmpeって何?

RTMP

Wikipediaを見てみると、RTMP(Real Time Messaging Protocol)というプロトコルがあるらしく、rtmpeというのはそのヴァリアントの1つで、Adobeの独自のセキュリティ機構で暗号化されたRTMPらしい。
つまり、プロトコルがHTTPでないので、通信が上手くいかずにデータがダウンロード出来ていなかったっぽい。

しかし、そうなると困った。
一応、python-librtmpというライブラリがある感じだったけど、それを使うようにするなら、それなりにコードを修正する必要が出てくる。
けど、正直なところ、Pythonはあまり書きたくない・・・*1

なら、いっそRubyにポーティングしてしまって、自分でコードをいじりやすくした上で、RTMPで配信されている動画もダウンロード出来るようにしようかな、と。

Rubyへのポーティング

元々のNicovideo Downloaderにはいくつかのコマンドラインオプションが用意されていたけど、今回は自分用のツールを用意するだけなので、機能は限定的に。

  • ログインには.netrcの情報を使う
  • 動画のファイル名には動画のIDを使う
  • コメントはダウンロードしない

.netrcからユーザ情報の取得

まずはユーザ情報の取得から。

調べてみると、netrcというgemで、簡単に出来るみたい。

heroku/netrc · GitHub

gemをサクッとインストール。

$ gem install netrc

そして、コードはこんな感じ。

require 'netrc'

netrc_info = Netrc.read
user, password = netrc_info["nicovideo"]

ちなみに、情報が正しく読めなかった場合、空の文字列が返されるっぽい。

ログイン処理

ユーザ情報が取得できたら、次はログイン処理。

とはいえ、さすがにログインページにアクセスするだけでログイン出来るようになるはずがないので調べたら、やっぱりCookieを使ってるのね。

Rubyとnet/httpsライブラリを使用してニコニコ動画にログインする - Qiita

このページを参考に、処理を書いていく。

まず、RubyでHTTP/HTTPSを使う場合、標準添付ライブラリのnet/http、およびnet/httpsを使う。
なので、これらのライブラリをロード。

require 'net/http'
require 'net/https'

そして、Net::HTTPのインスタンスを作成。
このインスタンスに対して、GETメソッドやPOSTメソッドを送っていくことになる。
なお、ログインの場合、HTTPSを使うので、実際にGETやPOSTを送る前にちょっと設定が必要。

LoginURI = URI.parse("https://secure.nicovideo.jp/secure/login?site=niconico")

https = Net::HTTP.new(LoginURI.host, LoginURI.port)

https.use_ssl = true
https.verify_mode = OpenSSL::SSL::VERIFY_NONE

そのあと、POSTするデータを作成してPOST。
レスポンスとしてNet::HTTPResponseのインスタンスが返ってくるので、これを受け取る。

LoginPostFormat = "current_form=login&mail=%s&password=%s&login_submit=Log+In"

postdata = sprintf(LoginPostFormat, user, password)
response = https.post(LoginURI.request_uri, postdata)

Net::HTTPResponseはNet::HTTPHeaderを継承しているので、レスポンスのヘッダの情報を読み出すにはNet::HTTPHeader#get_fieldsメソッドを使えばいい。
セットするべきCookieの情報は'set-cookie'というフィールドの値として入っているので、それを処理していく。

なお、'set-cookie'というフィールドは複数ある場合があるので、それぞれの値に対して処理を行う必要があることに注意。

'set-cookie'フィールドに対する値のフォーマットは、

KEY=VALUE; expires=DATE; path=PATH; domain=DOMAIN_NAME;

という感じみたい。(※最初のKEY=VALUEだけ必須)

今回必要なのはKEY=VALUEという部分だけなので、そこを取り出す。
そして、ログインで必要な情報は、NAMEが'user_session'で、かつ、VALUEが'deleted'でないものなようなので、それを探し出してあげる。

コードにすると、以下のような感じ。

user_session = nil
response.get_fields('set-cookie').each do |cookie|
  key, value = cookie.split(';').first.split('=')
  if (key == 'user_session') && (value != 'deleted')
    user_session = value
    break
  end
end

あとは、GETやPOSTを行うときに、HTTPヘッダとして、取得したCookieを送ってあげればいい。

なお、Cookieを送るときは、HTTPヘッダの'Cookie'フィールドで、次のフォーマットにしたデータを送るみたい。
('set-cookie'フィールドと違い、こちらは1つのフィールドで複数Cookieを送るのに注意!)

KEY_1=VALUE_1; KEY_2=VALUE_2; ... KEY_N=VALUE_N;

例えば、次のような感じ。

cookie_str = "user_session=#{user_session};"
response = http.get("/path/to/entity", {'Cookie' => session_cookie})

あとは、動画情報を取得して、データのダウンロード〜ファイルへの出力を行えばいいはず・・・

今日はここまで!

*1:2系と3系のゴタゴタの印象が悪すぎて使う気になれなかったので、習熟度が全然足りない。