Amazon.co.jp ウィジェット ソフト公開 Archive - PC破壊日記的ブログ

Home > ソフト公開 Archive

ソフト公開 Archive

Twitterのタイムライン(TL)をnode+MongoDBで保存、Ruby+Sinatraで表示できるようにしてみた。

このエントリーをはてなブックマークに追加

Twitter、私も利用しているのですが、フォロー数が多くなると問題となるタイムラインの流速問題。そうでなくても、公式クライアント含む各種Twitterではタイムラインの保存に限度があるので、過去の物を検索しようとしても不可能か、かなり困難です。
ならばTLを保存すれば良いのではないか?ということで、いろいろ調べた結果、「Twitter APIで取得できるデータはJSON形式、なのでMongoDBと親和性高い」ということで、この方向で進めることに。

参考にしたサイトが多数にわたるため、本記事の最後に記載します。参考にさせて頂いたことに感謝します。

1. 前準備

まずはRuby、node、MongoDBをインストール。私の環境はubuntu16.04 LTSですが、他のLinuxディストリビューションでも同様の環境は準備可能だと思います。ただ、できればOS/MongoDBは64bitを推奨。(データベースのサイズ制限、メモリ制限ほか様々な制限のため)

また、各ソフトウェアの細かい設定はしていない(標準設定のまま)なので、必要に応じ情報を収集して対応して下さい。


sudo apt install ruby
sudo apt install nodejs-legacy
sudo apt install npm

MongoDBについては、ubuntu16.04 LTSで標準のaptリポジトリでインストールされるパッケージが古すぎる(2.6.x系)ので、MongoDBの公式のインストール手順に従って3.x系をインストールして下さい。(2.6.x系だとソート時にソート用バッファメモリ不足が発生し、かつ回避が困難です)

Install MongoDB Community Edition on Ubuntu -- MongoDB Manual 3.4
https://docs.mongodb.com/manual/tutorial/install-mongodb-on-ubuntu/

一応、MongoDBが64bitでインストールされているか確認。


user@hoge:~$ mongo
> use admin
switched to db admin
> db.runCommand("buildInfo")
(中略)
"bits" : 64,
(後略)

問題無さそう。

ライブラリも必要。(途中で必要になる)

sudo apt install ruby-dev

次はnode用パッケージのインストール。

次はRuby-gemのインストール

2. MongoDBのアクセス制限の設定

MongoDB 3.6はパフォーマンスやセキュリティの関係で色々ワーニングが出ます。公式マニュアルにも記載がありますが、他にも日本語での説明ページがあったのでリンクしておきます。

MongoDB 3系を CentOS7 にインストール - Qiita
http://qiita.com/SOJO/items/dc5bf9b4375eab14991b

これ以外にも公式マニュアル他を参照して設定して下さい。まあデフォルトのままでも動きますけどね・・・。

3. TwitterのAPI KEYを取得する。

これはあちこちに解説記事があるので省略したい。必要な権限はツイートの読み取り権限のみです。書き込みはしないので今回は不要です。
consumer_key, consumer_secret, access_token_key, access_token_secretの4つのキーを後で使います。
一応、API申請ページにリンクしておきます。

4. Twitter TL取得用のスクリプトの作成。(前準備含む)

タイムライン取得にはMongoDBと親和性のあるNode.jsを利用します。まずはスクリプトの保存フォルダの作成と必要なライブラリの入手。


$ mkdir tw2db
$ cd tw2db
$ npm install mongoose
$ npm install twitter

次に、スクリプトの作成。これは下記サイトの内容そのままで動いたので丸パクリです(汗)
ただし、MongoDBのユーザー認証を有効にしているので、そこだけ改変しています。 Twitter の TL を全部 MongoDB にぶち込んでニヤニヤする - 凹みTips


var twitter  = require('twitter')
  , mongoose = require('mongoose')
  , Schema   = mongoose.Schema
;

// typeof で得た文字列を型に変換
var typeMap = {
        number   : Number,
        string   : String,
        boolean  : Boolean,
        object   : Object,
        function : Function
};

