Hatena::ブログ(Diary)

プロジェクトXBMC nico

XBMC nicoスクリプトのダウロードは こちら から

2009-12-16

ニコニコ動画の公式動画に投稿されたコメントの取得方法

| 02:16

前回、公式動画と呼ばれる動画のコメントを取得できなかったので調べてみました。


ニコニコ大百科 動画IDの概要より抜粋

動画IDの概要

動画毎に個別に与えられる番号。

要は動画URLhttp://www.nicovideo.jp/watch/aa9999の部分である。

動画IDは、投稿された動画サイト、または動画を提供するコンテンツホルダーを表すアルファベット二文字の文字列と動画そのものに固有に振られたユニークな連番で構成されている。

以下、動画の種類の一覧を掲載する。

ユーザーが投稿できる動画のID

* smSMILEVIDEOの動画。ほとんどの動画に付けられる。

* nm ‐ ニコニコ動画公式動画編集ソフトで投稿した動画。

* am ‐ AmebaVisionから転載された動画。2007年10月に廃止され、動画が削除された。

* fz ‐ フォト蔵から転載された動画。2008年5月に廃止されたが、こちらは現在も視聴ができる。

* ut ‐ Youtubeから転載された動画。現在閲覧することはできず、自動リンクされない。

公式動画

* ax ‐ avex公式

* ca ‐ 超!アニメロ,にょコにょコ動画

* cd ‐ (不明)

* cw ‐ キャラウッド動画

* fx - MTV公式

* ig ‐ アイ★グラ動画

* na ‐ Livedoorネットアニメ

* nl ‐ ニコニコ生放送

* om ‐ 音女

* sd ‐ (不明)

* sk ‐ spikeニコニコチャンネル

* yk ‐ YuriChannel動画

* yo ‐ よしよし動画

* za ‐ ニコニコアニメチャンネル

* zb ‐ ニコラジオ・TV

* zc ‐ ニコアニニュース

* zd ‐ ai sp@ce

* ze ‐ 虹視聴覚室

* so ‐ 公式動画全般。上記の公式動画専用識別子を廃止し、すべての公式動画の識別子をこれに統一した。


どうやら公式動画の頭には今後 so という頭文字が付くようです。

とりあえず、先人の知恵を参考にするのが一番簡単だと思い早速検索・・・、

したのですが思っていたような情報が見つからず、あえなく失敗... orz

たぶん、探し方が悪かったのだと思います ( ̄∇ ̄;)ハッハッハ


結局、探すのがメンドくさくなったので、自分で通信内容をトレースしてみることにしました 。


メッセージサーバへのリクエストでPOSTされたXMLデータの比較


・投稿動画の場合

<thread thread="スレッドID" version="20061206" res_from="-コメント数" user_id="ユーザID"/>

・公式動画の場合

<thread thread="スレッドID" version="20061206" res_from="-コメント数" user_id="ユーザID" threadkey="スレッドキー" force_184="1"/>

結果、公式動画とユーザーが投稿した動画では、メッセージサーバへPOSTする情報に違いがあるようです。

見て分かる通り、公式動画に投稿されたコメントの取得時には 投稿動画のコメント取得時にはない、threadkey と force_184 というパラメータが存在します。

つまり、この threadkey と force_184という情報を取得しなければならないということです。



コメント取得部分の通信データ

スレッドキーの取得

GET /api/getthreadkey?thread=1230277688 HTTP/1.1

Host: flapi.nicovideo.jp

レスポンス: 
threadkey=1260895818.CVzCNuF6F-6HLMku45inDrJ7020&force_184=1

↓・メッセージサーバへのリクエスト1

POST /23/api/ HTTP/1.1

Host: msg.nicovideo.jp

<thread thread="1230277686" version="20061206" res_from="-100" user_id="ユーザID"/>

レスポンス: 
<?xml version="1.0" encoding="UTF-8"?><packet><thread resultcode="0" revision="1" server_time="1260894018" thread="1230277686" ticket="0xf26e8f0"/><view_counter id="so5664590" mylist="22" video="4528"/></packet>

↓・メッセージサーバへのリクエスト2

POST /23/api/ HTTP/1.1

Host: msg.nicovideo.jp

<thread thread="1230277688" version="20061206" res_from="-100" user_id="ユーザID" threadkey="1260895818.CVzCNuF6F-6HLMku45inDrJ7020" force_184="1"/>

レスポンス: 
<?xml version="1.0" encoding="UTF-8"?><packet><thread last_res="39" resultcode="0" revision="3" server_time="1260894018" thread="1230277688" ticket="0x25d71a90"/><view_counter id="so5664590" mylist="22" video="4528"/><〜以下略〜></chat></packet>

メッセージサーバにリクエストする前に、http://www.nicovideo.jp/api/getthreadkey?thread=動画ID へのリクエストで threadkey と force_184 の値を取得していることが分かります。

/api/getthreadkey?thread=動画IDから返ってきたデータは以下のような形式

threadkey=スレッドキー&force_184=1

流れとしては、スレッドキーを取得してから、投稿動画コメントの取得と同じようにリクエストを出し、

