なかなかやることが見つけられないということもあり、久しぶりにNode-REDを触ろうかなと思いました。 そこで、まだやっていないことをリストアップしてみたところTwitter関連はまだやっていなかったので、 いいタイミングなのでチャレンジします!
また、Node-REDでツイート収集ができればBotやその他のWeb APIとの連携も容易になると思います。
念の為…
念のためバージョンアップしておきます。Ver1.0.4からVer1.0.6 たまたま運良く作業当日にVer1.0.6にバージョンアップしたようです。
【参考】
$ sudo npm install -g --unsafe-perm node-red /usr/bin/node-red -> /usr/lib/node_modules/node-red/red.js /usr/bin/node-red-pi -> /usr/lib/node_modules/node-red/bin/node-red-pi + node-red@1.0.6 removed 4 packages and updated 23 packages in 254.657s
【実行画面】
Node-RED実行します。(サービスからでもOKです。)
$ node-red
【実行画面】
しっかりとバージョンが1.0.6が表示されています。
Webブラウザからフローエディタへアクセスし、バージョンを確認してみます。
これで準備ができました。
ノードの追加
Twitterの処理を行うノードの追加を行います。
画面右側にあるハンバーガーメニュー【三】から【パレットの管理】を選択すると、
ユーザ設定のダイアログが表示されます。つづいて【ノードの追加】タブをクリックします。
すると、追加するノードを検索する画面に表示が変わります。
追加するのはnode-red-node-twitter
となるので、入力ボックスにtwitter
と入力します。すると一番上あたりに該当のノードが出現するので、右側にある【ノード】追加ボタンをクリックしてインストールを行います。
インストール後はNode-REDを再起動を行います。
都度起動している場合にはプロセスを終了して再度以下のコマンドを実行します。
$ node-red
サービスで起動している場合には以下で再起動させます。
$ sudo systemctl restart nodered.service
サービスで再起動した場合にはコンソール上には表示がされないので以下のコマンドで実行状態を確認します。
$ sudo systemctl status nodered.service
active(running)となっていれば無事に起動しています。
再起動したら、先程インストールされたノードがパレットに追加されているか確認します。
無事にソーシャルというグループにtwitter in
とtwitter out
のノードが追加されています。今回はtwitter in
を使って作っていこうと思います。
twitter inノード
でツイートを取得する
まずは、twitter inノード
とdebugノード
を取り出し、以下のように端子をつなげます。
とりだしたtwitter inノード
をダブルクリックして、【twitter in ノードを編集】ダイアログを開きます。
この画面ではTwitterのツイート取得に関する情報を設定することになります。Twitter IDの入力ボックスの右側にある【ペン】ボタンをクリックします。
twitter-credentialsの画面に変わります。ここではTwitter アプリに関する認証情報を入力することになります。
ここでは以下の項目を入力していくことになります。
事前にTwitterアプリを作成しておかないといけません。ポリシーの変更により、以前とは大きく異なって英語でどういうアプリなのかなどの申請を行って承認を得なければ作成することはできないようになっています。つまり即作成できるわけではありませんので、できるだけ事前に作成しておきましょう。 作成したアプリページは以下になります。(ちなみに同じページからアプリが作成申請を行えます)
https://developer.twitter.com/en/apps
事前に作成していればリスト形式でアプリが並びます。使用するアプリの【Details】ボタンをクリックし、
アプリの詳細画面から【Keys and tokens】タブをクリックして、
KeyとTokenが表示される画面に移ります。ここではKeyは表示されていますが、Tokenに関してはXで伏せられています。 作成時に表示されたTokenをメモしておけば、それを使用することになりますがメモを忘れた場合には【Regenerate】ボタンをクリックして再生成しましょう。
Twitterのページで以下の情報を得ることができたので
フローエディタの画面に戻って入力していきます。
入力が完了したら右上にある更新ボタンをクリックします。
これでTwitterアプリの認証情報が出来上がりました。(twitter inノード
を編集画面のTwitter IDにさきほど入力したユーザー名
が入っていれば完了しています)
あとは【検索対象】の入力ボックスにすべての公開ツイート
をセットし、
【検索条件】の入力ボックスにハッシュタグ#nhk
を入力します。
すべての設定が入力し終わったら右上にある【完了】ボタンをクリックします。 これでTwitterアプリの設定は完了しました。
デプロイして動作を確認する
まずはデプロイを行います。画面右上にある【デプロイ】ボタンをクリックして
以下のような表示がでれば完了です。
デプロイ完了後から自動でツイートを収集をはじめます。
デバック画面を表示すると以下のように次々と表示されていきます。
twitter inノード
の解説では以下のように書かれています。
これを参考にして確認をしてみたいと思います。
Debugノード
のpayload
表示
これツイートの本文が入っています。絵文字や改行コード(”\n”)なども含んだ形で入っているようです。
【msg.payload】の表示
"今週も #NHKジャーナル をお聴き頂きありがとうございました。\n#ステイホーム のお供に聴き逃し配信や #読むらじる。もおすすめです📻\n#ニュースで短歌 も近日UP予定📖\n我慢することが多いと思いますが少しでも息抜きできる工夫を… https://t.co/Q3D79dbj3A"```
Debugノード
のオブジェクト全体
表示
デバックをこちらに変更するとツイート全体のJSONデータが表示されます。ユーザー名やプロフィールリンク、ツイート日時などが必要であればこちらのJSONデータを解析することになります。以下はNHKラジオニュースさん公式のツイートとなります。
【msgオブジェクト全体】の表示
{ "topic": "tweets/nhk_radio_news", "payload": "今週も #NHKジャーナル をお聴き頂きありがとうございました。\n#ステイホーム のお供に聴き逃し配信や #読むらじる。もおすすめです📻\n#ニュースで短歌 も近日UP予定📖\n我慢することが多いと思いますが少しでも息抜きできる工夫を… https://t.co/Q3D79dbj3A", "lang": "ja", "tweet": { "created_at": "Fri Apr 24 14:10:10 +0000 2020", "id": 1253687683504697300, "id_str": "1253687683504697344", "text": "今週も #NHKジャーナル をお聴き頂きありがとうございました。\n#ステイホーム のお供に聴き逃し配信や #読むらじる。もおすすめです📻\n#ニュースで短歌 も近日UP予定📖\n我慢することが多いと思いますが少しでも息抜きできる工夫を… https://t.co/Q3D79dbj3A", "source": "<a href=\"http://www.nhk.or.jp/\" rel=\"nofollow\">NHK</a>", "truncated": true, "in_reply_to_status_id": null, "in_reply_to_status_id_str": null, "in_reply_to_user_id": null, "in_reply_to_user_id_str": null, "in_reply_to_screen_name": null, "user": { "id": 121348372, "id_str": "121348372", "name": "NHKラジオニュース", "screen_name": "nhk_radio_news", "location": "NHKラジオセンター", "url": "http://nhk.jp/radionews", "description": "▽マイあさ https://t.co/oQVD9ltuMO ▽Nらじ https://t.co/blhHtePPMt ▽NHKジャーナル https://t.co/rbLD12NKAZ の情報を発信。皆さんからのツイートは番組などでご紹介させていただくことがあります。各番組名のハッシュタグをつけて下さい。▼利用規約 https://t.co/Q11WanxIUh", "translator_type": "none", "protected": false, "verified": true, "followers_count": 27507, "friends_count": 62, "listed_count": 1455, "favourites_count": 47, "statuses_count": 136004, "created_at": "Tue Mar 09 06:32:44 +0000 2010", "utc_offset": null, "time_zone": null, "geo_enabled": false, "lang": null, "contributors_enabled": false, "is_translator": false, "profile_background_color": "DDF0FF", "profile_background_image_url": "http://abs.twimg.com/images/themes/theme1/bg.png", "profile_background_image_url_https": "https://abs.twimg.com/images/themes/theme1/bg.png", "profile_background_tile": true, "profile_link_color": "0084B4", "profile_sidebar_border_color": "C0DEED", "profile_sidebar_fill_color": "DDEEF6", "profile_text_color": "333333", "profile_use_background_image": true, "profile_image_url": "http://pbs.twimg.com/profile_images/1111584406517370885/9FX2-djD_normal.jpg", "profile_image_url_https": "https://pbs.twimg.com/profile_images/1111584406517370885/9FX2-djD_normal.jpg", "profile_banner_url": "https://pbs.twimg.com/profile_banners/121348372/1553857350", "default_profile": false, "default_profile_image": false, "following": null, "follow_request_sent": null, "notifications": null }, "geo": null, "coordinates": null, "place": null, "contributors": null, "is_quote_status": false, "extended_tweet": { "full_text": "今週も #NHKジャーナル をお聴き頂きありがとうございました。\n#ステイホーム のお供に聴き逃し配信や #読むらじる。もおすすめです📻\n#ニュースで短歌 も近日UP予定📖\n我慢することが多いと思いますが少しでも息抜きできる工夫を見つけながらお過ごしください(菅野)\nhttps://t.co/kKpOyHEBkN", "display_text_range": [ 0, 157 ], "entities": { "hashtags": [ { "text": "NHKジャーナル", "indices": [ 4, 13 ] }, { "text": "ステイホーム", "indices": [ 33, 40 ] }, { "text": "読むらじる", "indices": [ 53, 59 ] }, { "text": "ニュースで短歌", "indices": [ 69, 77 ] } ], "urls": [ { "url": "https://t.co/kKpOyHEBkN", "expanded_url": "https://www4.nhk.or.jp/nhkjournal/", "display_url": "www4.nhk.or.jp/nhkjournal/", "indices": [ 134, 157 ] } ], "user_mentions": [], "symbols": [] } }, "quote_count": 0, "reply_count": 0, "retweet_count": 0, "favorite_count": 0, "entities": { "hashtags": [ { "text": "NHKジャーナル", "indices": [ 4, 13 ] }, { "text": "ステイホーム", "indices": [ 33, 40 ] }, { "text": "読むらじる", "indices": [ 53, 59 ] }, { "text": "ニュースで短歌", "indices": [ 69, 77 ] } ], "urls": [ { "url": "https://t.co/Q3D79dbj3A", "expanded_url": "https://twitter.com/i/web/status/1253687683504697344", "display_url": "twitter.com/i/web/status/1…", "indices": [ 117, 140 ] } ], "user_mentions": [], "symbols": [] }, "favorited": false, "retweeted": false, "possibly_sensitive": false, "filter_level": "low", "lang": "ja", "timestamp_ms": "1587737410795" }, "location": { "place": "NHKラジオセンター" }, "_msgid": "801cfa15.d25888" }
これでTwitterAPIを使用してツイートデータを取得することができるようになりました。RaspberryPi
では非力なので、ある程度検索ワードで絞ったほうがいいのかなという印象です。
取得したツイートをデータベースに格納する
ここまでではツイートを取得するだけで、保存はできません。
せっかくなので今回取得したツイートをSQLite
のデータベースに格納しようと思います。
SQLite
に関しては基本的には以前やった内容の復習となります。
ではSQLiteの格納に関するフローを作成します。復習になるので、全体像をはじめにみせます。
クエリ文字列をinjectノード
から出力し、そのクエリを実行、debugノード
で表示するという形式になります。
では、クエリ文字列をinjectノード
から出力する部分を見てみます。この部分が一番重要ともともいえます。
4つのノードを作成してみました。
- テーブル生成
- テストデータの登録(デバック用)
- テーブル内のデータの表示
- テーブルの削除
テーブル生成
テーブル作成のノードを以下のようにします。
【トピック】の入力ボックスへは以下のクエリを入力して、【完了】ボタンをクリックします。テーブルが存在しなければテーブルを作成するようにしています。また、テーブルに格納する値は以下としました。
入力するクエリ文字列は以下の様にしています。
CREATE TABLE IF NOT EXISTS TW (ID INTEGER PRIMARY KEY AUTOINCREMENT, DATE TEXT, SCREENNAME TEXT, NAME TEXT, TW TEXT, OAFLAG TEXT, ICON_URI TEXT)
テストデータの登録
次はデバック様に作ったテストデータの登録になります。特に大きな意味はありませんが、記述などで詰まったところでデータを入れてテストするといいと思います。
【トピック】の入力ボックスへは以下のクエリを入力して、【完了】ボタンをクリックします。ツイートされた日時のタイムゾーンはローカルタイムゾーンになっていないので使用する場合には、+0900などに調整する必要があります。
INSERT INTO TW VALUES(NULL, 'Sun Nov 10 12:21:46 +0000 2019', 'スクリーンネーム', 'なまえ', 'ついーと' , '0', 'アイコン.jpg');
テーブル内のデータの表示
これは作成したテーブルの中身を表示させるノードです。【トピック】の入力ボックスへは以下のクエリを入力して、【完了】ボタンをクリックします。
select * from TW;
テーブルの削除
これはテーブル削除のノードです。【トピック】の入力ボックスへは以下のクエリを入力して、【完了】ボタンをクリックします。
drop table TW;
sqliteノード
の追加
sqliteノード
を追加します。
データベースファイルは/home/pi/tweet.db
を指定しておきます。
debugノード
の追加
最後は処理結果を表示するdebugノード
を追加しておきます。
Twitter アプリのデータをSQLite
に格納する
いよいよ、ツイートデータを格納します。ツイートデータもすべてが必要ではないので、検索されたツイートのJSONデータの一部のものがだけが必要なので、そのデータをクエリに埋め込みます。クエリの作成はtemplateノード
を追加して行います。
場所としてはtwitter inノード
とdebugノード
の間に格納します。各パラメータはJSONデータを参照することで取り出すことができます。今回はJSONデータのtweet
の中にあります。
- プライマリーキーID …
NULL
- ツイートの日時 …
tweet.created_at
- スクリーンネーム …
tweet.user.screen_name
- ネーム …
tweet.user.name
- ツイート …
tweet.text
- フラグ(特に必要はありませんが…) …
0
- ユーザーのアイコン画像URL …
tweet.user.profile_image_url_https
となります。テンプレートの中に埋め込む場合には{{ }}
が必要になります。例えば、tweet.created_at
を埋め込む場合には{{tweet.created_at}}
となります。
テンプレートには以下のテキストを入力して、【完了】ボタンをクリックします。
INSERT INTO TW VALUES(NULL, '{{tweet.created_at}}', '{{tweet.user.screen_name}}', '{{tweet.user.name}}', '{{tweet.text}}', '0', '{{tweet.user.profile_image_url_https}}');
ここまでできたら、テンプレートの出力端子をクエリの実行につなぎ、右上の【デプロイ】をクリックします。
すると、自動的にツイートを取得し、データベースに格納されていきます。(ただし、SQLite
のテーブルを作成していない場合にはエラーとなります)
画面をぼかしていますが、フローエディタの右側のデバックタブにツイートが表示されています。
【twitter in】ノードを停止する場合
デプロイするとtwitter inノード
は自動的に収集動作を開始してしまいます。debugノード
を接続した状態だと常に表示更新をし続けてしまうので、これでは面倒です。そういう場合には、twitter inノード
をダブルクリックし、【twitter in ノードを編集】画面を開き、一番下の【有効】ボタンをクリックして、
【無効】に変更します。
するとtwitter inノード
が破線の状態になり、動作が無効になります。
便利なのでメモとしておきます。
おわりに
Node-REDのTwitterノードをしようすることで簡単にツイートデータを収集することができるようになりました。慣れればすぐにできるようになると思います。ただ、DBに格納するとなるとクエリを書く必要があるので、その部分ではコーディングに近い作業が必要かなと思います。収集した後はデータを分析に使うなりなんなりすることができそうです。
他にもWebsocketのサーバーにできればクライアント側との通信を行ってWEB上に表示するといったことも可能かなと思います。Javascriptの知識は必要そうですけどね。
以下に本日作成したフローをエクスポートしました。ちなみにTwitterノード
はエクスポートした際にはCredential
情報は削除されます。これは情報を公開する側からするといい方法だなと思いました。
【JSONファイル抜粋】
[ { "id": "c1a07194.6d43b", "type": "tab", "label": "フロー 1", "disabled": false, "info": "" }, { "id": "8c0b920e.bad9a", "type": "debug", "z": "c1a07194.6d43b", "name": "", "active": true, "tosidebar": true, "console": false, "tostatus": false, "complete": "payload", "targetType": "msg", "x": 850, "y": 400, "wires": [] }, { "id": "71c929fa.e1a548", "type": "inject", "z": "c1a07194.6d43b", "name": "テストデータの登録", "topic": "INSERT INTO TW VALUES(NULL, 'Sun Nov 10 12:21:46 +0000 2019', 'スクリーンネーム', 'なまえ', 'ついーと' , '0', 'アイコン.jpg');", "payload": "", "payloadType": "date", "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "x": 170, "y": 440, "wires": [ [ "e1784590.017c28" ] ] }, { "id": "168be7d7.7bb9c8", "type": "inject", "z": "c1a07194.6d43b", "name": "テーブル内のデータの表示", "topic": "select * from TW;", "payload": "", "payloadType": "date", "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "x": 190, "y": 480, "wires": [ [ "e1784590.017c28" ] ] }, { "id": "2e977694.7c7eba", "type": "inject", "z": "c1a07194.6d43b", "name": "テーブルの削除", "topic": "drop table TW;", "payload": "", "payloadType": "date", "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "x": 160, "y": 520, "wires": [ [ "e1784590.017c28" ] ] }, { "id": "81e5a60.fd8fe58", "type": "inject", "z": "c1a07194.6d43b", "name": "テーブル生成", "topic": "CREATE TABLE IF NOT EXISTS TW (ID INTEGER PRIMARY KEY AUTOINCREMENT, DATE TEXT, SCREENNAME TEXT, NAME TEXT, TW TEXT, OAFLAG TEXT, ICON_URI TEXT)", "payload": "", "payloadType": "date", "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "x": 150, "y": 400, "wires": [ [ "e1784590.017c28" ] ] }, { "id": "e1784590.017c28", "type": "sqlite", "z": "c1a07194.6d43b", "mydb": "56879991.d56508", "sqlquery": "msg.topic", "sql": "", "name": "クエリの実行", "x": 640, "y": 400, "wires": [ [ "8c0b920e.bad9a" ] ] }, { "id": "1f44d7ed.263438", "type": "comment", "z": "c1a07194.6d43b", "name": "データベース処理", "info": "", "x": 150, "y": 360, "wires": [] }, { "id": "544f2b7c.eed8f4", "type": "twitter in", "z": "c1a07194.6d43b", "d": true, "twitter": "", "tags": "#nhk", "user": "false", "name": "read Tweet", "inputs": 0, "x": 140, "y": 597, "wires": [ [ "71ae5ab9.f11f64" ] ] }, { "id": "795001b7.1378", "type": "debug", "z": "c1a07194.6d43b", "name": "", "active": true, "tosidebar": true, "console": false, "tostatus": false, "complete": "topic", "targetType": "msg", "x": 640, "y": 597, "wires": [] }, { "id": "71ae5ab9.f11f64", "type": "template", "z": "c1a07194.6d43b", "name": "", "field": "topic", "fieldType": "msg", "format": "handlebars", "syntax": "mustache", "template": "INSERT INTO TW VALUES(NULL, '{{tweet.created_at}}', '{{tweet.user.screen_name}}', '{{tweet.user.name}}', '{{tweet.text}}', '0', '{{tweet.user.profile_image_url_https}}');", "output": "str", "x": 420, "y": 597, "wires": [ [ "795001b7.1378", "e1784590.017c28" ] ] }, { "id": "f0d739b1.3ac638", "type": "comment", "z": "c1a07194.6d43b", "name": "※Twitter-inノードのユーザー情報はexportすると消える", "info": "", "x": 280, "y": 657, "wires": [] }, { "id": "56879991.d56508", "type": "sqlitedb", "z": "", "db": "/home/pi/tweet.db", "mode": "RWC" } ]
【参考】