狐に騙され覚書

あぁこれ便利だなってことを忘れてもいいように覚書。主にプログラミング言語主体ですよ。

Rails4 + AngularJS + paperclip で動的画像アップロード

やりたいこと

画像の横のあるファイル選択ダイアログからファイルを選んだら、すぐにファイルをサーバーにアップロードして、アップロードに成功したら現在の画像をすり替える、ということをやりたいです。

図にするとこんな感じです。

f:id:chase0213:20150324132912p:plain

環境

  • Rails4 をベースにしたアプリケーション
  • AngularJS 1系を JSフレームワークとして使用
  • paperclip を使用して、画像格納用のモデルを作成
  • angular-file-upload を使用して、クライアント <=> サーバー間通信を行う

概要

ざっくり言うと、上であげた「やりたいこと」は以下の 2つの要素に分解できます。

  • 画像を格納するモデルを作成する
  • 画像をアップロードする機能を追加する

混ぜて書くとわかりにくくなるので、それぞれ説明します。

画像を格納するモデルを作成する

まず、アップロードされた画像をサーバーに格納するためのモデルを作成します。 一から作るのは面倒なので、paperclip という gem を使用します。

インストール

  • Gemfile に以下の 1行を追加します。
gem 'paperclip'
  • bundle install します。
$ bundle install

モデル作成

  • 画像を格納するためのモデルとして、Pictureモデルを作成します。
$ rails g model Picture name:string
  • Pictureモデルに avater という名前で ファイル名やパスを格納するためのアタッチメントを追加します。
$ rails g paperclip picture avater

# 作成された migrationファイルを表示
$ cat db/migrate/yyyymmddHHMMSS_add_attachment_avater_to_pictures.rb
class AddAttachmentPhotoToPictures < ActiveRecord::Migration
  def self.up
    change_table :pictures do |t|
      t.attachment :avater
    end
  end

  def self.down
    remove_attachment :pictures, :avater
  end
end
  • db:migrate します。
$ bundle exec rake db:migrate

これで、Pictureモデルに avater が追加されました。 例えば、

picture = Picture.take
picture.avater.url

などとすると、画像の url を取得することができます。

画像をアップロードする機能を追加する

これで画像を格納するためのモデルが構築できたので、次は実際に View から angular 経由でサーバーにファイルをアップロードするところを実装します。

インストール

  • Gemfile に以下の行を追加します。
gem 'angularjs-file-upload-rails'
  • bundle install します。
$ bundle install
  • application.js に以下の行を追加します。ただし、 angularjs よりも後に ロードされなければなりません。
//= require angularjs-file-upload

実装

とりあえずコードを列挙した上で説明します。 ただし、コードは分かりやすさを重視しており、そのまま本番に埋め込むのは適切ではありません。

avater.html.erb

<div ng-contoller="avaterCtrl" ng-init="setAvater(#{@picture.id})">
  <img alt="avater" src="{{avater_url}}" />
  <input type="file" nv-file-select uploader="uploader"/>
</div>

avaterCtrl.js

var myApp = angular.module('avaterApp', []);

myApp.controller('avaterCtrl', ['$scope', function($scope) {
  // uploaderインスタンスを作成
  $scope.uploader = new FileUploader({
    // /foo/bar のところには ajax_api_controller.rb で後述する post_new_avaterメソッドへのパスを入れてください
    url: '/foo/bar',
    headers : {
      // Rails用
      'X-CSRF-TOKEN': $('meta[name=csrf-token]').attr('content')
    }
  });

  // uploader にファイルが追加された際に呼び出されるコールバック関数
  $scope.uploader.onAfterAddingFile = function(item) {
    item.upload();
  };

  // upload が成功したときに呼び出されるコールバック関数
  $scope.uploader.onSuccessItem = function(item, response, status, headers) {
    // Queue を空にする
    $scope.uploader.clearQueue();
    // アバターの url を更新
    $scope.avater_url = response.avater_url;
  };

  $scope.setAvater = function(picture_id) {
    // /hoge/fuga のところには get_avaterメソッドへのパスを入れてください
    $http.get('/hoge/fuga', {
      picture_id: picture_id
    }).success( (data) ->
      $scope.avater_url = data.avater_url;
    )
  };
}]);