// オブジェクト/配列を受け取って Mongoose 用 Schema に変換
function makeSchema(data) {
        var schema = {};
        for (var x in data) {
                var type = typeof data[x];
                if (data[x] === null) {
                        schema[x] = Object;
                } else if (type === 'object') {
                        schema[x] = makeSchema(data[x]) ;
                } else {
                        schema[x] = typeMap[type];
                }
        }
        return schema;
}

// MongoDB へ接続
mongoose.connect('mongodb://dbuser:dbpasswd@localhost/Twitter');

// mongoose のスキーマ
var PostSchema, Post, isSchemaDefined = false;

// Twitter へ接続
new twitter({
        consumer_key        : 'XXXXXXXXXXXXXXXXXXXXXXXX',
        consumer_secret     : 'XXXXXXXXXXXXXXXXXXXXXXXX',
        access_token_key    : 'XXXXXXXXXXXXXXXXXXXXXXXX',
        access_token_secret : 'XXXXXXXXXXXXXXXXXXXXXXXX'
}).stream('user', function(stream) {
        stream.on('data', function(data) {
                // Friends リストのデータはすっ飛ばす
                if ( !('id' in data) ) {
                        return;
                } else {
                        console.log(data.user.screen_name, data.text);
                }

                // 最初のデータで Schema を作成
                if (!isSchemaDefined) {
                        PostSchema = new Schema( makeSchema(data, '') )
                        Post       = mongoose.model('Post', PostSchema)
                        isSchemaDefined = true;
                }

                // Post Schema から保存用のデータを生成して保存
                var post = new Post(data);
                post.save( function(err) {
                        if (err) console.error(err);
                });
        });
});

// 例外処理
process.on('uncaughtException', function (err) {
        console.log('uncaughtException => ' + err);
});

もちろんconsumer_key, consumer_secret, access_token_key, access_token_secretの部分は2.で取得した情報と書き換えて下さいね。
あと、ユーザー認証をする場合とユーザー認証をしない場合で、下記の行の修正が若干異なります。


*ユーザー認証する場合
mongoose.connect('mongodb://dbuser:dbpasswd@localhost/Twitter');
上記のdbuser、dbpasswdを置き換えて下さい。
*ユーザー認証しない場合
mongoose.connect('mongodb://localhost/Twitter');

あとは、動作テスト。


node tw2db.js

これで、自分のタイムラインに流れているツイートと同じものが流れてくるはずです。

5. Node.jsの自動起動と、自動Kill(cronで)

まずはNode.jsの起動を自動化します。これは下記ページを参考にして下さい。私はinit.dでサービス起動する形にしました。

initd-foreverでNode.jsアプリをデーモン化する | Developers.IO
http://dev.classmethod.jp/server-side/daemonize-nodejs-by-initd-forever/

Node.jsが重すぎるのか、一日以上経過すると頻繁にフリーズしているので、自動killするためのスクリプトを作ります。 cronで実行するので、シェルスクリプトに。ファイル名は例えば「autokill.sh」とかで。もちろんchmodで実行可能にするのを忘れずに。

#!/bin/sh

pid=`ps -elf | grep "tw2db.js" | grep -v grep | awk '{print $4}'`
if [ "${pid}" != "" ]; then
kill ${pid}
echo "kill ${pid}"
fi
sleep 2
nohup /usr/bin/node /path/to/tw2db/tw2db.js > /dev/null &

あとはcronに登録しておいて下さい。だいたい1日1回ぐらいでも良いと思います。私は1時間に一回(毎時40分頃)にしていますが・・・。

6. ビューア部分の作成(Ruby+Sinatra)

ある意味ここからが本番。

ビューアに必要なライブラリはこちら。


$ gem install mongo
$ gem install twitter
$ gem install sinatra
$ gem install sinatra-i18n
$ gem install rack-contrib
他にもあったかも・・・

そして、私が作成したスクリプトがこちら。なお、以下5つのファイル/フォルダを作成しています。


