2013年4月5日金曜日

tweets.zipをMongoDBに突っ込んでNoSQLを学ぶ(導入編)

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
 インスパイアされたので。MongoDBで同じような事やってみよーーーーかなーーーーーーーーーと。まぁあんまりMongoDB知らないですけど。
tweets.zipをMySQLに突っ込んでSQLを学ぶ(導入編)

なにはともあれアーカイブをダウンロード


これはtweets.zipをMySQLに突っ込んでSQLを学ぶ(導入編)に書かれているのでそちらを参考にして下さい。サーセン


tweets.csvを探す


実はtweets/data/js/tweets配下に月ごとのつぶやきのJSONデータがあるのでそれ使ったらええんちゃう、と思ったんですけど、ファイル分かれてるしめんどーなんでtweets.csvでいいです。tweets.csvはtweetsの直下にあります。



MongoDBでcsvをインポートする


csvのインポートはMongoDBに付属している"mongoimport"コマンドを利用します。が、残念ながらTwitterから落とせるcsvは色々腐っていてすんなりMongoDBにインポートする事が出来ません。という事でまずcsvデータを加工します。

csvを加工する


csv加工もjavascriptを使いたいなーと思うんでPhantomJSを使います。
PhantomJS: Headless WebKit with JavaScript API

Macならportsとかでスカッと入れられるんで入れたらええんちゃうんかと。brewは知らんけどあるでしょう。
sudo port install phantomjs

んで、以下のscriptを適当に保存してphantomjsで実行してcsvを加工します。ちょい長いです。具体的には"(ダブルクォーテーション)とかがフリーダムなんでちゃんとパースして適切なエスケープを入れてます。僕の4年分の15000件分程度なんでまだパターン漏れがあるかもしれません。改行も\nでやってるんでWindows環境だとダメかもしれません。死んだら教えて下さい。お悔やみ申し上げます。
var fs = require("fs");
var stream = fs.open(phantom.args[0], "r");
var tweets = stream.read();
stream.close();

var STATUS = {
 "WAIT":"0",
 "PARSE":"1",
 "CONTENT":"2"
}
var eol = "\n";
var size = tweets.length;
var i=0;
var result = "";
var status = STATUS.WAIT;
while(true){
 if(i >= size){
  break;
 }
 var c = tweets[i];
 if(status === STATUS.WAIT){
  if(c === "\""){
   status = STATUS.PARSE;
   result += c;
  }
 }
 else if(status === STATUS.PARSE){
  if(c === "\""){
   status = STATUS.CONTENT;
  }
  else if(c === eol){
   result += "\\n";
  }
  else if(c === ","){
   result += "\\,";
  }
  else{
   result += c;
  }
 }
 else if(status ===  STATUS.CONTENT){
  if(c === "," || c === eol){
   result += "\""+c;
   status = STATUS.WAIT;
  }
  else{
   result += "\\\"";
   if(c === "\""){
    result += "\\\"";
   }
   else{
    result += c;
   }
   status = STATUS.PARSE;
  }
 }
 i++;
}
console.log(result);
phantom.exit();

上記スクリプトを"organizing_csv.js"として保存したとします。phantomjsでcsvを加工します。
phantomjs organizing_csv.js tweets.csv  > tweets.csv.opt


csvをインポートする


さて、準備が出来たのでMongoDBに突っ込みます。ここはもう簡単です。mongoimportに各種オプションを指定して読ませるだけです。-dはDB名、-cはコレクション名です。折角なんでtwitterとtweetsとしました。

mongoimport -d twitter -c tweets -type csv -file tweets.csv.opt  -headerline

>connected to: 127.0.0.1
>Thu Apr  4 22:49:14   3258534/3382985 96%
>Thu Apr  4 22:49:14    14700 4900/second
>Thu Apr  4 22:49:14 imported 15205 objects

無事15205件のツイートがMongoDBに突っ込まれました。


MongoDBで黒歴史を遡る


折角インポートしたんでちょっと触ってみましょうか。

mongoに接続してDB一覧を見る


show dbsでDB一覧が見れます。twitterデータベースが出来ている事が確認出来ます。
mongo
>MongoDB shell version: 2.2.1
>connecting to: test
> show dbs
> local (empty)
> twitter 0.203125GB

DBを選択する


んで、まずDBを選択します。
use twitter

次にコレクションを見てみましょう。tweetsがありますねー。
show collections

>system.indexes
>tweets

とりあえず全件表示、件数表示


MongoDBはjavascriptで操作します。まーどうでもいいです。全件出す場合はこんな感じ。
db.tweets.find();

