Hatena::Groupmkdir

作業ログとかをメモする場所。

2012-09-07

Passenger+ApacheでSinatraやRailsで作ったアプリを動かす

| 15:24

なんだかPassengerSinatraRailsで作ったアプリを動かす設定をしたりしてあげたりすることがおおいのだけど、その都度調べ直してることが多かったのでまとめます。

環境はUbuntu 12.04を標準とします。

Passengerインストール

passengergemインストール

$ sudo gem install passenger 

これだけだとApacheで動かせないので、モジュールインストールする。

$ sudo passenger-install-apache2-module 

指示にしたがってEnterを押していくと、「必要なライブラリ類が足りないからインストールしろ」と言われたり*1モジュールビルドが始まったりする。ライブラリ類をインストールしろと言われたら言われた通りのパッケージをaptとかで入れる。きのう新しく立てたUbuntuのホストでやったら以下のパッケージが必要だった。

libcurl4-openssl-dev libssl-dev zlib1g-dev apache2-prefork-dev libapr1-dev libaprutil1-dev

モジュールビルドが終わるとApacheの設定方法が出てくる。DebianUbuntuのお作法では、モジュールのロード用のファイルと設定用のファイルを分けてmods-availableディレクトリにそれぞれ保存して、a2enmodで有効化する(mods-enabledディレクトリからシンボリックリンクを張る)みたいなのでそれに従う。

まずpassenger.load

$ sudo vim /etc/apache2/mods-available/passenger.load
LoadModule passenger_module /var/lib/gems/1.9.1/gems/passenger-3.0.17/ext/apache2/mod_passenger.so

そしてpassenger.conf

$ sudo vim /etc/apache2/mods-available/passenger.conf
PassengerRoot /var/lib/gems/1.9.1/gems/passenger-3.0.17
PassengerRuby /usr/bin/ruby1.9.1

終わったらa2enmodで有効化してapache再起動する。

$ a2enmod passenger
$ sudo service apache2 restart

サイトごとの設定

たとえば、 http://hogehoge.死ぬ.jp/*2アクセスした場合にSinatraRailsで書いた(Rackの)アプリケーションが表示されるようにしたいとする。

アプリケーション本体はどこに置いてもいいのだけど、僕はわりと/var/www/サイト名 みたいなところに置いている。普通 /var/www はdefaultのサイトのDocumentRootとして It Works! みたいなHTMLが置いてあるのだけど、これは /var/www/default に移している。今回は /var/www/hogehoge というディレクトリにいろいろ置く。

var/
  +-- wwww/
  |     +-- default/
  |           +-- index.html
  +-- hogehoge/
  |     +-- public/
  |     +-- tmp/
  |     +-- logs/
  |     +-- vendor/
  |     |     +-- bundle/
  |     ...
  ...

DebianUbuntuの場合、モジュールと同じようにVirtualHostごとに設定ファイルをsites-availableディレクトリに入れてa2ensiteで有効化するという手順を踏むのがお作法っぽい。そこでとりあえず sites-available/hogehoge というファイルに hogehoge.死ぬ.jp での設定を記述する。

$ sudo vim /etc/apache2/sites-available/hogehoge
<VirtualHost *:80>
  ServerAdmin hogehoge@example.com
  ServerName hogehoge.xn--s9j219o.jp
  DocumentRoot /var/www/hogehoge/public
  CustomLog /var/log/apache2/access-hogehoge.log combined
  ErrorLog  /var/log/apache2/error-hogehoge.log
  LogLevel warn
  SetEnv GEM_HOME /var/www/hogehoge/vendor/bundle/ruby/1.9.1
  <Directory /var/www/hogehoge/public>
    Options -Multiviews
    AllowOverride All
    Order allow,deny
    Allow from all
  </Directory>
</VirtualHost>

ここでのポイントは、

$ bundle install --path vendor/bundle
$ GEM_HOME=vendor/bundle gem install your-gem

