※あくまで試験的に公開しています

まぁありきたりというか、なんというかなんですけどニコニコ動画の自分用ライブラリを以前から作っていました。公開してもよかったんですけど、需要があるかわかんないし、まとまってないですし、ニコニコ動画的にもこういうライブラリをつかってごにゃごにゃするのはちょっと・・・という感じらしいので公開してませんでした。

なんですけど、公開されてるライブラリもありますし、せっかくなんで試験的に公開しておけばなんかの役に立つかな、と思いまして。使ってくれる人が多かったりすれば、まとめなおしてCodeReposに突っ込むかもしれません。

ダウンロードしてみる奇特な方はどうぞ

nicovideo.zip

必要なもの

1easy_install mechanize
2easy_install lxml

使い方

 1import nicovideo as n
 2client = n.NicoClient("mail", "pass", cookie_file="")
 3
 4print "nickname: %s" % client.nickname
 5
 6client.video("sm9").download(with_comments=True)
 7
 8print "search"
 9for video in client.video_search("test", by=n.SEARCH_KEYWORD, sort=n.SORT_POSTED_AT, order=n.ORDER_DESC)[:10]:
10  print video.title
11
12print "ranking"
13for video in client.video_ranking(by=n.RANKING_MYLIST, span=n.RANKING_DAILY, category="all")[:10]:
14  print video.title
15
16print "new arrival"
17for video in client.video_newarrival()[100:110]:
18  print video.title
19
20print "mylist"
21for video in client.video_mylist(2792158)[100:110]:
22  print video.title
23
24print "random"
25for video in client.video_random():
26  print video.title

こんな感じです。ニコニコ動画に負荷をかけないように、各httpリクエストの間に最低1秒待つので遅いです。ですが、できるかぎり情報はlazyにとりに行くようになっています。たとえば、 video_search でかえってくる Video はidとタイトルの情報のみもっていて、その他の情報(lengthなど)にアクセスすると自動的にメタ情報を取りにいきます。メタ情報は

 1print client.video("sm9").meta
 2# =>
 3description レッツゴー!陰陽師(フルコーラスバージョン)
 4tags {'#text': '', u'tag': [u'\u9670\u967d\u5e2b', u'\u30ec\u30c3\u30c4\u30b4\u30fc\uff01\u9670\u967d\u5e2b', u'\u516c\u5f0f', u'\u904b\u55b6\u516c\u8a8d\u52d5\u753b', u'\u3053\u306e\u52d5\u753b\u306e\u3046\uff50\u4e3b\u306fnicovideo', u'\u97f3\u697d', u'\u4f1d\u8aac\u306esm9', u'\u03b2\u6642\u4ee3\u306e\u82f1\u96c4', u'\u5439\u3044\u305f\u3089\u6210\u4ecf', u'\u30de\u30a4\u30ea\u30b9\uff15\uff10\u4e07\u796d\u308a\u4f1a\u5834']}
 5file_type flv
 6comment_num 2618200
 7l 320
 8is_premium 0
 9thread_id 1173108780
10view_counter 3839233
11link http://www.smilevideo.jp/view/9/350170
12nickname name
13user_id 00000
14title 新・豪血寺一族 -煩悩解放 - レッツゴー!陰陽師
15url http://smile-clb42.nicovideo.jp/smile?v=9.0468
16posted_at 2007-03-06 00:33:00
17video_id sm9
18thumb_type video
19watch_url http://www.nicovideo.jp/watch/sm9
20first_retrieve 2007-03-06 00:33:00
21mylist_counter 49956
22length 00:05:20
23thumbnail_url http://tn-skr.smilevideo.jp/smile?i=9
24ms http://msg.nicovideo.jp/10/api/
25time 1207225894
26done true
27#text
28last_res_body 1人だけじゃね

こんな感じのがとれます。基本的にthubnail APIからとってきて適当に型変換しています。thumbnail APIでの情報に加えて、file_typeなんかが付け加えられます。file_typeもちゃんと判別するのでflvでもmp4でもswfでももちろんちゃんとダウンロードできます。

ほかにも client.video_newarrival()[100:110] とすると、自動的に何ページ目かを計算してそのページからhttpアクセスしてとりにいきます。

