‘Ruby’ タグのついている投稿

スーパーボット大戦

2008 年 12 月 25 日 木曜日

Twitter / ahokaiという、自分の過去の発言を収集し、n-gramモデルとマルコフ連鎖を用いて分析・発言するプログラムを作りました。いわゆるボットという奴です。
方法については「圧縮新聞」を作った - phaのニート日記が詳しいです。


こちらからahokaiの全ソースコードをダウンロードできます。
shokai / bot-ahokai / overview — bitbucket.org
詳しい作り方やいきさつはbot ahokaiの作成 - 橋本詳解に書いたのですが、
今回はahokaiの様な「自分の発言を再構成して喋るbot」を設置する方法を書きます。
botがたくさん喋ってくれればクリスマスも寂しくないし、師走で忙しくても平気です。みんなで実在の人物を元にしたボットを設置して裏twitterを作り、最終戦争終結後もボット同士が喋り合いスーパーボット大戦しましょう。


主な機能は

  • あるユーザの過去の発言を3000件程度取得する
  • 発言データを再構成する
  • 再構成して発言する
  • followerにランダムに話しかける
  • followしてきたユーザをfollowし返す
  • 言及してきた発言をtwitter searchで検索し、favotiteし返す
です。

(続きを読む…)

MT3.2からWordPress2.6に移行した時の不具合をWWW::Mechanizeで修正

2008 年 12 月 19 日 金曜日