あと、特定のディレクトリの下で(たとえば http://hogehoge.死ぬ.jp/myapp/)でRackアプリを動かしたい場合は<VirtualHost>内に以下のようにRackBaseURIを追加する。

RackBaseURI /myapp

設定が終わったらapache再起動する。これでちゃんとアプリが動けばOK。bundlerを使っている場合はbundler自身がGemfileに書かれていないとGEM_HOMEにインストールされておらずrequireできなかったりするので注意。

*1:英語で

*2日本語ドメインなので、設定するときは http://hogehoge.xn--s9j219o.jp/ というURLを使う

2011-04-17

本名っぽい文字列を作る

| 13:04

この前「ネットで本名っぽい名前を名乗っておけば勝手に実名だと思ってもらえる」という話題になったときに、そういう名前を適当に名乗りまくったら面白いだろうなーと思ってそれっぽいシステムを作った。これを使ってTwitterのプロフィールとかを表記するようにしておけば、お人好しの県議会議員なんかがFollowしてくれるかもしれない。このシステムは便宜上、吉田海斗システムと呼ばれている。

2010-04-17 13:26追記

@tilyさんに教えてもらったruby-faker-japaneseが似たようなことするのに便利そう https://github.com/tily/ruby-faker-japanese

材料を揃える

苗字と名前のデータはWebからもってくる。「日本の苗字ランキング」みたいなサイトはあちこちにあるんだけど、クロールするのに向いていない構造だったり、JISコードにない文字が画像だったりして向かないサイトが多かった。結局、苗字は名字データベースから、名前は404 Not Foundからそれぞれクローラーを走らせて取得した。この2つのサイトはそれぞれ、漢字表記とひらがな表記の両方が載っていて、あとでいろいろいじくるのに適していた。ちなみに、「日本人のような名前」は詳しい説明がないのだけど、雰囲気的には読み方のランキングをもとに人名漢字を組み合わせているみたいで、漢字表記としては実在しないっぽい名前が多い。おもしろいのでそのまま使っている。

他には、こんなサイトを検討した

日本の苗字7000傑
IEじゃないと表示できないみたいなので諦めた。(TDC利用)
姓・名字・苗字の全国ランキング
IEじゃないと利用できないみたいなので諦めた。(TDC利用)
全国の苗字(名字)11万種
一覧が出せなさそうなので諦めた。
日本人の名字
JISコード外の文字が画像で表示されていて諦めた。
|| Not Found ||
クローラーは作ったけど読みが載っていないので使うのをやめた
同姓同名辞典(同姓同名検索・苗字ランキング・名前ランキング)
読みが載っていないので使わなかった

TDCを使用しているサイトに関してはIEを使うのがダルくてさいしょからあきらめていたけど、内部で使われているCSVファイルをダウンロードしていけるかもしれない、というのにさっき気づいた。

クローラーのコードをここに書いてもいいのだけど、それをするくらいならクロール済みデータを渡したほうが役に立ちそうだし、でもそういう再配布行為はさすがに怒られそうなのでここではやらないことにしておく。クローラーはいつもどおりMechanizeで作って、それをSequel経由でSQLite3に入力するようになっている。

データの修正

苗字を取得したサイトでは、JISコード外の文字を「(はしご高)」のように表現してあったので、それを手作業で修正する必要があった。そこで、クロールするときに「(」を含むものについてはflagというカラムに1を、そうでないものには0をそれぞれ入力するようにしておいて、flagが1になっているものについてはあとで修正して使うようなかたちにした。

修正用のスクリプトはこんな感じになった。

#coding:utf-8
require 'kconv'
require 'sequel'

new_kanji = {}
DB = Sequel.sqlite('yoshida.db')
DB[:last].filter(:flag=>1).all.each do |r|
  r.each_pair do |k,v|
    puts "#{k} : #{v}"
  end
  puts "================"
  old_name = r[:name]
  new_name = old_name.gsub(/([^)]*)/) {|m| new_kanji[m]?new_kanji[m]:m}
  skip_flag = false
  if old_name != new_name
    puts "-> \"#{new_name}\""
    puts "edit? [y/n]"
    while true
      if ARGF.gets.match(/([yn])/i)
        if $1 == 'y'
          break
        else
          DB[:last].filter(:id=>r[:id]).update(:name=>new_name,:flag=>0)
          skip_flag = true
          puts "updated"
          break
        end
      end
    end
  end
  next if skip_flag

  puts "input new name"
  input = ARGF.gets.toutf8.gsub(/\s/,'')
  if input.length == 0
    puts "skip"
  else
    puts "================"
    puts "Are You hure? : #{input}"
    while true
      if ARGF.gets.match(/([yn])/i)
        if $1 == 'y'
          DB[:last].filter(:id=>r[:id]).update(:name=>input,:flag=>0)
          after = input.split('')
          before = old_name.gsub(/(([^)]*))/,',\\1,').split(',').map!{|m|m.match(/(([^)]*))/) ? m : m.split('')}.flatten
          before.each_index do |i|

            if before[i].match(/(([^)]*))/)
              new_kanji[before[i]] = after[i]
              puts "#{before[i]} => #{after[i]}"
            end
          end
          puts "updated"
          break
        else
          puts "skip"
          break
        end
      end
    end
  end
  puts "================"