~/tw2db/viewer.rb
~/tw2db/views/tweets.erb
~/tw2db/config/application.rb
~/tw2db/config/locales/en.yml
~/tw2db/config/locales/ja.yml
~/tw2db/viewer.rb
require 'rubygems'
require 'mongo'
require 'sinatra'
require 'sinatra/i18n'
require 'rack/contrib'
require 'date'
require 'rack'
use Rack::Deflater
Sinatra.register Sinatra::I18n
#use Rack::Locale

#require './config'
CONNECTION_STRING = "mongodb://dbuser:dbpasswd@localhost/Twitter"
COLLECTION_NAME = "posts"
TAGS = ["mongodb","ruby"]
set :environment, :production

# The Unix epoch is the time 00:00:00 UTC on January 1, 1970
UNIX_EPOCH_TIME = Time.at(0)

# Strict version of +Time.parse+, returns +nil+ when parsing is failed.
def strict_parsetime(string)
  # +Time.parse+ returns localtime "1970/01/01 00:00:00" when parsing is failed.
  # So, ugly, I check whether returned value is UNIX epoch.
  time = Time.parse(string, UNIX_EPOCH_TIME) rescue nil
  if UNIX_EPOCH_TIME == time then
    # Previous +Time.parse+ possibly failed.
    time = nil unless (ParseDate.parsedate(string)[0] rescue nil)
  end
  time
end

def tweet_id2time(id)
  case id
  when Integer
    Time.at(((id >> 22) + 1288834974657) / 1000.0)
  else
    nil
  end
end

def time2tweet_id(time)
  (time.to_f * 1000 - 1288834974657).to_i << 22
end

configure do
  db = Mongo::Client.new(CONNECTION_STRING)
  TWEETS = db[COLLECTION_NAME]
I18n.default_locale = :ja
I18n.locale = :ja
end

get '/' do
    selector = {}

    @search_text=""
    @from_date=Time.at(1288834974657/1000)
    @to_date=Time.now
  @tweets = TWEETS.find({:id => {'$gte'=> (time2tweet_id(@from_date)),'$lte'=>(time2tweet_id(@to_date))}}).sort({"id" => -1}).limit(10000)
  @twcount=@tweets.count()
  erb :tweets
end
post '/' do
  @from_date=strict_parsetime(params[:from_date])
  @to_date=strict_parsetime(params[:to_date])
  if @from_date==nil
    @from_date=Time.at(1288834974657/1000)
  end
  if @to_date==nil
    @to_date=Time.now
  end
  @search_text=params[:search_text]
  @tweets = TWEETS.find({'$and':[{:text => Regexp.new(@search_text)},{:id => {'$gte'=> (time2tweet_id(@from_date)),'$lte'=>(time2tweet_id(@to_date))}}]}).sort({"id" => -1})
  @twcount=@tweets.count()
  erb :tweets
end

今回のスクリプトは外部接続を受け付ける設定「set :environment, :production がそれ」にしています。ここは必要に応じ改変して下さい。
ユーザー認証をする場合とユーザー認証をしない場合で、下記の行の修正が若干異なります。

*ユーザー認証する場合
CONNECTION_STRING = "mongodb://dbuser:dbpasswd@localhost/Twitter"
上記のdbuser、dbpasswdを置き換えて下さい。
*ユーザー認証しない場合
CONNECTION_STRING = "mongodb://localhost/Twitter"
~/tw2db/views/tweets.erb
<!DOCTYPE>
<html>

<head>
  <style>
    body{
      width:1000px;
      margin: 50px auto;
    }
    h2{
      margin-top:2em;
    }
        pre {
            /* Mozilla */
            white-space: -moz-pre-wrap;

            /* Opera 4-6 */
            white-space: -pre-wrap;

            /* Opera 7 */
            white-space: -o-pre-wrap;

            /* CSS3 */
            white-space: pre-wrap;

            /* IE 5.5+ */
            word-wrap: break-word;
        }
  </style>
    <title>Tweet Archive</title>
</head>

<body>
  <h1>Tweet Archive</h1>
  search by Time/Date <form action="/" method="post">