{ "_id" : ObjectId("515e68ee81dc484aaf93e666"), "tweet_id" : NumberLong("319741609916919808"), "in_reply_to_status_id" : "", "in_reply_to_user_id" : "", "retweeted_status_id" : "", "retweeted_status_user_id" : "", "timestamp" : "2013-04-04 09:21:54 +0000", "source" : "Tweet Button", "field11" : "ほお [ A3直前!NFCLAB追い込みハッカソン(開発者枠) http://t.co/VJL7MP2hhj ]", "field12" : "http://connpass.com/event/2094/" }
{ "_id" : ObjectId("515e68ee81dc484aaf93e667"), "tweet_id" : NumberLong("319702386367135744"), "in_reply_to_status_id" : "", "in_reply_to_user_id" : "", "retweeted_status_id" : "", "retweeted_status_user_id" : "", "timestamp" : "2013-04-04 06:46:03 +0000", "source" : "Tweet Button", "field11" : "実機でFirefox OSアプリ試せるのねぇ [ Hello Firefox OS: Firefox MarketplaceをAndroid版Firefox Auroraで試す http://t.co/1v5u27ZHlj ]", "field12" : "http://bsfirefox.blogspot.com/2013/04/firefox-marketplaceandroidfirefox-aurora.html" }
{ "_id" : ObjectId("515e68ee81dc484aaf93e668"), "tweet_id" : NumberLong("319614807709990912"), "in_reply_to_status_id" : "", "in_reply_to_user_id" : "", "retweeted_status_id" : "", "retweeted_status_user_id" : "", "timestamp" : "2013-04-04 00:58:02 +0000", "source" : "TweetDeck", "field11" : "今出しょう子も可愛くしたげてよ・・・" }
{ "_id" : ObjectId("515e68ee81dc484aaf93e669"), "tweet_id" : NumberLong("319612544287399937"), "in_reply_to_status_id" : "", "in_reply_to_user_id" : "", "retweeted_status_id" : "", "retweeted_status_user_id" : "", "timestamp" : "2013-04-04 00:49:03 +0000", "source" : "TweetDeck", "field11" : "staticおじさんとか可愛いもんやで" }
{ "_id" : ObjectId("515e68ee81dc484aaf93e66a"), "tweet_id" : NumberLong("319610859334811651"), "in_reply_to_status_id" : "", "in_reply_to_user_id" : "", "retweeted_status_id" : "", "retweeted_status_user_id" : "", "timestamp" : "2013-04-04 00:42:21 +0000", "source" : "Tweet Button", "field11" : "20年くらい前の話かと思った。ひっくり返った。 [ Windows 8のC++でプログラミングの常識がひっくり返った http://t.co/7bagFJXZIs ]", "field12" : "http://itpro.nikkeibp.co.jp/article/Watcher/20130331/467401/" }
{ "_id" : ObjectId("515e68ee81dc484aaf93e66b"), "tweet_id" : NumberLong("319601772056420352"), "in_reply_to_status_id" : "", "in_reply_to_user_id" : "", "retweeted_status_id" : "", "retweeted_status_user_id" : "", "timestamp" : "2013-04-04 00:06:15 +0000", "source" : "TweetDeck", "field11" : "そろそろGoogle pluserになるかぁー?" }
...

件数はどうかな。
db.tweets.find().length();

>15205

年月日でソート


じゃーちょっとソートを。古い順になりましたねー
db.tweets.find().sort({"timestamp":"desc"});

{ "_id" : ObjectId("515e68f081dc484aaf9421ca"), "tweet_id" : NumberLong("5473927171"), "in_reply_to_status_id" : "", "in_reply_to_user_id" : "", "retweeted_status_id" : "", "retweeted_status_user_id" : "", "timestamp" : "2009-11-06 08:16:59 +0000", "source" : "web", "text" : "仕事だ" }
{ "_id" : ObjectId("515e68f081dc484aaf9421c9"), "tweet_id" : NumberLong("5511509893"), "in_reply_to_status_id" : "", "in_reply_to_user_id" : "", "retweeted_status_id" : "", "retweeted_status_user_id" : "", "timestamp" : "2009-11-07 18:12:52 +0000", "source" : "web", "text" : "眠い" }
{ "_id" : ObjectId("515e68f081dc484aaf9421c8"), "tweet_id" : NumberLong("5511523599"), "in_reply_to_status_id" : "", "in_reply_to_user_id" : "", "retweeted_status_id" : "", "retweeted_status_user_id" : "", "timestamp" : "2009-11-07 18:13:33 +0000", "source" : "web", "text" : "Androidの青空文庫ビューアいい加減にリリースしないといけないがバージョン互換の確認とDBの再定義が面倒臭すぎて死にそう。" }
{ "_id" : ObjectId("515e68f081dc484aaf9421c7"), "tweet_id" : NumberLong("5552112211"), "in_reply_to_status_id" : "", "in_reply_to_user_id" : "", "retweeted_status_id" : "", "retweeted_status_user_id" : "", "timestamp" : "2009-11-09 05:40:16 +0000", "source" : "web", "text" : "なんだこりゃ" }
{ "_id" : ObjectId("515e68f081dc484aaf9421c6"), "tweet_id" : NumberLong("5552376671"), "in_reply_to_status_id" : "", "in_reply_to_user_id" : "", "retweeted_status_id" : "", "retweeted_status_user_id" : "", "timestamp" : "2009-11-09 05:57:45 +0000", "source" : "web", "text" : "とりあえずフォローしまくればいいんだな。きっと" }
...