end

実行するとおかしいところのあるデータを表示してくれるので、入力できるやつは入力する。一度たとえば「(はしご高)」を「髙」に変更して入力すると以後「(はしご高)」を「髙」に変換するのを勝手にやってくれる。べんり。

名前をつくる

データベースからランダムに値を抜き出す方法はなにかないか考えたときに、いちばん楽でそこそこ速いのはそれぞれのレコード整数値でIDを振っておいて、乱数でそのIDのレコードを引き出すことだろうと思った。Sequelでは検索の結果が配列になるので、そこからsampleメソッドでランダムに貰っても良かったのだけど、メモリ確保にものすごく時間がかかるのでやめた。このへんはもっと賢い方法があるのかもしれない。

名前のデータに時々変な文字が入っていたりしたので、ひらがなカタカナ漢字でないものは外すようにした。変なモノがきたら再検索するようになってるけど、変なモノが来る確率は非常に低いので問題ない。

#coding:UTF-8
require 'sequel'
require 'romankana'

module YoshidaKaito
  DB = Sequel.sqlite(File.dirname(__FILE__) + '/../db/yoshida.db')

  def YoshidaKaito.choice type 
    biggest = DB[type].reverse_order(:id).first[:id]
    while true
      last_name = DB[type].filter(:id => (biggest*rand).floor).first
      if last_name && last_name[:flag] == 0 && last_name[:name] =~ /^[ぁ-んァ-ヴ一-龠]*$/
        return last_name
      end
    end 
  end 

  def YoshidaKaito.first
    YoshidaKaito.choice :first
  end 

  def YoshidaKaito.last
    YoshidaKaito.choice :last
  end

  def YoshidaKaito.full
    {:first=>YoshidaKaito.first,:last=>YoshidaKaito.last}
  end
end

これで、 YoshidaKaito.fullとかすれば、

{:first=>{:id=>664372, :name=>"祥暁", :yomi=>"やすあき", :flag=>0}, :last=>{:id=>39891, :name=>"峯川", :yomi=>"みねかわ", :flag=>0}}

みたいなデータが貰える。便利。

データを整形する

実名でTwitterを使ってる人でも、表記は漢字だったりひらがなだったりローマ字だったりカタカナだったりする。そこで、名前を作ったうえでいろんな表記に変形するようにした。ローマ字やカタカナ変換へのには、このあいだ作ったromankanaを使う(このために作った)。

表記方法は、Procオブジェクト配列で表現して、苗字と名前のデータを引数としてランダムに選んだProcオブジェクトをcallすることで適当な表記に変更した文字列を貰えるようにする。