ajax_api_controller.rb

  def get_avater
    picture = Picture.find(params[:picture_id])
    render json: { avater_url: picture.avater.url }
  end

  def post_new_avater
    picture = Picture.new()
    picture.avater = params[:file]
    if picture.save
      render json: { avater_url: picture.avater.url }
    else
      render text: 'failed to upload new avater'
    end
  end

解説

いくつか重要な点を説明します。

クライアント側の処理

まずは、uploaderインスタンスを宣言します。

avaterCtrl.js

// uploaderインスタンスを作成
$scope.uploader = new FileUploader({
  // /foo/bar にはアップロード処理に関するコントローラーのパスを指定します
  url: '/foo/bar',
});

uploader にはいくつかの コールバック関数 が定義されており、それらをオーバーライドすることでイベントに応じた処理を行います。

まず、ファイル選択ダイアログにて画像が選択されると、その画像は Queue(キュー)に追加されます。 そのタイミングで、自動的に画像のアップロードを行いたいので、onAfterAddingFile関数をオーバーライドしています。

avaterCtrl.js

// uploader にファイルが追加された際に呼び出されるコールバック関数
$scope.uploader.onAfterAddingFile = function(item) {
  item.upload();
};

次に、アップロードが成功したら既存の画像を差し替えます。

avaterCtrl.js

// upload が成功したときに呼び出されるコールバック関数
$scope.uploader.onSuccessItem = function(item, response, status, headers) {
  // Queue を空にする
  $scope.uploader.clearQueue();
  // アバターの url を更新
  $scope.avater_url = response.avater_url;
};

この際、Queue をクリアしないと一度アップロードしたファイルがずっと Queue に残り続け、ファイルを選択する度に複数ファイルをアップロードしてしまいます。 removeAfterUpload というプロパティが定義されているため、それを true にしても同じ効果が得られるはずです(未検証)。 今回は 1画像しか保持しない構成なので clearQueue しても問題ないですが、複数ファイルをアップロードする際などにはタイミング等々もう少し考える必要があります。

基本的にクライアント側の処理はこれだけなのですが、Railsアプリケーションの場合には X-CSRF-TOKEN をサーバーに送信する必要があります。 関連issue: https://github.com/nervgh/angular-file-upload/issues/40

なので、実際には uploaderインスタンスの宣言は以下のようになります。

avaterCtrl.js

// uploaderインスタンスを作成
$scope.uploader = new FileUploader({
  url: '/foo/bar',
  headers : {
    // Rails用
    'X-CSRF-TOKEN': $('meta[name=csrf-token]').attr('content')
  }
});

サーバー側の処理

まず、クライアント側ではページを読み込んだ際に avater への url を取得する必要があるため、その url を返却する api を定義します。

ajax_api_controller.rb

def get_avater
  picture = Picture.find(params[:picture_id])
  render json: { avater_url: picture.avater.url }
end

angular-file-upload を使用してファイルをアップロードした場合、params[:file] にそのファイルに関するパラメータが代入されて渡されます。 なので、サーバー側ではそれを使用して新規に Pictureインスタンスを作成します。

ajax_api_controller.rb

def post_new_avater
  picture = Picture.new()
  picture.avater = params[:file]
  if picture.save
    render json: { avater_url: picture.avater.url }
  else
    render text: 'failed to upload new avater'
  end
end

最後に、これらの apiメソッドに対して適宜 routing を行えば完了です。 routing に関しては他に素晴らしいドキュメントがいくつもあるので、ここでは割愛します。

まとめ

これで動的にアバター画像を変更することができるはずです。 普段は個人的な趣味嗜好で、Rails のテンプレートエンジンには slimテンプレートを使っており、 また、js は coffeescript で書いているので、本記事のコードには文法エラーがたぶんに組み込まれている可能性があります。 何か気になる点等あればご連絡いただけると幸いです。

Jubatus で記事連動広告を表示したい話

はじめに

