固定ハッシュタグ複数定点観測用追跡器作りました

[Groovy][Twitter] 固定ハッシュタグ複数定点観測用追跡器作りました。

#devsumi とか、#jawsug とか、ずっと追跡し続けたい固定ハッシュタグは、いくつかあるものです。
自分のPDAバイスそのものでハッシュタグを追跡すると、途中を見落とししたり、電池切れで追えなくなったりします。
このような問題があるため、できれば追跡そのものは自宅のマシンで無人で行い、その結果を随時PDAで閲覧する方式の
方が便利が良いと思います。また、デブサミのような大規模なセミナーだと複数のハッシュタグを追わないと追いつかないケースもあり、
複数のハッシュタグを同時に記録し続けたいこともあります。
そんな目的のために複数固定ハッシュタグ追跡器作りました。
複数のハッシュタグを追跡し続けてログを付けます。


準備:
TAGS.txt という、事前に追跡しておきたい、お好みのハッシュタグテーブルを作ります、1件1行です。
実は、登録する単語は、ハッシュタグである必要はなく、また日本語でもOKです。TwitterはStream APIで検索する場合は、ハッシュタグのみが可能です、逆に通常の検索の場合は、日本語でも何でも探せますが、その代わり、即時性がありません、スピードと多様性はトレードオフですが、ここでは、記録性を優先するので、通常の検索で追っています、従って5分から10分程度の遅延を伴います。

動かし方: TAGS.txt と同じディレクトリーで HashTagChaser.groovyを動かします。
すると#jggug.log といった名前のログファイルが作られます。

この結果をPDAでコンファレンス会場などで閲覧すればOKです。
で、この結果を外からどうやって見るかですが、簡単なことですが単にDropboxディレクトリー内で動かすだけです。
巨人の肩の上に立つ訳です。
Dropboxの閲覧はPCでもiPhoneでも可能です。
Groovyをインスコするのはイヤという人のために、独立したjar版も作っておきました。
HashTagChaser.jar
こちらは、Javaだけあれば単独で動きます。


また、Dropboxのアカウントをお持ちでない人は、よかったらこちらからアカウント取得してください。
http://db.tt/8BiuAAb
招待ありでアカウントを作成すると、紹介者と招待された人の双方に、初期値の2Gに、プラスで250MB の容量追加が与えられます。招待者の私(nemo10)も250MBメリットがあります。

TAGS.txt ファイルの例 (単なる単語でもOK)

#jggug
#javareading
#devsumi
アイマス

それぞれのタグ名に応じて #jggug.log とか #javareading.log などのログが時々進んでいきます。

実行コード HashTagChaser.groovy 
>|groovy|
import static groovy.util.GroovyCollections.combinations
import groovy.util.Eval
import groovyx.gpars.GParsExecutorsPool

// 検索単語エンコード
String.metaClass.encodeIt = { ->
	return URLEncoder.encode(delegate,"UTF-8").replaceAll(/\.log/,'')
}
assert "ツイッター.log".encodeIt() == "%E3%83%84%E3%82%A4%E3%83%83%E3%82%BF%E3%83%BC"

// 発言番号を取得
String.metaClass.getNum = { ->
	if (delegate ==~/.*tag.*search.*/) {
		//println "発言番号取得 " + delegate.replaceAll(/.*:([0-9]+)/, '$1')
		return delegate.replaceAll(/.*:([0-9]+)/, '$1')
	} else {
		return delegate
	}
}
assert "tag:search.twitter.com,2005:16060673193".getNum() == "16060673193"

// 発言IDを取得
String.metaClass.getID = { ->
	if (delegate ==~/.*\(.*\)/) {
		//println "ID取得 " + delegate.replaceAll(/([^ ]+) \(.*/, '$1')
		return delegate.replaceAll(/([^ ]+) \(.*/, '$1')
	} else {
		return delegate
	}
}
assert "nemo_kaz (kazuo nemoto)".getID() == "nemo_kaz"

// 日本語チェック
String.metaClass.isKana = { ->
	if (delegate ==~/.*[あ-んア-ン].*/) {
		return "OK"
	} else {
		return "NG"
	}
}
assert "tag:search.twitter.com,2005:16060673193".getNum() == "16060673193"

//////////////////////////////////////

def loggers=[]
def file = new File("TAGS.txt")

file.eachLine{logname -> loggers.add (new Agent(logname+".log")) }

for (;;){
	try {
		loggers.each {clazz -> clazz.logIt() }
		Thread.sleep(1000*60*8)
	} catch (Exception e) { println "Exception"+e }
}

class Agent{
	String url
	File fhandle
	def counter
	def logname
	Agent(String input) {
		logname=input
		fhandle = new File(input)
		//fhandle.write("aaa\n")
	}
	def logIt() {
		def feed = null
		print " "
		def  atom2 = ("http://search.twitter.com/search.atom?q="+logname.encodeIt()).toURL().text
		feed = new XmlSlurper().parseText(atom2)
		feed.entry.collect{it}.reverse().each {
			if (counter < it.id.toString().getNum().toLong()) {
				counter = it.id.toString().getNum().toLong()
				if (it.title =~ /^RT/) {} else {
					println "■"+it.title +"  "+it.author[0].name.toString().getID() 
					fhandle.append("■"+it.title +"  "+it.author[0].name.toString().getID()+"\n" )
				} //RTは割愛
			} else {
				Thread.sleep(1000*5)
			}

		}//each
	}//logIt()
} //class Agent