さらに、公式動画のコメントを取得しています。つまりメッセージサーバに2回リクエストをおこなっています。

ですが、一度目のリクエストでも公式動画のコメントを取得できました。


それらを踏まえたサンプルコードを載せておきます

実際のプログラムで使う場合は、セッション情報などを保管しておかないとアクセス過多でアク禁をくらいます(テスト中になりましたw)

Pythonでのサンプル

メールアドレスパスワードは自分のものを設定して下さい。

#!/usr/bin/python
# -*- coding: utf-8 -*-

import httplib,urllib
import re
import cgi

mailAddress = "メールアドレス"
passWord = "パスワード"

videoid = "so6763675"# 天体戦士サンレッド FIGHT. 01

headers = {'User-Agent':'Mozilla/5.0 (Windows; U; Windows NT 6.1; ja; rv:1.9.1.5) Gecko/20091102 Firefox/3.5.5 (.NET CLR 3.5.30729)',
    'Accept': '*/*',
    'Accept-Language': 'ja,en-us;q=0.7,en;q=0.3',
    'Accept-Charset': 'UTF-8,*',
    'Connection': 'keep-alive',
    'Keep-Alive': '300',
    'Cookie': '; '}

# 動画ページ
if videoid[0:2] == "so":
    conn = httplib.HTTPConnection('www.nicovideo.jp', 80)
    conn.request('GET', '/watch/%s' % videoid, '', headers)
    rs = conn.getresponse()
    body = rs.read()
    print body
    rs.close()
    conn.close()
    if rs.status == 302:
        mc = re.compile('/watch/(.*)').search(rs.getheader('Location'))
        videoid = mc.group(1)


# ログイン処理
post_dict = {'next_url': '',
    'submit': '',
    'mail': mailAddress,
    'password': passWord
    }
headers['Referer'] = 'http://www.nicovideo.jp/'
headers['Content-type'] = 'application/x-www-form-urlencoded';
conn = httplib.HTTPSConnection('secure.nicovideo.jp')
conn.request('POST', '/secure/login?site=niconico', urllib.urlencode(post_dict), headers)
rs = conn.getresponse()
mc = re.compile('(user_session=(?!deleted)[^;]*);?').search(rs.getheader('Set-Cookie'))
user_session = mc.group(1)
headers['Cookie'] = user_session
rs.read()
rs.close()
conn.close()


# 動画ページ
conn = httplib.HTTPConnection('www.nicovideo.jp', 80)
conn.request('GET', '/watch/%s' % videoid, '', headers)
rs = conn.getresponse()
body = rs.read()
rs.close()
conn.close()
mc = re.compile(r'nicoplayer\.swf\?ts=(\d+)').search(body)
video_ts = int(mc.group(1))


# 動画情報の取得
conn = httplib.HTTPConnection('www.nicovideo.jp', 80)
conn.request('GET', '/api/getflv/%s' % videoid, '', headers)
rs = conn.getresponse()
body = rs.read()
rs.close()
conn.close()
qs = cgi.parse_qs(body)
thread_id = qs["thread_id"][0]
user_id =  qs["user_id"][0]
mc = re.compile(r'&ms=http%3A%2F%2F(.+?)\.nicovideo\.jp(%2F.+?)&').search(body)
message_server = urllib.unquote_plus(mc.group(1))
message_path = urllib.unquote_plus(mc.group(2))


# コメントの取得
headers['Content-type'] = 'text/xml'
if videoid.isdigit():
    # スレッドキーの取得
    conn = httplib.HTTPConnection('www.nicovideo.jp', 80)
    conn.request('GET', "/api/getthreadkey?thread=%s" % videoid, '', headers)
    rs = conn.getresponse()
    body = rs.read()
    rs.close()
    conn.close()
    mc = re.compile(r"threadkey=(.*)&force_184=(.*)").search(body)
    threadkey = mc .group(1)
    force_184 = mc .group(2)
    postXml = '<thread thread="%s" version="%s" res_from="%s" user_id="%s" threadkey="%s" force_184="%s"/>' % ( thread_id, 20061206, - 1000, user_id, threadkey, force_184)
else:
    postXml = '<thread thread="%s" version="%s" res_from="%s" user_id="%s"/>' % (thread_id, 20061206, - 1000, user_id)
conn = httplib.HTTPConnection('%s.nicovideo.jp' % message_server, 80)
conn.request('POST', message_path, postXml, headers)
rs = conn.getresponse()
body = rs.read()
rs.close()
conn.close()

# コメントを出力
print body

こんな感じです。っというわけで恒例の・・・


ドーン!ヽ(^∇^*)/

f:id:s01149ht:20091217033718j:image


サンレッド キターっ (|||ノ`□´)ノオオオォォォー!!

そして、コメントが地名ばっかりだー (|||ノ`□´)ノオオオォォォー!!


っと、まあ、これでやっとコメントも見ることができました ε-(´∀`*)ホッ

それじゃ次って、あれ?

2話からは有料!?(`□´;)なぬっ!

なんだこのオチはー!!ヽ(`Д´)ノウワァァァン


一部修正したソースはこちらです

トラックバック - http://d.hatena.ne.jp/s01149ht/20091216/1260983794
Connection: close