そういえば今更ですが昨年の10月に Jubatus Hackathon #1 に参加してきました。

Jubatus ハッカソン - connpass

発表時のスライドはこちらに綺麗にまとめていただいているので、ご参考にどうぞ。

Jubatus Blog: Jubatusハッカソンを開催しました

1日で企画〜実装まで持っていったとは言え、色々雑だったなぁと反省しています。 なので少し補足をしようと思います。

作ったもの

ハッカソンの少し前に、国内最大級の広告イベントに行ってきました。それに見事に感化されて、じゃあ広告系のやつを作ろう!と同チームの後輩に言ったらすんなりOKもらったので、それを作りました。

具体的な仕様や想定は以下の通り:

  • いわゆる owned media(記事)を持っていて、それにマッチする広告を表示したい
  • マッチするとは、記事の内容と広告の内容が類似することをいう
  • (仕事上がりで結構つらいのであまり頭を使いたくない)

特に重要なのは一番下の条件で、重みが非常に大きいです。

少し真面目に理由付けをすると、迷ったらできるだけシンプルな構成をとるという自戒があります。 なぜならば、複雑なシステムはメンテナンスコストが増大するからです。 特に機械学習を用いる場合、システムにバグ(や嬉しくない挙動)があったとして、

などなど、考慮しないといけないところが多いので、もうこれ以上仕事したくないからです(真面目)。 基本的にエンジニアは楽をするために自動化をするので、仕事が増えたら本末転倒なわけです(?)。

ちなみにコードはそのままだとたぶん動かないですが、github に上がってます。

chase0213/jubatus-hackathon-01 · GitHub

私たちのスライドはこちらです。

Jubatus hackathon 1_hiyoshi

システム構築

github をご覧になった方はお気づきと思いますが、当初は Rails + Elasticsearch + Jubatus の 3サーバ構成をとろうとしていました。 ただ、Elasticsearch の部分が本質的ではなかったので棄却されました。 使いたかった。。。

リクエストの流れは大体以下の通りです。

  • Railsサーバ上で owned media(記事)が掲載されている
  • 広告は json形式(タイトルと広告の概要文を含む)で、jubatus上で学習
  • ユーザーは適当な検索キーワードを入力して、記事を検索する
  • Railsサーバは絞り込んだ記事の内容を POST でバックエンドの Jubatus(python + Django で受ける)に送信する
  • リクエストを受け取った Jubatusサーバは、recommender を使用して、記事の内容に近い広告記事の id を Railsサーバに返却
  • Railsサーバは Jubatusサーバから受け取った id を元に広告を取得し、ユーザーに表示

つまり結局のところ、recommender に突っ込んでるだけです。

それから、記事と広告の類似度を判定するところは、mecab + ipadic を使って形態素解析したもの Jubatus のベクトルコンバータを使って特徴量として保持しています。

データ変換 — Jubatus

このコンバータが非常に優秀なので特徴量をこね回すところはほとんど何もしていません。 なのでノイズまみれになってあまり精度が出ないのですが、この件に関しては後述します。

デモ

AWS上にデプロイしてデモをしました。 ただそれなりのスペックのマシンを使ったので、今は(金銭上の理由で)止めてしまっています。 適当にコードをいじったらローカルでも動くので適当にいじってください。

構想

学習という学習の部分は、広告を動的に追加できるところで、あまり学習器の旨味を出せませんでした。 普通、機械学習って言えば使われる度に賢くなるというところを想像する方が多いと思いますので、その部分について簡単に意見を書いておきます(コードは書きません、悪しからず)。

まず、広告系で学習器を使うと言えば、実際にクリックされたものとそうでないものとでインプレッション率に差を付けるところだと思います。 つまり、良くクリックされる記事と広告の組み合わせに対しては、インプレッションを多めにして、そうでないものに対しては気まぐれにたまに表示する、ということです。

実際には作っていないのですが、classifier(分類器)を使ってできそうだと思っています。
具体的には、検索システムで絞りこまれた記事の内容と、クリックされた広告の内容の UNION をとって、クリックされたというラベル付きで classifier に突っ込んでおきます。 同様にして、クリックされなかったものに対しても記事と広告の UNION をクリックされなかったというラベルつきで保存しておくのですが、ここで 1点注意が必要です。

