LoginSignup
1
0

More than 1 year has passed since last update.

ニコ生のコメントをnode.jsで取得するときにハマったこと

Last updated at Posted at 2023-03-08

はじめに

最近、ニコ生のコメント取得をするプログラムをnode.jsで書いてみました。
私はこのコードを使って、ニコ生のギフトをもらった時に配信上に動画が流れるようにしています。

ソースコードの解説(ソースコード全体はページ下部)

詳しい解説について

今回書いたコードは

を参考にしたので、ほとんどのコードは上記のページと同じであり、上記のページに詳しい解説が書かれているので、今回はハマったところだけ書いていきます。

websocketのURLの取得

参考元のサイトでは、 chromeのデベロッパーツールでコードを実行するという都合上以下のようにgetElementByIdで websocketのURLが取得できる。

参考サイトのプログラム.js
// アクセスした生放送のページのhtmlから情報を取ってくる
const embeddedData = JSON.parse(document.getElementById("embedded-data")-getAttribute("data-props"));
const url_system = embeddedData.site.relive.webSocketUrl;
const user_id = embeddedData.user.id

しかし、node.js環境下ではそうもいかないので、 cheerioというモジュールを利用しました。
まず取得したい生放送のhtmlコードを取得し、そこからcheerioで目的のURL情報を抽出している。

 node.jsで取得できるように修正したプログラム.js
const live_id = "co5043209"; //任意のurlを入れる
// live_id にコミュニティIDを指定すると放送中のものをとってきてくれる
const url = "https://live2.nicovideo.jp/watch/" + live_id;

// 生放送ページのhtmlを取得
const syncRequest = (url) =>
  new Promise((rsv) => request(url, (e, r, b) => rsv(b)));


// websocketのurlを生放送ページのhtmlから取得する
const res = await syncRequest(url);
const $ = cheerio.load(res);
const embeddedData = JSON.parse($("#embedded-data").attr("data-props"));
const url_system = embeddedData.site.relive.webSocketUrl; // websocketのurl
console.log(url_system);

このコードを実行すると 「wss: //」 から始まるURLが取得できます。

ヘッダー情報の追加

本来は、websocketのurlが取得できれば、 node.js環境下でも、引用元のプログラムをそのままコピペすれば動くはずなので すが、視聴セッションと接続するときにエラーが帰ってきて、正常に動きませんでした。
どうやら、ニコ生の仕様が変わったらしく、 ヘッダー情報にUserAgent が含まれていないと、接続エラーになるようです。 https://qiita.com/pasta04/items/33da06cf3c21e34fc4d1
というわけで、ヘッダー情報に適当にchromeの情報を含めておきました。

ニコ生の仕様に合わせヘッダーを追記.js
websocket_system = new WebSocket(url_system, {
      headers: {
        // 最近のニコ生ではこのヘッダー情報がないとエラーを起こす
        "User-Agent":
          "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36",
      },
    });

コメントの抽出

これだけでも、コメント取得できるのですが、出力すると
スクリーンショット 2023-03-08 005822.png

こんな感じで見にくいのでコメントだけ抽出します。

ニコ生のコメント抽出.js
//コメント部分のみを抽出
    const comment = JSON.parse(evt.data).chat?.content;
    //コメントを出力
    if (comment != undefined) {
      console.log(comment);
    }
  }

このとき、

chatの後ろに?を付けない場合.js
const comment = JSON.parse(evt.data).chat.content;

ではなく、chatの後に?を付けて

chatの後ろに?を付ける場合.js
const comment = JSON.parse(evt.data).chat?.content;

にしているのは、毎回chatパラメータが含まれているわけではなく、

{"thread":{"resultcode":0,"thread":"M.z3OkrOWReKCjkmEsODFS1g","revision":1,"server_time":1678204652,"last_res":82,"ticket":"3cdc5056"}}

このようなものも流れてくるので、chatが来た時だけ処理するようにするためです。このやり方は視聴者さんに教えてもらいました。

その他分からない点があったら

基本的に毎日ニコ生とtwitchの同時配信してるので、コメントしてください。

ソースコード

node.jsのバージョン v18.13.0

commentGet.js
const request = require("request");
const cheerio = require("cheerio");
const WebSocket = require("ws");

const live_id = "co5043209"; //任意のurlを入れる
// live_id にコミュニティIDを指定すると放送中のものをとってきてくれる
const url = "https://live2.nicovideo.jp/watch/" + live_id;

// 生放送ページのhtmlを取得
const syncRequest = (url) =>
  new Promise((rsv) => request(url, (e, r, b) => rsv(b)));