<br>
search string(Regexp)<input type="text" name="search_text" placeholder"文字列を 入れると検索します" value="<%= @search_text %>"><br>
since<input type="text" name="from_date" placeholder="yyyy/mm/dd hh:mm:ss" value="<%= @from_date %>"><br>
until<input type="text" name="to_date" placeholder="yyyy/mm/dd hh:mm:ss" value="<%=
 @to_date %>"><br>
<input type="submit">
</form>
  <% TAGS.each do |tag| %>
    <a href="/?tag=<%= tag %>"><%= tag %></a>
  <% end %>
<br>
search count:<%= @twcount %> <br>
  <% @tweets.each do |tweet| %>
<hr>
    <pre><h2><font size=6><%= tweet['text'] %></font></h2></pre>
    <p>
      <a href="http://twitter.com/<%= tweet['user']['screen_name'] %>">
        <%= tweet['user']['name'] %>
      </a>
      on <a href="https://twitter.com/<%= tweet['user']['screen_name'] %>/statuses/<%= tweet['id_str'] %>" target="_blank"><%= I18n.l Time.parse( tweet['created_at']).to_time , format: :long %></a><%= Time.at(((tweet['id_str'].to_i >> 22 )+1288834974657)/1000.0).strftime("%Y-%m-%d %H:%M:%S.%L %Z") %>

    </p>

    <img src="<%= tweet['user']['profile_image_url'] %>" width="48" />
  <% end %>

</body>

</html>
~/tw2db/config/application.rb

config.time_zone = 'Tokyo'
config.i18n.default_locale = :ja
~/tw2db/config/locales/en.yml、 ~/tw2db/config/locales/ja.yml (どちらも全く同じ内容です)
ja:
  time:
    formats:
      default: ! '%Y/%m/%d'
      long: ! '%Y年%m月%d日 %H時%M分%S秒 %z'
      short: ! '%Y年%m月%d日 %H:%M'

en:
  time:
    formats:
      default: ! '%Y/%m/%d'
      long: ! '%Y年%m月%d日 %H時%M分%S秒 %z'
      short: ! '%Y年%m月%d日 %H:%M'
あとはスクリプトを実行して、ブラウザからアクセスします。

ruby viewer.rb
立ち上げたサーバーのポート4567にアクセスすればOKです。
例:http://192.168.0.1:4567/
検索部分は正規表現ですが、あまり高機能じゃないので過信しないように。

X.参考にしたサイト群

今回は以下のサイトのスクリプトや情報を元に活用しました。本当に感謝です。

* MongoDBのインストール

MongoDBの薄い本(The Little MongoDB Book) - cuspy diary

http://www.cuspy.org/diary/2012-04-17/

* Twitter TLをMongoDBに保存

Twitter の TL を全部 MongoDB にぶち込んでニヤニヤする - 凹みTips

http://tips.hecomi.com/entry/20120908/1347094725

* forever と initrd-foreverで死活監視

Node.js 自動再起動モジュール - Qiita

http://qiita.com/disc99/items/57490f5eef3e2eb685ba

node.js node.jsスクリプトをforeverでデーモン化する -でじうぃき

http://onlineconsultant.jp/pukiwiki/?node.js%20node.js%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%97%E3%83%88%E3%82%92forever%E3%81%A7%E3%83%87%E3%83%BC%E3%83%A2%E3%83%B3%E5%8C%96%E3%81%99%E3%82%8B

initd-foreverでNode.jsアプリをデーモン化する | Developers.IO
http://dev.classmethod.jp/server-side/daemonize-nodejs-by-initd-forever/

 

* node.jsの自動Kill(頻繁に固まるので)

node.jsの自動再起動 | 爆裂健.com

http://bakuretuken.com/node-js%E3%81%AE%E8%87%AA%E5%8B%95%E5%86%8D%E8%B5%B7%E5%8B%95/

* Ruby+Sinatraでログを表示する

Ruby MongoDB イン・アクション のTweetArchiverを作成する - 1.21 jigowatts

http://sh-yoshida.hatenablog.com/entry/2016/08/27/025808

* Ruby+Sinatraでpostデータ処理(検索用)

Sinatraでフォームからデータを受け取る方法

http://ruby.weva.jp/sinatra/2013/09/10/params.html

* Rubyでの日付文字列の処理の厳格化

