Node.jsでニコニコ動画のコメントを取得する

#はじめに
Node.jsを使ってニコニコ動画のコメントを取得してみたのでメモ。Node.jsに関してはほぼ初心者です。間違っているところがあるかもしれませんので悪しからず。
#概要
動画のコメントを取得するには、まずニコニコ動画にログインした状態で以下のURLを叩きます。

```
http://flapi.nicovideo.jp/api/getflv/動画ID(sm~)もしくは、スレッドID
```

ログインした状態とは、cookieに *user_session* がセットされた状態のことです。すると以下のようなレスポンスが返ってきます(１例, 関係ないところは伏字#です)

```
thread_id=1173108780&l=###&url=######&ms=http%3A%2F%2Fnmsg.nicovideo.jp%2Fapi%2F&ms_sub=######&user_id=######&is_premium=1&nickname=######&time=######&done=true&ng_rv=######&hms=######&hmsp=######&hmst=######&hmstk=######&userkey=######
```

msの内容がメッセージサーバーのアドレスとなるので、ここに以下のようなXMLをPOSTします。

```XML
<thread thread="スレッドID" version="20061206または20090904" res_from="コメント数" />
```

*thread* には先程の *thread_id* をセットします。versionは *"20061206"* または *"20090904"* をセットしますが、どちらでもOKです。 *"20090904"* の方では分毎のコメント数なども取得できるらしい。 *res_from* には *"-100"* のように、取得するコメント数を設定します。 *"-100"* なら最新のコメントから遡って100コメント取得するということです。
すると以下のようにコメントを取得できます。

```XML
(前略)
<chat thread="1173108780" no="4420146" vpos="11045" leaf="1" date="1476966753" date_usec="345153" anonymity="1" user_id="25BP9uAa71SdBsimA14X8cuKcTU" mail="184 big cyan">遺影　　遺影　　遺影　　遺影　　遺影</chat>
<chat thread="1173108780" no="4420147" vpos="11263" leaf="1" date="1476966780" date_usec="366667" anonymity="1" user_id="25BP9uAa71SdBsimA14X8cuKcTU" mail="184 medium cyan">遺影　遺影　遺影　遺影　遺影　遺影　遺影　遺影　遺影</chat>
<chat thread="1173108780" no="4420148" vpos="11848" leaf="1" date="1476966804" date_usec="245066" anonymity="1" user_id="25BP9uAa71SdBsimA14X8cuKcTU" mail="184 big cyan">壱岐長良</chat>
<chat thread="1173108780" no="4420149" vpos="11989" leaf="1" date="1476966817" date_usec="258329" anonymity="1" user_id="25BP9uAa71SdBsimA14X8cuKcTU" mail="184 big cyan">裏に肉染み</chat>
<chat thread="1173108780" no="4420150" vpos="12152" leaf="2" date="1476966824" date_usec="58985" anonymity="1" user_id="25BP9uAa71SdBsimA14X8cuKcTU" mail="184 big cyan">凄いシワ</chat>
<chat thread="1173108780" no="4420151" vpos="12483" leaf="2" date="1476966831" date_usec="466594" anonymity="1" user_id="25BP9uAa71SdBsimA14X8cuKcTU" mail="184 big cyan">読むザマス</chat>
<chat thread="1173108780" no="4420152" vpos="12625" leaf="2" date="1476966838" date_usec="905771" anonymity="1" user_id="25BP9uAa71SdBsimA14X8cuKcTU" mail="184 big cyan">その原稿を</chat>
<chat thread="1173108780" no="4420153" vpos="12805" leaf="2" date="1476966850" date_usec="91500" anonymity="1" user_id="25BP9uAa71SdBsimA14X8cuKcTU" mail="184 big cyan">灘メルモ（21）</chat>
(後略)
```

`<chat></chat>`の中身がコメントとなります。`<chat>`のキーには以下のような物が含まれています。
<dl>
  <dt>no</dt><dd>- コメント番号</dd>
  <dt>vpos</dt><dd>- コメントの書き込み再生位置(1/100秒)</dd>
  <dt>date</dt><dd>- コメントの投稿日時(UNIX秒)</dd>
  <dt>user_id</dt><dd>- コメントしたユーザーのID</dd>
</dl>

#妥協点
本当はcookieにセットする *user_session* をNode.jsで取得したかったのですが、ログイン処理がうまくできなかったため、今回はブラウザのcookieから直接取ってきました。残念。これはまた今度ということで。
#やってみる
動画のID(sm~の~部分)を指定することでコメントを取得できるようにします。ひとまず *thread_id* と *ms* を取得するところまで。

```javascript:getComment.js
var request = require('request');
var cheerio = require('cheerio');
var moment = require('moment');

var API_URL = "http://flapi.nicovideo.jp/api/getflv/sm";
var RES_FROM = "-1000";
var VERSION = "20090904";

//結果を保存する配列
var comment_list = [];

// 引数チェック
if (process.argv.length < 3) {
    console.log('missing argument.');
    return;
}

// 引数の内容を受け取る
var movie_id = process.argv[2];
var url = API_URL+movie_id;

//ヘッダーを定義
var headers = {
  cookie: 'user_session=##############################################'
  }
//GETのオプション
var option = {
  url: url, 
  method: 'GET',
  headers: headers
}

request(option, function(err, res, body){
//レスポンスからthread_idとmsを抽出
  var thread_id = body.match(/thread_id=(.*?)(&|$)/)[1];
  var ms = decodeURIComponent(body.match(/ms=(.*?)(&|$)/)[1]);
//コメントを取得する処理
  getComment(thread_id, ms);
});

(続く)
```

前述のように

```
'user_session=###############################################'
```

の伏字(#)部分は、ブラウザのcookieから取ってきました。取得コメント数(RES_FROM)とバージョン(VERSION)は固定しました。

```
  var thread_id = body.match(/thread_id=(.*?)(&|$)/)[1];
  var ms = decodeURIComponent(body.match(/ms=(.*?)(&|$)/)[1]);
```
正規表現を使ってレスポンスから *thread_id* と *ms* の中身を抽出しています。正規表現については今回は割愛。msの中身はURIエンコードされているので、ここでデコードしておきます。

さて、これでthread_idとmsを取得し、メッセージサーバーに問い合わせる準備ができました。*getComment(thread_id, ms)* で、コメントを取得する処理を行います。

```javascript:getComment.js
(続き)
function getComment(thread_id, ms){
//XMLにthread_idをセットし、msにPOST
  //POSTのヘッダー
  var headers = {
  'Content-Type':'application/xml'
  }
  //POSTするXML
  var body = '<thread thread="'+thread_id+'" version="'+VERSION+'" res_from="'+RES_FROM+'" />'
  //POSTのオプション
  var options = {
    url: ms,
    method: 'POST',
    headers: headers,
    body: body
  }

  request(options, function (err, res, body) {
    $ = cheerio.load(body);

    $("chat").each(function(i, e){
    //最初に宣言したcomment_listに結果を整理して保存
      comment_list[i] = {
        date: moment.unix($(e).attr("date")).format("YYYY-MM-DD"),
        no: $(e).attr("no"),
        vpos: $(e).attr("vpos")/100,
        user_id: $(e).attr("user_id"),
        comment: $(e).text()
      }
    });
    console.log(comment_list);
  });
}
```

レスポンス中のdateの中身はUNIX秒なので、momentモジュールを用いてわかりやすい形に直しています。vposは100で割って秒に直しました。
試しにsm9(陰陽師)のコメントを取得してみましょう。
```コマンドライン
node getComment.js 9
```

```javascript:結果
(前略)
  { date: '2016-10-23',
    no: '4421143',
    vpos: 28.85,
    user_id: 'l5kEv8RrorGegDl8yyknrCN7llU',
    comment: '悪霊退散卍悪霊退散卍悪霊退散卍悪霊退散卍悪霊退散卍悪霊退散卍悪霊退
散卍悪霊退散卍悪霊退散卍悪霊退散卍悪霊退散卍悪霊退散卍悪霊退散卍悪霊退散' },
  { date: '2016-10-23',
    no: '4421144',
    vpos: 92.51,
    user_id: 'l0em-hHQ-mouJxpLtEMFZ3On6ds',
    comment: '悪霊退散悪霊退散悪霊退散悪霊退散悪霊退散悪霊退散悪霊退散悪霊退散悪
霊退散悪霊退散悪霊退散悪霊退散悪霊退散悪霊退散悪霊退散' },
  { date: '2016-10-23',
    no: '4421145',
    vpos: 99.25,
    user_id: 'l0em-hHQ-mouJxpLtEMFZ3On6ds',
    comment: 'どーまん！せーまん！どーまん！せーまん！' },
  { date: '2016-10-23',
    no: '4421146',
    vpos: 262.25,
    user_id: 'l0em-hHQ-mouJxpLtEMFZ3On6ds',
    comment: '払ってもらおう 陰陽寺' },
(後略)
```

これで *comment_list* にコメント関係の情報を取得できました。 ~~やったねたえちゃん！~~

#よくわからなかったところメモ

- Node.jsでのニコニコ動画へのログイン
- *RES_FROM* の値に関係なくコメントが1000件分しか取得できない
- user_sessionの制限時間

これらについては今後の調べていくということで。

#参考
書籍
[JS+Node.jsによるWebクローラー/ネットエージェント開発テクニック](https://www.amazon.co.jp/JS-Node-js%E3%81%AB%E3%82%88%E3%82%8BWeb%E3%82%AF%E3%83%AD%E3%83%BC%E3%83%A9%E3%83%BC-%E3%83%8D%E3%83%83%E3%83%88%E3%82%A8%E3%83%BC%E3%82%B8%E3%82%A7%E3%83%B3%E3%83%88%E9%96%8B%E7%99%BA%E3%83%86%E3%82%AF%E3%83%8B%E3%83%83%E3%82%AF-%E3%82%AF%E3%82%B8%E3%83%A9%E9%A3%9B%E8%A1%8C%E6%9C%BA/dp/4883379930)
Webサイト
[ニコニコ動画のコメント解析](https://blog.nanoway.net/web/nicovideo-comment-api)
[ニコニコ動画コメント等データ仕様](http://www.slideshare.net/masahiroh1/ss-24757915)