キーワードで抽出


じゃーキーワードで。findの所に検索する正規表現をぶっこめば抽出出来る。
db.tweets.find({"text":/java/})

{ "_id" : ObjectId("515e68ee81dc484aaf93e873"), "tweet_id" : NumberLong("312226191383871488"), "in_reply_to_status_id" : "", "in_reply_to_user_id" : "", "retweeted_status_id" : "", "retweeted_status_user_id" : "", "timestamp" : "2013-03-14 15:38:19 +0000", "source" : "web", "text" : "interfaceのデフォルト実装がコンパイル通らなかった。なんで。java8ェ・・・" }
{ "_id" : ObjectId("515e68ee81dc484aaf93e8c4"), "tweet_id" : NumberLong("311691694364041216"), "in_reply_to_status_id" : "", "in_reply_to_user_id" : "", "retweeted_status_id" : "", "retweeted_status_user_id" : "", "timestamp" : "2013-03-13 04:14:25 +0000", "source" : "web", "text" : "RTをふぁぼはあったが遂にjava8追加情報は無かった・・・。" }
{ "_id" : ObjectId("515e68ee81dc484aaf93e8c9"), "tweet_id" : NumberLong("311632352789004288"), "in_reply_to_status_id" : "", "in_reply_to_user_id" : "", "retweeted_status_id" : "", "retweeted_status_user_id" : "", "timestamp" : "2013-03-13 00:18:37 +0000", "source" : "web", "text" : "Java8とJava7の新機能使ったコード集書いてみた。Java8ってもっといっぱいある気がするけど誰かおせーて [ java8_java7.java https://t.co/Tzlc7BtuRR ]", "expanded_urls" : "https://gist.github.com/sys1yagi/5141091" }
{ "_id" : ObjectId("515e68ee81dc484aaf93e8f8"), "tweet_id" : NumberLong("311334806694793217"), "in_reply_to_status_id" : "", "in_reply_to_user_id" : "", "retweeted_status_id" : "", "retweeted_status_user_id" : "", "timestamp" : "2013-03-12 04:36:16 +0000", "source" : "web", "text" : "そういやjava7辺りでjavaコントロールパネルで出来たっぽい気がしてきた。Macなら環境設定のその他の\\", "expanded_urls" : "java\\", "field9" : "な" }
{ "_id" : ObjectId("515e68ee81dc484aaf93e8fa"), "tweet_id" : NumberLong("311333756243943425"), "in_reply_to_status_id" : "", "in_reply_to_user_id" : "", "retweeted_status_id" : "", "retweeted_status_user_id" : "", "timestamp" : "2013-03-12 04:32:06 +0000", "source" : "web", "text" : "target=jsr14でjava8をAndroidで使うよ( ー`дー´)キリッ" }
{ "_id" : ObjectId("515e68ee81dc484aaf93eaec"), "tweet_id" : NumberLong("306254125945982977"), "in_reply_to_status_id" : "", "in_reply_to_user_id" : "", "retweeted_status_id" : "", "retweeted_status_user_id" : "", "timestamp" : "2013-02-26 04:07:28 +0000", "source" : "web", "text" : "Rubyのcaseってjavaのswitchだよね(^q^)" }
...

ふむふむじゃぁあのキーワードで。。。



やめろ!Twitter始めたてでノリが分からなくて入学したての大学生みたいな寒い感じのツイートをしていた過去を掘り返すのはやめろ!


まぁ自分だけのアレなんでええんちゃいますか。MongoDBに突っ込めばキーワードで抽出したりソートとか簡単にできますねー。まぁMySQLもそうですけど。色々遊べそうですよね。一個考えたのは年月日指定するとそのタイムラインを再現する奴ですね。その日時のつぶやきがリアルタイムで更新されていくと。一人だとアレですけどユーザ多かったら「あーこういう感じのTLだったなぁーほげー」となるかもしれませんね。でもデータでかいし管理めんどいんでやりません。

0 件のコメント:

コメントを投稿