module YoshidaKaito

  MM = [
    [
      Proc.new {|f,l| "#{l[:name]} #{f[:name]}"},
      Proc.new {|f,l| "#{l[:name]}#{f[:name]}"},
    ],
    [
      Proc.new {|f,l| "#{l[:yomi].to_hiragana} #{f[:yomi].to_hiragana}"},
      Proc.new {|f,l| "#{l[:yomi].to_hiragana}#{f[:yomi].to_hiragana}"},
    ],
    [
      Proc.new {|f,l| "#{l[:yomi].to_katakana} #{f[:yomi].to_katakana}"},
      Proc.new {|f,l| "#{l[:yomi].to_katakana}#{f[:yomi].to_katakana}"},
      Proc.new {|f,l| "#{f[:yomi].to_katakana} #{l[:yomi].to_katakana}"},
      Proc.new {|f,l| "#{f[:yomi].to_katakana}#{l[:yomi].to_katakana}"},
    ],
    [
      Proc.new {|f,l| "#{l[:yomi].to_roman} #{f[:yomi].to_roman}"},
      Proc.new {|f,l| "#{l[:yomi].to_roman} #{f[:yomi].to_roman.upcase}"},
      Proc.new {|f,l| "#{l[:yomi].to_roman.upcase} #{f[:yomi].to_roman.upcase}"},
      Proc.new {|f,l| "#{l[:yomi].to_roman.capitalize} #{f[:yomi].to_roman.capitalize}"},
      Proc.new {|f,l| "#{l[:yomi].to_roman.upcase} #{f[:yomi].to_roman.capitalize}"},
      Proc.new {|f,l| "#{l[:yomi].to_roman.capitalize} #{f[:yomi].to_roman.upcase}"},
      Proc.new {|l,f| "#{l[:yomi].to_roman} #{f[:yomi].to_roman}"},
      Proc.new {|l,f| "#{l[:yomi].to_roman} #{f[:yomi].to_roman.upcase}"},
      Proc.new {|l,f| "#{l[:yomi].to_roman.upcase} #{f[:yomi].to_roman.upcase}"},
      Proc.new {|l,f| "#{l[:yomi].to_roman.capitalize} #{f[:yomi].to_roman.capitalize}"},
      Proc.new {|l,f| "#{l[:yomi].to_roman.upcase} #{f[:yomi].to_roman.capitalize}"},
      Proc.new {|l,f| "#{l[:yomi].to_roman.capitalize} #{f[:yomi].to_roman.upcase}"},
    ],
  ]
  def YoshidaKaito.all names=YoshidaKaito.full
    "#{names[:last][:name]} #{names[:first][:name]}#{names[:last][:yomi]} #{names[:first][:yomi]}"
  end

  def YoshidaKaito.normal names=YoshidaKaito.full
    "#{names[:last][:name]} #{names[:first][:name]}"
  end

  def YoshidaKaito.random names=YoshidaKaito.full
        MM.sample.sample.call(names[:first],names[:last])
  end
end

これで、YoshidaKaito.full でふつうの漢字表記、YoshidaKaito.normal でカッコで読み方を併記した表記、YoshidaKaito.random でいろんな表記がランダムで出るようになった。

Twitterで使う


#coding:utf-8
$LOAD_PATH << File.dirname(__FILE__) 
require 'lib/yoshida.rb'
require 'twitter'

Twitter.configure do |config|
  config.consumer_key = 'hogehoge'
  config.consumer_secret = 'fugafuga'
  config.oauth_token = 'mogemoge'
  config.oauth_token_secret = 'piyopiyo'
end
client = Twitter::Client.new

names = YoshidaKaito.full

name_for_twitter = YoshidaKaito.random(names)
name_with_kana = YoshidaKaito.all(names)
puts name_with_kana
client.update_profile(:name=> name_for_twitter)

このスクリプトを定期的に走らせることで、Twitterの名前がどんどん変わっていく。たのしい。

かな併記の表記が出力されるようになっているのは、これを別のスクリプトにパイプして、そこでは別のTwitterアカウント標準入力文字列をpostするという処理になっている。こうして、あるアカウントで名前変更のログがひたすら発言されている状態になっている。こういうスクリプトがあると便利だよという話は、d:id:shokai から聞いた。

mixiの名前を変更する

facebookでやると確実に許されないので、僕の中でオワコンと化している(まりもの水替えしかしていない)mixiの名前を変えまくるスクリプトを作った。

#coding:utf-8
$LOAD_PATH << File.dirname(__FILE__) 
require 'lib/yoshida.rb'
require 'mechanize'
require 'kconv'

names = YoshidaKaito.full
nick = "#{names[:last][:name]}#{names[:first][:name]}"

agent = Mechanize.new
agent.max_history = 1 

agent.get('http://mixi.jp/')
agent.page.form('login_form').field('email').value = 'dankogai@example.com'
agent.page.form('login_form').field('password').value = 'kogaidan'

agent.page.form('login_form').submit

agent.get('http://mixi.jp/edit_profile.pl')
agent.page.form('regForm').field('last_name').value = names[:last][:name]
agent.page.form('regForm').field('first_name').value = names[:first][:name]
agent.page.form('regForm').field('nickname').value = nick
agent.page.form('regForm').submit
agent.page.forms.first.submit

ログインしたあと、本当ならmetaタグリフレッシュにしたがってホーム画面に移動してリンクをクリックして……ということをやるのが普通だと思うのだけど、mixiの場合はURL直打ちで移動したほうが面倒が少ない。名前を変えまくることの利点としては、誰だかわから名前だと「だれだっけ」と思われて何度もクリックされたりするみたいで、あしあとが増えます。おもしろいですね。

2011-04-07

ローマ字/ひらがな/かたかなを変換するgemを作った

| 16:58

