2008/10/02

Twitter人工無脳: @dabesa & @AngraMainyu

以前北海道開発オフの中で作成した、マルコフ連鎖を使用して日本語タイムラインから投稿文を生成してポストする人工無脳、@dabesa
しばらく放置されていたのですが、Rubyの勉強を始めたのを機に一念発起、Ruby化、DB化しました。最初に僕の半年分のTwitter Archiveを食わせ、後は定期的に僕のTwitter Archiveから更新分の投稿を取得して形態素解析しDBに投入。投稿は二階のマルコフ連鎖です。つまり@dabesaが投稿する要素は全て僕のTwitterへの投稿です。
Special Thanks to @showyou & @ha_ma.

(0)形態素テーブル作成。

create table dabesable (
id int not null auto_increment primary key,
surface text not null,
nextword text);

(1)テキストファイルを解析して形態素テーブルに登録するスクリプト。

#!/usr/bin/ruby -Ku

require 'MeCab'
require 'mysql'

begin
c = MeCab::Tagger.new(ARGV.join(" "))
mysql = Mysql::new('localhost','USERNAME','PASSWORD','DATABASENAME')

dfile = open("data.txt",'r')
dfile.each do |sentence|
sentence.chop!
n = c.parseToNode(sentence)
n = n.next
# 行頭単語の登録
res = mysql.prepare("select id,nextword from dabesable where id=1")
res.execute
newword = res.fetch[1].to_s + "<>" + n.surface
res = mysql.query("update dabesable set nextword='#{Mysql::quote newword}' where id=1")

while n do
surfword = n.surface
n = n.next
if n then
nextword = n.surface
else
nextword = "EOL"
end

res = mysql.prepare("select id,surface,nextword from dabesable where surface='#{Mysql::quote surfword}'")
res.execute

if res.num_rows == 0 then
# 単語が登録されていない場合には新規登録する
res_insert = mysql.query("insert into dabesable values(NULL,'#{Mysql::quote surfword}','#{Mysql::quote nextword}')")

else
# 単語が登録されている場合にはnextwordを追加する
qid,qsurf,qword = res.fetch
newword = qword.to_s + "<>" + nextword.to_s
res_update = mysql.query("update dabesable set nextword='#{Mysql::quote newword}' where id='#{qid}'")
end
end
end
mysql.close
dfile.close

rescue
print "RuntimeError: ", $!, "\n";
end

(2)タイムラインを解析して形態素テーブルに登録するスクリプト。

#! /usr/bin/ruby -Ku

require'rubygems'
gem 'twitter4r'
require 'twitter'
require 'time'
require 'pit'

require 'MeCab'
require 'mysql'
require 'kconv'

begin
# 前回の最新投稿のIDを取得
flg = 0
sid = nil
idfile = open("id.txt",'r')
idfile.each do |id| sid = id.chop end
idfile.close

c = MeCab::Tagger.new(ARGV.join(" "))
mysql = Mysql::new('localhost','USERNAME','PASSWORD','DATABASENAME')

config = Pit.get("dabesa")
cl = Twitter::Client.new(config)

# smokeymonkeyのArchiveを取得
timeline = cl.timeline_for(:user, :id=>'smokeymonkey') do |status|
# 前回取得より新しいデータがあれば最新のIDをファイルに保存
if flg == 0 and status.id.to_i >= sid.to_i then
idfile = open("id.txt",'w')
idfile.puts status.id.to_i
idfile.close
flg = 1
end

# 前回取得より新しいデータがなければ終了
if status.id.to_i <= sid.to_i then
mysql.close
exit
end

post = Kconv.kconv("#{status.text}",Kconv::UTF8)
post.chop!
n = c.parseToNode(post)
n = n.next
# 行頭単語の登録
res = mysql.prepare("select id,nextword from dabesable where id=1")
res.execute
newword = res.fetch[1].to_s + "<>" + n.surface
res = mysql.query("update dabesable set nextword='#{Mysql::quote newword}' where id=1")

while n do
surfword = n.surface
n = n.next
if n then
nextword = n.surface
else
nextword = "EOL"
end

res = mysql.prepare("select id,surface,nextword from dabesable where surface='#{Mysql::quote surfword}'")
res.execute
if res.num_rows == 0 then
# 単語が登録されていない場合には新規登録する
#res_insert = mysql.query("insert into dabesable values(NULL,'#{Mysql::quote surfword}','#{Mysql::quote nextword}')")
else
# 単語が登録されている場合にはnextwordを追加する
qid,qsurf,qword = res.fetch
newword = qword.to_s + "<>" + nextword.to_s
#res_update = mysql.query("update dabesable set nextword='#{Mysql::quote newword}' where id='#{qid}'")
end
end
end
mysql.close

rescue
print "RuntimeError: ", $!, "\n";
end

(3)形態素テーブルから文章を生成してTwitterに投稿するスクリプト。

#! /usr/bin/ruby -Ku

require 'rubygems'
gem 'twitter4r'
require 'time'
require 'twitter'
require 'pit'

require 'mysql'

begin
mysql = Mysql::new('localhost','USERNAME','PASSWORD','DATABASENAME')
res = mysql.prepare("select id,nextword from dabesable where id=1")
res.execute
nextwords = res.fetch[1].to_s.split(/<>/)
i = nextwords.size
nword = nextwords[rand(i)]
postline = nword

res = mysql.prepare("select surface,nextword from dabesable where surface='#{Mysql::quote nword}'")
res.execute

while res.num_rows != 0 and nword != "EOL" do
nextwords = res.fetch[1].to_s.split(/<>/)
i = nextwords.size
nword = nextwords[rand(i)]
if nword == nil then nword = "EOL" end
postline = postline.to_s + nword.to_s
res = mysql.prepare("select surface,nextword from dabesable where surface='#{Mysql::quote nword}'")
res.execute
end

postline.gsub!(/@/,'.@')
mess = postline.gsub(/EOL/,'')

config = Pit.get("dabesa")
cl = Twitter::Client.new(config)
cl.status(:post,mess)
mysql.close

rescue
print "RuntimeError: ", $!, "\n";
end


このタイムライン解析スクリプトと投稿スクリプトをcronで回してます。しばらくは試験期間中。

なお、同じ構造を使って、僕のFriendsタイムラインから投稿を取得する@AngraMainyuという人工無脳も作りました。コレは無秩序に投稿を取得し続けるので、最初はDBの形態素量も少ないのですが、徐々に成長すればいいなぁと思っています。