// コメント取得関数
async function commentGet() {
  // websocketのurlを生放送ページのhtmlから取得する
  const res = await syncRequest(url);
  const $ = cheerio.load(res);
  const embeddedData = JSON.parse($("#embedded-data").attr("data-props"));
  const url_system = embeddedData.site.relive.webSocketUrl; // websocketのurl
  console.log(url_system);

  // websocketでセッションに送るメッセージ
  const message_system_1 =
    '{"type":"startWatching","data":{"stream":{"quality":"abr","protocol":"hls","latency":"low","chasePlay":false},"room":{"protocol":"webSocket","commentable":true},"reconnect":false}}';
  const message_system_2 = '{"type":"getAkashic","data":{"chasePlay":False}}';
  // コメントセッションへWebSocket接続するときに必要な情報
  let uri_comment;
  let threadID;
  let mes_comment;

  /*視聴セッションのWebSocket関係の関数*/
  // 視聴セッションとのWebSocket接続関数の定義
  function connect_WebSocket_system() {
    websocket_system = new WebSocket(url_system, {
      headers: {
        // 最近のニコ生ではこのヘッダー情報がないとエラーを起こす
        "User-Agent":
          "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36",
      },
    });
    websocket_system.onopen = function (evt) {
      onOpen_system(evt);
    };
    websocket_system.onclose = function (evt) {
      onClose_system(evt);
    };
    websocket_system.onmessage = function (evt) {
      onMessage_system(evt);
    };
    websocket_system.onerror = function (evt) {
      onError_system(evt);
    };
  }

  // 視聴セッションとのWebSocket接続が開始された時に実行される
  async function onOpen_system(evt) {
    console.log("CONNECTED TO THE SYSTEM SERVER");
    await doSend_system(message_system_1);
    await doSend_system(message_system_2);
  }

  // 視聴セッションとのWebSocket接続が切断された時に実行される
  function onClose_system(evt) {
    console.log("DISCONNECTED FROM THE SYSTEM SERVER");
    // コメントセッションとのWebSocket接続を切る
  }

  // 視聴セッションとのWebSocket接続中にメッセージを受け取った時に実行される
  async function onMessage_system(evt) {
    is_room = evt.data.indexOf("room");
    is_ping = evt.data.indexOf("ping");
    //来場者数も取得したいときはis_statistic = evt.data.indexOf("statistic");

    // コメントセッションへ接続するために必要な情報が送られてきたら抽出してWebSocket接続を開始
    if (is_room > 0) {
      console.log("RESPONSE FROM THE SYSTEM SERVER: " + evt.data);
      // 必要な情報を送られてきたメッセージから抽出
      evt_data_json = JSON.parse(evt.data);
      uri_comment = evt_data_json.data.messageServer.uri;
      threadID = evt_data_json.data.threadId;
      message_comment =
        '[{"ping":{"content":"rs:0"}},{"ping":{"content":"ps:0"}},{"thread":{"thread":"' +
        threadID +
        '","version":"20061206","user_id":"guest","res_from":-150,"with_global":1,"scores":1,"nicoru":0}},{"ping":{"content":"pf:0"}},{"ping":{"content":"rf:0"}}]';
      // コメントセッションとのWebSocket接続を開始
      connect_WebSocket_comment();
    }

    // pingが送られてきたらpongとkeepseatを送り、視聴権を獲得し続ける
    if (is_ping > 0) {
      await websocket_system.send('{"type":"pong"}');
      await websocket_system.send('{"type":"keepSeat"}');
      console.log("ping");
    }
  }

  // 視聴セッションとのWebSocket接続中にエラーメッセージを受け取った時に実行される
  function onError_system(evt) {
    console.log("ERROR FROM THE SYSTEM SERVER: " + evt.data);
  }

  // 視聴セッションへメッセージを送るための関数
  async function doSend_system(message) {
    console.log("SENT TO THE SYSTEM SERVER: " + message);
    await websocket_system.send(message);
  }

  /*コメントセッションのWebSocket関係の関数*/
  // コメントセッションとのWebSocket接続関数の定義
  function connect_WebSocket_comment() {
    websocket_comment = new WebSocket(uri_comment, "niconama", {
      headers: {
        "Sec-WebSocket-Extensions":
          "permessage-deflate; client_max_window_bits",
        "Sec-WebSocket-Protocol": "msg.nicovideo.jp#json",
      },
    });
    websocket_comment.onopen = function (evt) {
      onOpen_comment(evt);
    };
    websocket_comment.onclose = function (evt) {
      onClose_comment(evt);
    };
    websocket_comment.onmessage = function (evt) {
      onMessage_comment(evt);
    };
    websocket_comment.onerror = function (evt) {
      onError_comment(evt);
    };
  }

  // コメントセッションとのWebSocket接続が開始された時に実行される
  function onOpen_comment(evt) {
    console.log("CONNECTED TO THE COMMENT SERVER");
    doSend_comment(message_comment);
  }

  // コメントセッションとのWebSocket接続が切断された時に実行される
  function onClose_comment(evt) {
    console.log("DISCONNECTED FROM THE COMMENT SERVER");
    commentGet(); //接続が切断されたら再読み込みをする
  }

  // コメントセッションとのWebSocket接続中にメッセージを受け取った時に実行される
  function onMessage_comment(evt) {
    //コメント部分のみを抽出
    const comment = JSON.parse(evt.data).chat?.content;
    //コメントを出力
    if (comment != undefined) {
      console.log(comment);
    }
  }

  // コメントセッションとのWebSocket接続中にエラーメッセージを受け取った時に実行される
  function onError_comment(evt) {
    console.log("ERROR FROM THE COMMENT SERVER: " + evt.data);
  }

  // コメントセッションへメッセージを送るための関数
  function doSend_comment(message) {
    console.log("SENT TO THE COMMENT SERVER: " + message);
    websocket_comment.send(message);
  }

  // 視聴セッションとのWebSocket接続開始
  connect_WebSocket_system();
}
commentGet();

実行する際の注意点

実行する際にはソースコードがあるディレクトリで、

npm install request cheerio ws

を実行して、 request, cheerio, wsのモジュールをインストールすることをお忘れなく。

実行例

コメントをconsole.logを出力しています。
今回コメントを取得した放送は、私の放送です。
スクリーンショット 2023-03-08 001945.png

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0