あと、ちょっとダウンロード中の表示がリッチな感じです。

ちょっと設計について

基本的に、NicoClientを拡張して機能を実装します。拡張方法は NicoClientExtension クラスを拡張したクラスを定義する、以上です。これでNicoClientに自動的にクラス名をunderscoreしたメソッドが生えます。

たとえば VideoNicoClientExtension です。

 1class Video(NicoClientExtension):
 2  URL_WATCH = NicoClient.URL_BASE + "watch/%s"
 3  URL_API_GET_FLV = NicoClient.URL_BASE + "api/getflv/%s"
 4  URL_API_GET_THUMB_INFO = NicoClient.URL_BASE + "api/getthumbinfo/%s"
 5
 6  FILE_TYPE_RE = re.compile(".*/smile\?([a-z]{1})\=.*")
 7
 8  def __init__(self, client, id, meta = None):
 9    self.client = client
10    self.id = id
11    self.meta = meta or {}
12
13# ...

という定義で client.video というメソッドが生えるという設計になっています。メタクラスにはこういう使い方があります。検索機能も、新着も、気まぐれも全部 NicoClientExtension として実装されています。

問題点

  • まとまってません。1ファイルに適当につめこんでいます。
  • 全然テストしてません。
  • sm9(陰陽師)が消されると動かなくなります

 

 

こんな感じです。


via

Scalaはメソッド名に日本語使えるよ。Scalaは日本語プログラミング言語だよ(言いすぎ)。

 1object JapaneseLanguage {
 2  class Standard[A](self:A) {
 3    def の[B](f:(A) => B) = f(self)
 4    def を[B](f:(A) => B) = f(self)
 5    def する() = ()
 6  }
 7
 8  class PseudoKansai[A](self:A) {
 9    def のな[B](f:(A) => B) = f(self)
10    def をな[B](f:(A) => B) = f(self)
11    def すんねん() = ()
12  }
13}
14
15
16def 平方根(v:int) = Math.sqrt(v.asInstanceOf[double])
17def 逆数(v:double) = 1/v
18def 表示(v:Any) = { println(v); v }

とりあえず標準語とエセ関西弁を用意しておきました。

標準語を使いたい方は

1import JapaneseLanguage.{Standard => Japanese}
2implicit def any2japanese[A](v:A) = new Japanese(v)
3
4100 の 平方根 の 逆数 を 表示 する
5// => 0.1

とでも、エセ関西弁が使いたい方は

1import JapaneseLanguage.{PseudoKansai => Japanese}
2implicit def any2japanese[A](v:A) = new Japanese(v)
3
4100 のな 平方根 のな 逆数 をな 表示 すんねん
5// => 0.1

とでもしてください。

いやー日本人にやさしいプログラミング言語ですね、Scalaは!最近日本の人たちに注目されてきている理由もわかります!


Python で Rake を真似るとしたら という反応を頂いたので、それにまつわるお話を。

まず、CodeReposにコミットしてあるtasktoolsは distutils及びsetuptoolsを拡張する ということを念頭に置いています。ですのである程度distutils.core.Commandの思想というか、インターフェイスを残しています。

  • タスクをクラスで定義すること
  • オプションの定義方法
  • sub_commandsfinalize_options といったメソッド

などなどは元のまんまです。


じゃあ、 distutilssetuptools を抜いて好きにRakeをPythonで真似るとしたら、というのが今回メインのお話。最初にこんな感じでコマンドを定義しますよ、というのを出してしまいます。

 1from __future__ import with_statement
 2from 今回作成したモジュール import *
 3from subprocess import call
 4
 5# ここからタスク定義
 6
 7with namespace("test"):
 8
 9  @task
10  def build(t, depends = ["test:make"]):
11    """builds this modules. """
12    call(["ls"])
13    print t.name, ": build done"
14
15  @task
16  def make(t):
17    print "make"
18
19# 実行
20run()

Rakeだとこうなる。

 1namespace "test" do
 2
 3  desc "builds this modules"
 4  task "build" => "make" do |t|
 5    sh("ls")
 6    puts "#{t.name} done."
 7  end
 8
 9  task "make" do |t|
10    puts "make"
11  end
12
13end

