コマンドライン上で動作するニコニコ動画ダウンローダー on Python

ニコニコ動画の.flvファイルをダウンロードするPythonスクリプトです.

引数としてsm??????のようなIDとhttp://www.nicovideo.jp/watch/sm?????? のようなURLの両方を受け付けます.また,複数の引数を受け取ると順次ダウンロードします.

GreaseMonkeyスクリプトなどで同様の目的を達成するものは既にいくつかありますが,サイトの仕様変更のためか,うまく動作しないことが多いので自作してみました.

標準のモジュール(ライブラリ)のみを使っているので,多くの環境で動作するはずです.

その他の特長として,プログレスバーで進捗を表示したり,ページのタイトルをチェックして適切なファイル名をつけたりするといったものもあります.

個人的にはPyhtonでのBasic認証Cookie制御の要領が分かったことと,自分で処理に時間がかかるプログラムを書いたときに,ProgressBarクラスを使って完了までの時間が予測出来るようになったことが一番の収穫だったりします.

#!/usr/bin/env python
# -*- encoding: utf-8 -*-

import cgi
import cookielib
import urllib
import urllib2
import re
import sys
import time

username = ""
password = ""

bufsize = 524288

class ProgressBar:
  format = "%s[%s%s] %d/%d ETA %s %dKB/sec\r"
  def __init__(self, _max, name="", size=24):
    self.max = _max
    self.cur = 0
    self.name = name and name+" " or name
    self.size = size
    self.num_progress = 0
    self.out = sys.stdout

  def start(self):
    self.progress(0)

  def progress(self, delta):
    if self.num_progress == 0:
      self.start = time.time()
    self.num_progress += 1

    self.cur += delta
    cursize = int(float(self.cur) / self.max * self.size)
    leftsize = self.size - cursize
    time_consume = time.time()-self.start
    _eta = self.num_progress >= 2 and \
        ( time_consume / (float(self.cur)/self.max) - time_consume) or -1
    eta = _eta >= 0 and  "%02d:%02d" % divmod(_eta, 60) or "--:--"
    kbs = self.cur / 1024 / time_consume
    self.out.write(self.format % \
      (self.name, '*'*cursize, '-'*leftsize, self.cur, self.max, eta, kbs))
    if self.cur >= self.max:
      self.out.write("\r\n")
    self.out.flush()

def login(username, password):
  cj = cookielib.CookieJar()
  opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj))
  req = urllib2.Request("https://secure.nicovideo.jp/secure/login?site=niconico");
  account = {"mail": username, "password": password}
  req.add_data(urllib.urlencode(account.items()))
  opener.open(req)
  return opener

def get_video(url_or_video_id, opener):
  video_id = re.match("^.*(sm.*)$", url_or_video_id).group(1)
  for line in opener.open("http://www.nicovideo.jp/watch/" + video_id):
    if line.find("<title>") > -1:
      title = re.match("^s*<title>.+?‐(.+?)</title>\s*$", line).group(1)
      break 
  res = opener.open("http://www.nicovideo.jp/api/getflv.php?v=" + video_id).read()
  url = cgi.parse_qs(res)["url"][0]

  source = opener.open(url)
  length = int(source.headers["content-length"])
  filename = "%s-%s.flv" % (video_id, title) 
  dest = file(filename, "wb", bufsize)
  bar = ProgressBar(length)
  bar.start()
  while dest.tell() < length:
    dest.write(source.read(bufsize))
    bar.progress(bufsize)

def main():
  if username == "" or password == "":
    print "error: please specify your account info in", sys.argv[0]
    sys.exit(1)
  if len(sys.argv) < 2:
    print "usage: %s url_or_video_id [url_or_video_ids]" % sys.argv[0]
    sys.exit(1)

  args = sys.argv[1:]
  for i, url_or_video_id in enumerate(args):
    sys.stdout.write("%s/%s " % (i+1, len(args)))
    session = login(username, password)
    get_video(url_or_video_id, session)

if __name__ == '__main__':
  main()


PythonBasic認証Cookie制御をサポートするアプローチは,PerlともRubyとも違っていて面白いなぁと思いました.どうやらデザインパターンでいうChain of Responcibilityのようですね.

  cj = cookielib.CookieJar()
  opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj))
  f = opener.open("http://example.com/") # Cookieを受信した場合適切に処理する

2008/01/10 追記

ニコニコから動画を落とすスクリプト - ひきメモ

  • 同じくPythonで書かれたニコニコ動画FLVファイルダウンロードスクリプト.ちゃんとoptparseを使ってコマンドラインオプションを処理していたり,webブラウザcookieを流用出来るアプローチがとられていたりと本エントリのものと比べて使い勝手が良く,またそれらの実装方法のサンプルとしても参考になります.