あるリンクがクリックされなかったとしても、ユーザーがそれに対して興味がないと判断するのは早計である。
なぜならば、多くの場合ユーザーは、他の多くの選択肢を提示されており、
そのリンクに対して興味がないことを能動的に示したわけではないからである。

なんか引用っぽくしましたが、私見です。 裏を返せば、クリックしたという情報はクリックしなかったという情報よりも強く扱われるべきです。

というわけで、この辺りを統計的にうまいことチューニングして classfier に突っ込んで 2値分類させれば、ユーザーがその組み合わせでクリックしそうかどうかを予測できます。 その値を上段(recommender)に返して、係数として上手く反映させると、たくさんクリックされる組み合わせはより多く表示されるようになります。

終わりに

最後の段落は机上の空論なので、落とし穴はそこら中に散らばっていると思います。 それから、上の方で「ノイズまみれ」と表現しましたが、これはある程度許容すべきです。 なぜならば、全くノイズの無い広告はユーザを退屈させるからです(広告は専門外なのであまり深く言及することは避けます)。

Jubatus で facebook への不正ログインを検知したい話(1)

前回、 あまりにも有益でない classifier を作ったので、もう少し使えるものを作ります。

facebook のデータダウンロード

facebook で、右上(2014年7月現在)にある「▼」マークをクリックして、 「設定」の項目を表示すると、「一般アカウント設定」の下の方に「Facebookデータをダウンロード」というリンクがあります。 これをクリックして認証とかを色々すると、自分の今まで facebook にあげたデータが htm形式でダウンロードできます。 軽い気持ちでこれをダウンロードしてみたんですが、正直クリティカルな情報が多すぎて若干引きました。 こんなデータを使ってレコメンダーとか作ったら、さぞかし精度の高いものができるのだろうなというのを感じています。

security

それで、今回の話は、上でダウンロードしたデータの中に、「セキュリティ」という項目があり、 facebook上で誰がどこからこのアカウントにログインしたか、などがわかるので、 これを使って不正アクセスを見抜こうというものです。

使用するのは相変わらず Jubatus です。 異常値検知のための、 jubaanomaly という API が用意されているので、これを使います。

configration

Jubatus server の設定ファイルはこちらです。 https://github.com/chase0213/anomal_facebook_activity/blob/master/lof.json

基本的にサンプルそのままなので、特に説明することはないです。 サンプルをそのまま流用しても、ある程度使えるものが作れるのも Jubatus の良いところです。

今回は「Jubatus で〜 (1)」 なので、(2) を公開するときにはこの辺りのチューニングをちゃんとやっていると思います。

pre-processing

facebook からダウンロードしてきたデータは、htm形式でマークアップされているので、適宜変形します。 今回は、適当に pythonスクリプトを作成して変形しました(python 2.6)。 https://github.com/chase0213/anomal_facebook_activity/blob/master/data/trim.py

(string_rules とかに書いても良いのですが、もう少しちゃんと整形する必要があったので)

anomaly detection

データの準備ができたら、それを Jubatusサーバに渡して、異常度を計算してもらいます。

client = jubatus.Anomaly(HOST,PORT,NAME)

として Jubatusサーバを起動しておいて、

ret = client.add(datum)

としてデータを追加します。 datum は、jubatus.common.Datum形式のデータです。 そうすると、ret にはそのデータの id と異常度が返ってきます。 最初はこの client.add(Datum) の返り値を見て、ちゃんと 1.0 付近にデータが分布していることを見たほうが良いです。 その中で、1.0 から明らかに離れているものがあったら、そのデータを注視してみてください。 不正アクセスの可能性があります。

それで、ある程度データを Jubatusサーバに蓄積したら、 試しに普段と全然違うデータを与えてみます。

anomal_datum = Datum({
    "activity": "DELETE",
    "time":     "2014年7月15日 17:59 UTC+12",
    "ip_address": "127.0.0.1",
    "brawser": "IE6",
    "cookie": "???"
})