Python版もなかなかシンプルで見やすいと思うんですがどうでしょう。あとちょっとtips。さっきの記事だと os.system を使ってますが、 subprocess モジュールの callcheck_call のほうが便利じゃないかな、と思います。

タスクのメタ情報は引数のデフォルト値を使います。ここで @task(depends = ["test:make"]) ってしたほうがいいんじゃないの?と思う人もいるかもしれません。真っ当な意見だと思いますし、それがPythonicなやり方と思います。しかしそうするとこうなります。

 1with namespace("test"):
 2
 3  @task(depends = ["test:make"])
 4  def build(t):
 5    """builds this modules.  """
 6    call(["ls"])
 7    print t.name, ": build done"
 8
 9  @task() #<= ここがキモくないですか?
10  def make(t):
11    print "make"

Pythonではproperty以外では () をつけないとメソッドが呼び出せません。だから引数が何もない場合は空のカッコがつくわけです(まぁDescriptorでhogehogeすればできるんですがね)。これ、イケてないですよね。というわけで引数のデフォルト値を使います。

ではこのタスク定義でタスクを実行するためのコードです。

 1from __future__ import with_statement
 2import inspect,new,sys
 3from contextlib import contextmanager
 4from optparse import OptionParser
 5from itertools import *
 6
 7class _Tasks(dict):
 8  def __getitem__(self,key):
 9    if key in self: return dict.__getitem__(self, key)
10    _abort("Error: Unknown task: %s"%key)
11
12class Task(object):
13  def __init__(self, run, name, description, 
14               depends = None, options = None):
15    self._run = new.instancemethod(run, self, self.__class__)
16    self.name = _get_current_ns()+name
17    self.description = description
18    self.depends = depends or []
19    self.options = options or []
20    tasks[self.name] = self
21
22  def run(self):
23    for depend in self.depends:
24      if tasks[depend].run() is False:
25        _abort("Error: faild %s"%depend)
26    return self._run()
27
28_namespace = []
29tasks = _Tasks()
30
31@contextmanager
32def namespace(name):
33  _namespace.append(name)
34  yield 
35  _namespace.pop() 
36
37def task(f):
38  names, _, _, values = inspect.getargspec(f)
39  Task(f, f.__name__, f.__doc__ or "no description", **dict(izip(names[1:], values or [])))
40
41def print_tasks():
42  maxlength = max(imap(len, tasks.iterkeys()))
43  for name in sorted(tasks.iterkeys()):
44    print name.ljust(maxlength), "#", tasks[name].description.strip()
45
46def run():
47  usage = "usage :%prog [options] targets"
48  parser = OptionParser(usage)
49  parser.add_option("-T", "--tasks", action="store_true",
50                    dest="print_tasks",
51    help="Display the tasks (matching optional PATTERN) with descriptions, then exit.")
52  (options, args) = parser.parse_args()
53  if options.print_tasks: _abort(print_tasks())
54  if not args:            _abort(parser.get_usage())
55
56  map(lambda t:t.run(), imap(tasks.__getitem__, args))
57
58def _abort(msg):
59  if msg : print msg
60  sys.exit(1)
61
62def _get_current_ns(): return _namespace and ":".join(_namespace)+":" or ""

結構短いかな?。一応ちゃんと動きます。

tasks.py -T

1test:build # builds this modules.
2test:make  # no description

tasks.py test:build

1make
2tasks.py
3test:build : build done

うむ。


結局、Rubyの言語内DSL構築能力は

  • () が省略できる
  • ブロック(pythonではデコレータとwith_statementがありますが)

によるものが多いと思っています。ことPythonとの対比では。「シンプルさ」「書きやすさ」なんかは好みかな、と(Rakeの例だとdocstringを持っている分Pythonの方がいい感じさえしてきます)。例えば end ってなんだよ、って言う人にはPythonのほうがウケがいいかもしれませんしそうじゃないかもしれません。

そんなこんなで試しに書いてみたらいい感じだったんで、ちゃんと本格的にPython版Rakeとして開発を続けてみようかなあ。


ところで言語内DSLといえばScalaでしょう。Rubyなんてちょろいもんですよ。なんてったってScalaは(ry