絶対あるだろうと思ってたらgemにはどうもなさそうで、Web上で見つかるのはなんか古そうでエンコードまわりで破滅する感じなので作った。gemの作り方はshokaiさんのこれを参考にした(この間スキーに行ったときにgem作ろうぜっていう話になって教えてもらった)。

とりあえず、文字列に.to_hiraganaとかつけるとローマ字含めてひらがなにしてくれたりするのでそこそこ便利。文字コードも勝手に変換してくれる設定になってる(はず)。ruby 1.8でも1.9でもたぶん動く。

newgemでrspecを使う話

newgemでrspecを使う場合、gemrspecを入れると2.5.0が入ってしまうのでバージョンを1.3.1にしないとnewgemが動かない。テスト駆動開発をまともにやったことがないのでrspecについて全然知らないのだけど、rspec2系列だとrequire 'spec'じゃなくてrequire 'rspec'じゃないとno such file to load だと言われてしまうっぽい……。このへんはちゃんと勉強したい。

gem install rspec --version '=1.3.1'

2011-02-13

神奈中バスの時刻表取得

| 16:25

以前にDBに保存するために作っていたものを整理して公開した。Ruby+Mechanizeで動く。神奈中バスのダイヤ案内から指定されたバス停の時刻表を取得して返す。

https://github.com/ymrl/kanachu

Mechanizeの文字化け対策

| 16:25

前からMechanizeで色々書いていて、ちょくちょく文字化け問題に直面することがあった。どうもiconvが文字コード設定を見誤るとか、 文字コード宣言より前にマルチバイト文字があると死ぬとか、そういうのが原因らしい。その都度対処するのはめんどくさいのでもうMechanizeで使う文字コードは全部UTF-8に変換してしまうのがいちばん手っ取り早いっぽい。

mechanizeオブジェクトはpre_connect_hooksとpost_connect_hooksに接続前と接続後の処理が書けるので、接続後に受信したデータの文字コードUTF-8に変換し、接続前に送信するデータの文字コードをもとの文字コードに戻すような処理を加える。

    @agent =  Mechanize.new
    @encode = nil
    # 受信するテキストをすべてUTF-8にする
    @agent.post_connect_hooks << Proc.new do |prm|
      if prm[:response]["Content-Type"] =~ /^text\/.*$/
        @encode = Kconv.guess(prm[:response_body])
        prm[:response_body] = Kconv.kconv(prm[:response_body],Kconv::UTF8,Kconv::AUTO)
        prm[:response]["content-Type"] = "text/html; charset=utf-8"
        prm[:response_body].gsub!(/<meta[^>]*>/i) do |m|
          m.gsub(/S(?:hift)?[-|_]JIS|EUC[-_]JP|Windows-31J/i,"UTF-8")
        end
      end
    end

    # 送信するテキストをUTF-8からもとの文字コードに戻す
    @agent.pre_connect_hooks << Proc.new do |prm|
      if @encode
        np = []
        prm[:params].each do |m|
          np.push URI.encode(Kconv::kconv(URI.decode(m),@encode))
        end
        prm[:params] = np
      end
    end

わりとこれでどうにかなる。

2010-12-16

Homebrew + rvmな環境でインストールしたRubyのirbで日本語が入力できない

| 16:00

irbで上下キーが使えたのでreadlineが動いている!とか思っていたけどぬか喜びだった。

Homebrewでreadlineインストールして、 brew link readline してrvm install 1.9.2 しなおしたら動くかと思ったらエラーがでたので、brew unlink readline したのちrvmのほうでreadlineインストールした

$ rvm package install readline
$ rvm install 1.9.2 -C --with-readline-dir=$HOME/.rvm/usr

参考 : http://d.hatena.ne.jp/rochefort/20100907/p1

AndresAndres2012/10/14 21:43If you're reading this, you're all set, panrder!

ckwrqhockwrqho2012/10/15 12:14QD77Ek <a href="http://tvblguafzyiz.com/">tvblguafzyiz</a>

umgqfbyzvumgqfbyzv2012/10/15 22:07OZdzo5 , [url=http://ikdbcbdxbfnc.com/]ikdbcbdxbfnc[/url], [link=http://zogczhjzkbqq.com/]zogczhjzkbqq[/link], http://jgxulecpylid.com/

uvqpokmuvqpokm2012/10/16 23:591fQbC8 <a href="http://mfrmkspngaci.com/">mfrmkspngaci</a>