データを定義したら、Jubatusサーバに異常度を計算させます。 ここでは、データを登録せずに異常かどうかだけ見て欲しいので、client.add の代わりに client.calc_score を使います。

anomality = client.calc_score(anomal_datum)

calc_score は float値を返り値として持っているので、煮るなり焼くなり好きにしてください。

「普段のデータ」も見せられたら良かったのですが、自ら anomaly access を増やすことになるので割愛します。

まとめるとこんな感じになります。 https://github.com/chase0213/anomal_facebook_activity/blob/master/anomaly.py

それで、試しに実行してみた結果がこちらです。

$ python anomaly.py
anomality(anomal datum): 2.33819794655
anomality(nomal datum): 0.999999880791

2行目が上で定義した異常なデータを与えたもの、3行目が「普段のデータ」を与えたものです。 異常なデータに関して、異常度が明らかに大きくなっています。 たぶん、統計的な検定とか閾値とか設定してアラートをあげるようにしたら良いのだと思います。

以上、前回よりも少しだけ有益な Jubatus を使ってみる話でした。 次回はこれをもう少し真面目にチューニングしたいと思います。

余談ですが、「Jubatus Anomaly」で Google検索したときに、古い API用のページがヒットしている気がします。 バージョンによって API が上手く動かなかったりする(今回しました)ので、気をつけてください。

Jubatus で市区町村名から都道府県を予測してみる話

昔話

僕が機械学習を学び始めた頃は、そもそも github なんてものはなくて、 ネットに落ちてるソースを落として来てコンパイルして「あー、よくわかんないけど動かない・・・」 となるか、「あー、よくわかんないけど動いた・・・」となるかどちらかでした。 そうでなければ、自分で拙いコードを書いて、どこまで正しく実装できているかわからない (計算機科学的に効率の悪いコードだったことは間違いない)コードで実験していました。

ところが最近は、あまりに複雑な理論の実装であっても、たいていは github にあがっているのです。 そして github に公開されるということは、使い方が明示され、かつ誰でも使えるように 平易なインターフェースが容易してあります。

Jubatus はまさにそんなフレームワークで、複雑な理論を一切知らなくても機械学習ができてしまう夢の様なものです。 http://jubat.us/ja/

太鼓持ちはここまでにして。

Jubatus を使ってみた、という記事です。 まず何をしたいかというと、市町村名を入力したらどこの都道府県の地名かを教えてくれるものを作ります。 目的が「使ってみる」なので、対象ははっきり言って何でも良かったのですが、 都道府県名の住所録が こちら のサイトに csvファイルで提供されていたので使用させていただきました。

pre-processing

上でダウンロードしてきたデータを使わせていただくのですが、ちょっと文字コードSJIS で嫌なので、utf-8 に変換します。

wget http://jusyo.jp/downloads/new/csv/csv_zenkoku.zip
unzip csv_zenkoku.zip
nkf -w zenkoku.csv > zenkoku_utf-8.csv

これでデータが日本語として読めるようになったと思います。 ちなみに、Windows環境だとこの変換は不要ですが、今回は LinuxCentOS)を想定しています。 (そもそも jubatus を Windows で使おうとすると一筋縄ではいかないはずなので、この説明は不要と思いますが)

それで、一番最初の行が嫌(各列の説明)なので消してしまいます。

これで一応 jubatus に喰わせるデータは整ったのですが、このままだとデータの並びに規則性がありすぎて 何を言っても「北海道」と返す、北海道Lover が完成するだけなので、予め並びをシャッフルしておきます。

shuf zenkoku_utf-8.csv > shuffled_zenkoku.csv

これを data というディレクトリを切って保存しておきます。

configration

これでデータは整ったので、今度は Jubatus に喰わせるために json で設定を書きます。 https://github.com/chase0213/address_classifier/blob/master/adrs_clf.json

学習アルゴリズムには AROW を用います。 特に理由とかはないです。

それで、基本的に今のままだと入力ベクトルは文字列を要素として持つベクトルなので、 この文字列をどう扱うか、を string_rules の項目に書きます。 実用的なものを作ってみる企画ではないので、とりあえず unigram で分割した文字の個数を数えるだけにします。