Ruby の Time.parse で文字列を Time に変換するときのエラーチェック

http://www.metareal.org/2007/06/21/error-checking-in-ruby-time-parsing/

*Ruby+Sinatraの日付のロケール処理

RubyとRailsにおけるTime, Date, DateTime, TimeWithZoneの違い - Qiita

http://qiita.com/jnchito/items/cae89ee43c30f5d6fa2c

Hideki SAKAMOTO の雑記 (2010-09-26)

http://www.on-sky.net/~hs/index.cgi?date=20100926

Sinatra Recipes - Development - I18n

http://recipes.sinatrasapporo.org/p/development/i18n?


東方名華祭8用 サークルリストからスペース順を生成するVBAマクロ

このエントリーをはてなブックマークに追加

2014年3月30日に開催される東方名華祭8(http://meikasai.com/)にて現在公開されているサークルリストからスペース順を生成するVBAマクロを含んだExcelファイルを作成しました。

BASEシートに記載のサンプルデータ通りにデータを入力して、「マクロを実行」ボタンで実行してください(投げっぱなし)
ALLシートにある程度整形した物が出力されます。

・・・すみません、明日にはもう少しきちんとした物を。。。

スペース名分割.xlsm

続きを読む - 東方名華祭8用 サークルリストからスペース順を生成するVBAマクロ


Spriteとマウスイベントとの関係 Rectangle(四角)編

このエントリーをはてなブックマークに追加

先日誤ってAdobe FlashをIYHしてしまったので、
せっかくなので昔から作ろうと考えていたゲームをFlashで作ろうかと。
というのも、どうしてもJavascriptでは、マウスやキーボードイベント関係で難しい部分があったためです。

今回は、Spriteで四角形を描き、マウスイベント(MOUSE_OVER:マウスカーソルがSpriteの上に来たとき)
を実行しようと試みました。

しかし、なぜか反応が極端に悪い。
何回か通過させると反応することもあるのですが、これでは実用に耐えません。

最初はFlashが重すぎるのかと考えましたが、Flashの負荷状況を調べる
stats(ActionScript入門Wiki@rsakane - FPS、メモリ使用量を可視化するStatsを使用する
を使用して調べてみると、いくらマウスを動かしても負荷が高すぎることなんか全然ありません。

これはどういうことか。

続きは「続きを読む」より・・・。

続きを読む - Spriteとマウスイベントとの関係 Rectangle(四角)編



OutlookExpressメールボックスサイズ容量警告プログラムVer1.0、公開開始

このエントリーをはてなブックマークに追加

私の人生の中で正式にVectorに登録した一般公開ソフトは今回が初となります。

Outlook Express メールボックスサイズ容量警告プログラムを一般公開いたしました。
Ver1.0→http://www.vector.co.jp/soft/win95/net/se475524.html
(Vectorでの公開ファイル名と異なりますが、あちらはタイトル名制限があるので・・・)

本ソフトの更新、本ソフトに関する事柄については、カテゴリ「ソフト公開」にて行います。
要望等についても当ブログやメールにて受け付けます。

よく見たらVectorのコメント部分、文字化けしてますねぇ・・・。
修正依頼出しておきますか。

検索用キーワード:
OutlookExpress、Outlook Express メールボックス メールフォルダ 容量 サイズ 警告 プログラム 2GB 受信できない 送信できない メール 消える 消えた 空っぽ 送受信できない エラー

時代に即したソフトウェア、ついに発表! OutlookExpress メールボックスサイズ警告プログラム!

このエントリーをはてなブックマークに追加

本日 2009年4月1日、ついに時代に即したソフトウェアの開発に成功しました。
(実はエイプリルフールネタではなく、本当に作りました)

続きを読む - 時代に即したソフトウェア、ついに発表! OutlookExpress メールボックスサイズ警告プログラム!

Index of all entries

Home > ソフト公開 Archive

2進数時計
※クリックで読みやすくなります。
※この時計の時刻は、閲覧しているパソコンのものであり、必ずしも正確な時間とは限りません
Search
Feeds

Google Adsense
Tag Cloud

このページの最初に戻る