今年の春にSFCで動かしていたMovableType3.2が調子悪くなって、さくらに置いたWordPressに移行したわけだけど(さくらレンタルサーバ+wordpressにしてみた
その時に過去ページのいくつかが見えなくなる不具合があった。んで今朝からそれを解消するスクリプトを動かしている。今1000件目あたり。昼過ぎには完了するはず。

どういう不具合かというと、wordpressにログインしていない人がアクセスしたページは404ステータスになる。apacheが吐く404ではなくwordpressが吐く404で、「エラー 404 - 見つかりませんでした」と出る。ログインしていると普通に記事を見る事が出来る。
このせいで、俺は見れているのにURLを送ると「記事がないですよ」とみんなに言われて、原因を特定するのに時間がかかった。

そしてその修正方法だが、単に個別記事編集画面で「保存」を押すだけでその記事は復活した。なぜだかわからない。
さくらのコントロールパネルからMySQLのテーブルを見ても、どのテーブルが悪さしているのかよく分からなかったのでRubyのWWW::Mechanizeを使って全ページ「保存」ボタンを押しなおす事にした。

WWW::Mechanizeはログインフォームにパスワードを入れたり、UserAgentを偽装してwebページにアクセスしたりできる非常に有用なモジュール。Perlでは使った事あったけどRubyでは今回が初。

参考




必要なものをインストール

sudo gem install mechanize hpricot



普通にwp-login.phpからログインしてdashboardへ行き、cookieを持った状態で各記事個別ページへ移動して保存ボタンを押していく。記事個別ページが連番で助かった。あと自分のサーバをいじめたくないのでsleepは長めにしてある

shokai-blogfix.rb として保存

#!/usr/bin/env ruby
require ’rubygems’
require ’kconv’
require ’mechanize’
require ’pp’
user = ”username”
pass = ”password”

puts ’start’

agent = WWW::Mechanize.new
page = agent.get(”http://shokai.org/blog/wp-login.php”)
login_form = page.forms_with(”loginform”).first
login_form.log = user
login_form.pwd = pass
dashboard = agent.submit(login_form)

puts ”login: ” + dashboard.title

# 各ページ処理
for i in 1..1558
  sleep 10
  begin
    page = agent.get(”http://shokai.org/blog/wp-admin/post.php?action=edit&post=#{i}”)
    post_form = page.forms_with(”post”).first
    page = agent.submit(post_form)

    puts i.to_s+”success!” if page.body =~ /投稿が更新されました/
  rescue
    puts i.to_s+”error”
  end
end

puts ’–finished–’

パスワードなどは適宜変更。
URLもハードコーディングしている。1回しか使わないスクリプトだし

gtalk-sendコマンド

2008 年 10 月 22 日 水曜日

重い処理が終わったらgoogle talkで通知する - 橋本詳解
が便利で、サーバーでの処理が終わったらIM通知するのに使っている。

IMはポップアップして音も鳴るので、Terminalを睨んでなくても良くなる
google talkで重い処理が終わったら通知


(サーバーで使う物と、自分が普段使うのとでGoogleアカウントが2つ必要)

(続きを読む…)

chumbyでLDR未読数を表示するwidget

2008 年 5 月 18 日 日曜日

LDR unread countがchumby.comのwidgetとして公開された。

Livedoor Readerのアカウント名を設定すると、未読数が表示されて焦らされるが、1万超えたあたりからどうでもよくなった。

アップロードしたwidgetは基本privateで、publicにした後審査が入ってwidgetsの検索画面から他のユーザに使ってもらえるようになる。今回はだいたい4時間ぐらいで審査が通ったみたい。

DSCF2199

ダウンロード → SourceCode(Flash AS2 / CS3)

(うおお、前の記事書いてから1週間たってる・・・)

前のTwitter widgetより前に作って投稿してあったものなんだけど、これは数字が出るだけなので、Flash的にも手抜き。

Flashは手抜きだけど、chumby widget固有のノウハウである

1.クロスドメイン許可されていない外部のサーバーのデータを読みに行く

2.chumby.comにデータを保存できる設定画面を作る

3.chumby.comに保存したデータをwidgetから読み込む

という事をやってみたので、まとめておこう。

■1.クロスドメイン許可されていない外部のサーバーのデータを読みに行く

Livedoor Readerの未読数を取得するAPIを使う。

パラメータにuser=shokaishokaiなどすると未読数が出てくる。

特にXMLとかではないので、AS2の場合LoadVarsを使えば良い。

ローカルドメインで動かす分にはこれで構わないが、セキュリティの事もあってサーバーに置いてあるFlashは別のサーバーにあるデータを読みに行く事ができないようになっている。そのため、rpc.reader.livedoor.comを読みに行けない。前のtwitterで使ったXMLクラスなんかは例外。

そのため、大学院棟のサーバーにchumby.comからのアクセスを許可するcrossdomain.xmlを置いて、RubyでproxyするCGIを置いてそれをchumby上のFlashから読むようにした。

crossdomain.xml











*.chumby.com からのアクセスのみ許可すればよい

このサーバーにRuby適当に作ったproxyを置いて、Flashから読ませる。

これで外部ドメインのデータを読めるようになった。

■2.chumby.comにデータを保存できる設定画面を作る

widgetの管理画面で、設定値を入力するダイアログを出すようにした。

データはchumby.comに保存され、自作widgetがchumby上でロードされたしばらく後に設定値が_root直下の変数として現れるという仕様らしい。

LDR unread count customize

この辺のドキュメントを参考にした。

サイズは320×240 pixelで、Sample Parameter Example(AS2)に同梱されてた com.Chumby.WidgetParams.as を使うのが楽らしいので作ってみた

configure.fla

まあ今回はユーザ名を保存するだけなのでサンプルを削っただけなんだけど、送信が終わったらgetURLでjavascriptを呼んで自分自身を閉じたりとかしていて面白い。

■3.chumby.comに保存したデータをwidgetから読み込む

chumby.comに保存されたデータは、widgetが画面に読み込まれてからしばらくたってから、_rootに現れる。

どうやら毎回読み込みに行っているらしい。

値が_rootに現れるまで待たないと、undefinedになってしまうので待つ。

this.onEnterFrame = function(){

    if(username == null) return; // 待ちます

    ldr_loadUnread(username);

    delete this.onEnterFrame

}

こんな感じで、chumby+サーバーサイドができました。

GoogleEarth上でFlickrの位置情報付き写真を見れるサービスを作った

2008 年 5 月 8 日 木曜日

blogを書けなくて(MTが再構築できなくて)もう1ヶ月ぐらい前に作ったものになるけど、春休みにRuby on Railsのお勉強をしていて、その最終成果物としてGoogleEarth上でFlickrのGeoTag付き写真を見れるサービスを作ったのでまとめておく。

ついでに、緯度経度でFlickrの写真を検索できるAPIも公開してある。

screen-capture.png

■実装方法

1.Flickrのタグ検索で、geotagが付いている写真を定期的にクロールしてきて、写真のURL、緯度経度をRailsのDBに溜める(photo modelに溜める)

2.GETパラメータで緯度経度をRailsに渡して、photo modelを検索する

3.検索結果をGoogleEarthで表示できるように、kml出力のViewを作る

4.GoogleEarthで視点が変わる毎にサーバーに問い合わせて、返ってきたkmlを表示するkmlを作る

5.mongrelでRailsを動かして、apache2のmod_proxy_balancerでRailsのURLをサブディレクトリに見せかける

こんな感じで。一番時間がかかったのが1のクローラの実装で、これは俺がRuby初心者すぎて2日ぐらいかかった。あとは、デンマーク行きの飛行機と帰りの空港からのバスでやったので5時間ぐらい。この辺は、データを溜めて、クライアントからのリクエストに基づいてフォーマットを決めて吐き出す部分が最初からできてるRailsのおかげ。

■1.geotag付き写真クローラ

まずFlickrはタグ検索ができるけど、数値じゃなくて「〜〜を含む文字列」としてしか検索できないので、例えば「北緯35度東経135度周辺の写真が欲しい」という時は使えない。なので、あらかじめメタデータを集めておいて、数値として範囲を指定して検索できるようにしておく。

geotagは小数点ありの値(geo:lat=35.2341など)なんだけど、普通にgeo:latやgeo:lonを含む写真一覧をFlickr APIから検索しても小数点無しのタグしか手に入らない。

小数点ありのタグを手に入れるために、Net::FlickrでFlickrAPIからタグ検索して、

Net::Flickrで全public photoからタグ検索 - shokaiの日記

“geotagged”タグを含む写真をリストアップして、それから再度photo_idを使ってFlickrAPIのphotos.getInfoメソッドを使ってtagのraw attributeを見なければならなかった。

s.h.log » RubyでFlickr APIのphotos.getInfoを呼ぶ

クローラ自体はRailsで実装されていなくて、ActiveRecordだけを使ってcronで1時間毎に実行されている。

s.h.log » RailsなしでActiveRecordだけ使って、SQLiteを読み書きする

SQLiteのファイルがRailsと共有されているだけ。今回は、Rails側からの書き込みが無いので特に問題は起こらないだろうと判断した。

いまのところ10万枚の写真が集まった。SQLiteのファイルが欲しい人はあげます。

あと、クローラは連続でHTTPアクセスするとFlickrに悪い気がするので、適当にsleepを入れてみてるんだけど、そうすると実行に結構時間がかかる。これが、クローラを作って動かして直して作って…と繰り返す時に面倒だったが、和田さんに言われてscreenをデタッチ/アタッチするという技を覚えた。デタッチしておくと、sshを切断してもクローラが止まらなくて、アタッチしたら普通に続きからできる(もしくは結果が出てる)screenはクローラ作ってるときに重要。

■2.緯度経度をGETパラメータでRailsに渡して検索する

photos.kml?lat=35&lon=135&size=thumb

みたいな形式で、パラメータを渡してDBに溜めた写真を検索する。

KMLを全部出すと多すぎたので、緯度経度を指定してその周囲の写真だけ出す - shokaiの日記

paramsで受け取れる。パラメータ名はシンボルになっているので注意。

controllerで、ActiveRecord::Baseのfind_by_sqlメソッドで普通に検索。(この方法だと四角い。PostgreSQLやMySQLの空間型を使えば丸くできるらしいがとりあえずSQLiteでやる)

そしてmodelの配列をViewに渡してやる。

■3.検索結果をGoogleEarthで表示できるように、kml出力のViewを作る

kml形式のviewを作る。views/photos/index.html.erb を参考にして、index.kml.erbを作ればok

mime-typeにkmlを追加しておく必要があった。

flickrのgeotagついてる写真を収集してたので、kml出力するようにした - shokaiの日記

結果は巨大なkmlになるので、Rails標準搭載のWEBrickだときつい。でかいときはhttpサーバーをmongrelに換える。

MongrelでRailsをデプロイ - shokaiの日記

apache2のサブディレクトリをmongrelで起動してるrailsにプロキシする - shokaiの日記

■4.GoogleEarthで移動する毎に検索するkmlを作る

GoogleEarthの表示を作っているkmlは、静的な表示だけでなく、NetworkLinkタグで別のKMLの内容をURLを指定して読み込める。さらにViewRefleshModeをonStopにすると、画面の移動が止まった時に再読み込みできる。

再読込時に、視点の緯度経度を[lookatLat][lookatLon]で取得できるので、

lat=[lookatLat]&lon=[lookatLon]

としてNetworkLinkで読み込むkmlのURLの後ろにくっつけてやる事で、GETパラメータになる。

GoogleEarthで表示している場所の緯度経度から検索する - shokaiの日記

■5.mongrelでRailsを動かして、apache2のサブディレクトリに見えるようにする

デプロイ。

4まではMac OSX上でやって、ここからは大学院棟に置いたUbuntuに移し替えた。RailsをSQLite+Mongrel環境で動かして、URLをapacheで隠すようにすると、Railsプロジェクトのディレクトリをそのまま移し替えるだけでほぼ動くので超楽。

Flickrのgeotag写真を検索する - shokaiの日記

apache2のサブディレクトリをmongrelで起動してるrailsにプロキシする - shokaiの日記

MongrelでRailsをデプロイ - shokaiの日記

Mongrel入れる - shokaiの日記

———–

こんな感じで、クローラの実装と、バックエンドのサービスとしてAPIを吐くためのRails、というのを覚えた。

今まで戦略的に「長い時間をかけたインタラクション」をするモノや、「データを共有した複数のモノと人とのインタラクション」を作る事を避けていたんだけど(ややこしくてプロトタイピングできないものは作らない方針なので)

Rails使うとサクサクとこのぐらいまではいけるようになったので、自分の得意なフィジカルなデバイス作成と合体させて何か新しい物を作る予定。

RailsにFlashからpostしたり取り出したりする

2008 年 3 月 29 日 土曜日

Railsのscaffoldでさくっと作ったインタフェースって、HTTP-POSTからSQLに保存したり取り出したりもできて色々プロトタイプを作るのに便利なのでまとめておく。

ためたデータの管理もできるし。

■Rails側の準備

まずrailsを入れる。

Macの人は既に入っているはず。Windowsなら、ActiveScriptRubyを入れてパス通すと、gemコマンドが入るのでgem install railsすればrailsコマンドが使えるようになる。DBはSQLiteのdll版をc:\windows\system32に入れればできる様になるはず。

railsでtestbbsというプロジェクトを作って、そこにscaffoldでentry(投稿)というmodelを作る。entryには、name(投稿者)とmessage(本文)が入る。

rails testbbs

cd testbbs

ruby script/generate scaffold entry name:string message:text

rake db:migrate

(単数形に注意)

webサーバーを立ち上げる

ruby script/server

http://localhost:3000/entriesから見れるようになっている。はず。(複数形に注意)

こんなページができている。ここからentryを増やしたり消したりもできる。

post from flash to rails

ここまでで、http://localhost:3000/entries/newから投稿できるのと、http://localhost:3000/entries.xmlから一覧を取得できるようになった。

Source Code (Ruby on Rails 2.0.2)

■Flashから投稿する

HTTP POSTで行う。

今回作ったもの(動きます)

Source Code (AS3 / Flash CS3 + Flashdevelop)

今回もFlashDevelop上でAS3書いて、FlashCS3オーサリング/コンパイルした。

こいつでPOSTすると、http://localhost:3000/entriesに結果が出るようになる。

post from flash to rails

どうやってるかというとまず

http://localhost:3000/entries/newを見ると

  

    Name        

  

    Message

       

  

       

という風にPOSTしているので、entry[name]とentry[message]をパラメータで渡せばいいとわかる。

(続きを読む…)

RubyでFlickr APIのphotos.getInfoを呼ぶ

2008 年 3 月 17 日 月曜日

Net::Flickrをインストールしたら、flickr.photos.getInfoメソッドが実装されてなかったので良い機会なので実装してみた。

flickr apiをrubyで使う - replore的日記がとても参考になった。

FlickrAPIは普通にRESTなので、REXMLで取得してparseする。

今回自分で実装したのはgeotagまわりのため。FlickrのAPIの仕様でtagは小数点やハイフンが消されるので、その元の値を取るのにgetInfoメソッドが必要になる。Tagオブジェクトのrawからアクセスできるようにした。

使い方はphoto_idをコンストラクタに渡す。

info = PhotoInfo.new(photo_id)

info.tags.each{ |tag|

  puts tag.raw

}

flickr-photos-getInfo.rb

require ’open-uri’

require ’rexml/document’

require ’cgi’

FLICKR_API_KEY = ’your-api-key’

def flickr_call(method_name, arg_map={}.freeze)

  args = arg_map.collect {|k,v| CGI.escape(k) << '=' << CGI.escape(v)}.join('&')

  url = ”http://www.flickr.com/services/rest/?api_key=%s&method=%s&%s” %

    [FLICKR_API_KEY, method_name, args]

  doc = REXML::Document.new(open(url).read)

end

class PhotoInfo

  @xml_doc

  def initialize(photo_id)

    @xml_doc = flickr_call(’flickr.photos.getInfo’, ’photo_id’ => photo_id)

  end

  def tags

    tag_list = Array.new

    REXML::XPath.each(@xml_doc, ’//tag’){ |tag|

      tag_list << Tag.new(

                          ’id’ => REXML::XPath.first(tag,’attribute::id’),

                          ’author’ => REXML::XPath.first(tag,’attribute::author’),

                          ’raw’ => REXML::XPath.first(tag,’attribute::raw’),

                          ’tag’ => tag.text

                          )

    }

    return tag_list

  end

end

class Tag

  def initialize(args)

    @id = args['id']

    @author = args['author']

    @raw = args['raw']

    @tag = args['tag']

  end

  

  def id

    @id

  end

  

  def author

    @author.to_s

  end

  

  def raw

    @raw.to_s

  end

  

  def tag

    @tag.to_s

  end

  

  def is_machine

    if @tag =~ /.+:.+=.+/

      return true

    else

      return false

    end

  end

  

  def is_geo

    if @tag =~ /geo:.+=.+/

      return true

    else

      return false

    end

    

  end

end