"string_rules": [
      { "key": "*", "type": "unigram", "sample_weight": "bin", "global_weight": "bin" }
]

当然、実用的なものを作りたかったらこの部分はちゃんと考える必要があります。 (そもそも前処理の部分でほとんど何もしていないので実用的もなにもないのですが)

設定の詳細については Jubatus公式ページ を御覧ください。

starting jubatus server

設定が終わったら jubatusサーバを起動します。

$ jubaclassifier --configpath adrs_clf.json

エラーが出なかったら起動しています。

training

設定が終わったらいよいよ学習フェーズに入ります。 いわゆる調教です。 https://github.com/chase0213/address_classifier/blob/master/train.py

データ全部突っ込んで学習したらタイムアウトしたので、とりあえず 50,000件くらい与えています。

tnum = 50000

普通は学習用のデータと分類用のデータを別に格納します。 今回は面倒なので(

特に難しいことはしていないので、ここまで読んでいただいている方ならコードを見れば何をしているかわかると思います。 ので説明は割愛します。

一点だけ重要なのは、

# training data must be shuffled on online learning!
random.shuffle(train_data)

ここです。 サンプルそのまま流用しているのでご丁寧にコメントまで入っていますが、 教師データをシャッフルしないで渡すと、データの並びの影響が反映されます。 アルゴリズムをよく理解しているわけではないので詳しくはなんとも言えないですが、 たぶん最後の方で喰わせたデータの影響力が強くなるのではないかと思います。 今回の場合、もともとデータをシャッフルしているのでので、 ここでシャッフルしなくてもそこまで顕著に性能劣化することはないですが、再利用するときに忘れるとあれなので。

シャッフルしたら、学習開始です。

# run train
client.train(train_data)

classification

こちらも特に難しいことはないので、コードを見てください。 https://github.com/chase0213/address_classifier/blob/master/detect.py

今回は、「伊勢崎」「高崎」「鎌倉」という3つの地名を与えて、どこの都道府県でしょう!? というのをします。

結果はこちら。

$ python detect.py
群馬県 伊勢崎
群馬県 高崎
神奈川県 鎌倉

おぉ!!あってる!!すごい!!!

・・・・・・。

お手元の python で、50,000件の「都道府県-市区町村」の対を保存して、 これどこの都道府県でしょう?というものをやってみてください。 全体が 16万件くらいのはずなので、 1/3 くらいの確率で当たるはずです。

この例が賢くないことは始める前から分かっていたことなので良いのですが、 ただそれでも良い点があります。 それは、「未知のデータに対する分類能力」です。

classifier(ないしは機械学習)は元来、既知のデータを与えて未知のデータを予測する というのもなので、もし教師データとして与えられなかった地名に対しても、 予測する(とりあえずの答えを返す)ことができます。 python のみでこれをやろうとしたら、結構難しいはずです。

以上、jubaclassifier を使ってみる、でした。

python-twitter を chef でインストールする

「そういえば昔 pythontwitter api 叩いてデータ取ってきたなー。」

と思い、手元の環境に入れようとして下の doc に行き当たりました。

https://code.google.com/p/python-twitter/

 

「あー、そういえば色々手で入れなきゃで面倒くさかったなー、自動化しよう。」

ということで、chef で cookbook を作成しました。

https://github.com/chase0213/cookbook_python_twitter

 

python-twitter が以下のライブラリに依存しているようなので、まずはこちらからインストールします。

 

これを cookbook でするために、それぞれ cookbook を作成します。

サクッと作ってサクッと使いたかったので、コードの再利用性とかそこまで考えていないです。 ただし、最低限の冪等性は保持しようと思い、not_if でモジュールのチェックを行っています。

それで、cookbook_python_twitter の metadata.rb に

depends "cookbook_python_httplib2" ... 

などと書いて、 recipe/default.rb で

include_recipe "cookbook_python_httplib2" ...

などとしておけば完成です。 node定義とかで recipe[cookbook_python_twitter] を指定してあげれば無事にインストールできる(・・・はず)です。


ちなみに、あとから気づいたんですが、githubpython-twitter の README を見たら、pip でインストールできるみたいですね。 無駄な労力を・・・。

https://github.com/bear/python-twitter

NHK_ReqAnswer を作った話

どこかの記事でふと、NHKが番組APIを公開しているみたいな話を見て、なんとなしに何か作れないかなーっと思って作りました。

https://twitter.com/NHK_ReqAnswer

 

【使い方】

1. NHK_ReqAnswer に follow してもらいます。

2. 検索したい単語と、ハッシュタグ(#nhk_reqanswer)を付けてつぶやきます。

3. 10分に一度くらいの割合で、今の時刻から最も近い時間に始まる番組 3つがリプライされます。

 

こんな感じです。

実は 1. の部分は自動的に follow する仕組みを実装していないので、reqest 増えてきたらやらなきゃなーと思っているところではあるのですが、とりあえず今のところ手動です。

3. の部分は、NHK一般、教育、衛星の順に検索しに行きます。

もし教育テレビだけで探したければ、ツイートに -E と付けてください。

  • -G:NHK一般から検索
  • -E:NHK教育から検索
  • -S:NHK衛星から検索

例えば教育テレビから「将棋」というキーワードを持つ番組を検索したい場合、

将棋 -E #nhk_reqanswer

とすると、例えばこんな感じで返ってきます:

@_c_hase 将棋フォーカス「4五角戦法に対抗しよう」 (e1: 2014/02/09 10:00-10:30) 第63回 NHK杯テレビ将棋トーナメント「準々決勝・第2局」 (e1: 2014/02/09 10:30-12:00)

 

【システムの中身】

NHKAPIを使うと、当日分とその翌日分の番組表が json 形式で取得できるので、

その中からタイトルとサブタイトルにツイートの文字が含まれるかどうかを見て返しているだけです。

細かい部分でチューニングしたりしていますが、基本的に難しいことをしていなかった気がします。

ソースは全然リファクタリングとかしていなくって読みにくいこと必至ですけど、一応 github にあげていたりします。

https://github.com/chase0213/ReqAnswer

 

---

NHK番組表API

http://api-portal.nhk.or.jp/

python-twitter

https://github.com/bear/python-twitter

Chef Workstation のインストール

Opscode の公式サイトを見ながら CentOS に Workstation を入れたので覚書。

構成

基本的に Opscode が 言ってるとおり にインストールする。ただ、apt-get とかしているのでこの部分を yum に読み替えて実行していく。

rbenv の設定とかを github からとってくるらしいのでまず git を入れる

$ sudo yum install git

公式gitリポジトリから rbenv をクローンして、パスに追加する

$ git clone git://github.com/sstephenson/rbenv.git .rbenv
$ echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> .bash_profile
$ echo 'eval "$(rbenv init -)"' >> .bash_profile

.bash_profile を再読み込み

$ source ~/.bash_profile

ruby-build を github から色々してインストールする

$ git clone git://github.com/sstephenson/ruby-build.git
$ cd ruby-build
$ ./install.sh

目的のバージョンの Ruby をインストール

$ rbenv rehash
$ rbenv install 1.9.3-p385
$ rbenv shell   1.9.3-p385
$ rbenv global  1.9.3-p385

Chef のインストール

$ gem install chef
$ rbenv rehash

それで、 gem install chef を実行したらなんか怒られる。

$ gem install chef
ERROR:  Loading command: install (LoadError)
    cannot load such file -- zlib
ERROR:  While executing gem ... (NameError)
    uninitialized constant Gem::Commands::InstallCommand

zlib ないんだけど!って言われているので zlib を入れる。必要なら先に言ってよ、もう。

$ sudo yum install zlib-devel

で、もう一度 gem install chef しても同じことを言われる。色々調べたら、zlib -> ruby って順番でインストールしないといけない(パスの設定とか?)らしいので、ruby を一度削除して、もう一度入れなおす。

$ rm -rf ~/.rbenv/versions/1.9.3-p385
$ rbenv install 1.9.3-p385

これで gem install chef ってして、無事にインストールできました。