マックではSサイズを買え!

ゴールデンウィークまっただ中ということで、マクドナルドで食事をする人も多いと思いますが、そんな人に耳寄りな情報をご紹介。
ここ数日はとても暖かいのでついついマックシェイクでも頼んでみたくなりますよね?
そんなときにはMサイズ(220円)を1つ注文するのなら、Sサイズ(100円)を2つ注文した方がお得なのです。
通常はそのような価格設定はしないように思うのですが、この前重さを量ってみたら下のようになりました。

Sサイズ×2: 約392g(容器込みの2つ分の重さ) - 約20g(容器2つ分の重さ) = 372g


Mサイズ×1: 約315g(容器込みの1つ分の重さ) - 約20g(容器1つ分の重さ) = 295g


なんとSサイズ2つのほうが、Mサイズ1つより約80gも多いのです。
もちろん個体差はあると思うのですが、たぶんほとんどの場合Sサイズを2つ買った方が量は多いと思います。
それにSサイズを2つ買えば2つの味が楽しめるのもgood.
ということでマックシェイクを買うのならSサイズを2つ買うのが断然おすすめです。

Happy Golden Week!


スタバではグランデを買え! ―価格と生活の経済学
吉本 佳生
ダイヤモンド社
売り上げランキング: 1030
おすすめ度の平均: 4.0
4 経済をはじめて学ぶには丁度よいか
4 比較優位の考えが参考になった
4 価格の仕組みが分かります
5 多くの人に読んでもらいたいです
4 身近なところから実感できる経済学

新しいfollowerやremoveした人のBioやFollowing/Followersを表示する

以前新しいfollowerやremoveした人を表示するRubyスクリプトというものを書いた。
しかし最近よくわからない外人のfollowerが増えてきていることもあり、名前だけ表示するだけではなくBioやFollowing, Followers数などがわかれば、いちいちその人のページを確認する必要もないよね、ということでBioとFollwing, Followers数も表示するRubyスクリプトを書いた。


follotter.rb

require 'rubygems'
require 'mechanize'
require 'kconv'

class Follotter
  def initialize(user, pass)
    @agent = WWW::Mechanize.new
    @agent.user_agent_alias = 'Mac Safari'
    @agent.max_history = 1
    login_form = @agent.get('http://twitter.com/').forms.action('https://twitter.com/sessions').first
    login_form['username_or_email'] = user
    login_form['password'] = pass
    @agent.submit(login_form)
  end

  def get_followers(command = 'check')
    case command
    when 'update'
      File.open("followers.txt", "w") do |io|
        (1..lastpage_index).each do |n|
          io.puts((@agent.get("http://twitter.com/followers?page=#{n}")/"a.url").map {|i| i.inner_text})
        end
      end
    when 'check'
      followers = (1..lastpage_index).inject([]) {|memo, n|
        memo + (@agent.get("http://twitter.com/followers?page=#{n}")/"a.url").map {|fr| fr.inner_text}
      }
      older_followers = File.open("followers.txt", "r") {|io| io.readlines}.map {|l| l.chomp}
      (older_followers - followers).each do |i|
        info = get_followers_info(i)
        puts "-----------------------------------------"
        puts "#{i} has removed you."
        puts "Bio: #{info[0]}"
        #sjisでしか表示できない場合は info[0].tosjis のようにする
        puts "Following : Followers = #{info[1]} : #{info[2]}"
      end
      (followers - older_followers).each do |i|
        info = get_followers_info(i)
        puts "-----------------------------------------"
        puts "#{i} has started to follow you."
        puts "Bio: #{info[0]}"
        #sjisでしか表示できない場合は info[0].tosjis のようにする
        puts "Following : Followers = #{info[1]} : #{info[2]}"
      end
    else
      raise 'invalid arguments'
    end
  end

  private
  def lastpage_index
    num = (@agent.get('http://twitter.com/followers')/:h2).inner_text.match(/\d+/).to_s.to_i
    puts "Your #{num} Followers"
    (num%20 != 0) ? (num/20 + 1) : (num/20)
  end

  def get_followers_info(follower_name)
    user_page = @agent.get("http://twitter.com/#{follower_name}")
    bio = (user_page/"span.bio").inner_text
    following = (user_page/"ul.stats"/"li[1]/span").inner_text
    followers = (user_page/"ul.stats"/"li[2]/span").inner_text
    [bio, following, followers]
  end
end

if __FILE__ == $0
  user = 'username' #自分のusernameに変更
  pass = 'password' #自分のpasswordに変更
  Follotter.new(user, pass).get_followers(*ARGV)
end

基本的には前回自分が書いたものを、ujihisaが添削してくれた記事を大いに参考にしながら修正するようにした。
今まであまりArray#mapやArray#injectを使ってこなかったのだが、このように使えるんだと大変勉強になった。
あともう一つ気をつけたところは要素を指定する際にXPathをあまり使わないようにしたこと。
XPathは簡単に要素指定できるのですごく便利である一方、ページ構造に強く依存しているためページ構造の変化に弱いという欠点がある。そのあたりをタグ名やクラス名を指定するようにしてみた。
formも単純に何番目のフォームとするのではなくて、action名を元に決めることにした。(前回からログインフォームの位置が変わっていたりしたので)


Bioを表示する際に、Windowsコマンドプロンプトを使っている人は、日本語を表示するために出力する文字の文字コードsjisにしなくてはならない点に注意してください。
UTF-8で表示できる場合はプログラム冒頭の require 'kconv' は不要。


実行方法は前回と同じで

ruby follotter.rb update

で、比較対象となるfollowerをfollowers.txtに書き込み、

ruby follotter.rb

で、followers.txtとの差分を出力する。実行結果はたとえば下のようになる。


実行日時ごとにfollowersファイルを作成して、follower履歴みたいのをわかるようにした方がいいかなあ、と思いつつもそこまではやれてないです。

twitterの発言漏れをチェックするRubyスクリプト

ここ数日twitterでの発言漏れがひどいらしくて、自分の発言が特定のfollowerのtimelineに表示されないことがしばしば起こっている。
じゃあ一体誰のところで表示されていて、誰のところで表示されていないんだろうと思って、それを知るためのRubyスクリプトを書いた。あくまでも簡易的なものですが。要mechanize.
usernameとpasswordを変更するのを忘れずに。


4/22 3:15追記 重要
mechanizeの履歴を制限するのをすっかり忘れていました。
具体的には特に履歴が必要なければ @agent.max_history = 1 などのようにします。この設定をしないと(おそらく)すべてのページの履歴(Mechanize::Page)を記憶するので膨大なメモリを食います。
実際、僕の環境では最初のコードでは最大時で200MB以上のメモリを消費していました。上記のようにしただけで使用メモリは最大でも15MB以下に抑えられました。
ついついこの設定って忘れちゃんですよね。。。


4/22 3:55さらに追記
深夜に約200人をチェックした僕の場合で6分弱かかりました。かかる時間はチェックする人数に応じて大体これに比例すると考えられそうです。

require 'rubygems'
require 'mechanize'

class Chekker
  def initialize
    @agent = WWW::Mechanize.new
    @agent.user_agent_alias = 'Mac Safari'
    @agent.max_history = 1 #追記 重要!
    @username = 'username' #自分のusernameに変更
    @password = 'password' #自分のパスワードに変更
    login_form = @agent.get('http://twitter.com/').forms.first
    login_form['username_or_email'] = @username
    login_form['password'] = @password
    @agent.submit(login_form)
  end

  def check_post
    error_user = []
    product_set.each do |fl|
      begin
        friends_with = []
        friends_with = (@agent.get("http://twitter.com/#{fl}/with_friends")/"td.content"/:strong).map {|t| t.inner_text}
        error_user << fl if !friends_with.include?(@username)
      rescue WWW::Mechanize::ResponseCodeError => e
        case e.response_code
        when '404'
          next
        when '502'
          sleep 5
          retry
        end
      rescue Timeout::Error
        puts "WRYYY"
        sleep 5
        retry
      end
    end
    puts error_user
    if ARGV[0] == '-p'
      puts error_user.map {|u| '@' + u}.join(' ')
    end
  end

  private
  def lastpage_index(type)
    num = (@agent.get("http://twitter.com/#{type}")/:h2).inner_text.match(/\d+/).to_s.to_i
    (num%20) ? num/20 : num/20 + 1
  end

  def get_username(type)
    (1..lastpage_index(type)).inject([]) {|memo, i| memo + (@agent.get("http://twitter.com/#{type}?page=#{i}")/"a.url").map {|a| a.inner_text}}
  end

  def product_set
    get_username('followers') & get_username('friends')
  end
end

if __FILE__ == $0
  Chekker.new.check_post
end

やっていることは単純で、自分のfollowerかつ自分のfollowing(friend)であるユーザを求めて、彼らのページを個別に訪れ、with_othersに自分の発言が含まれているかをチェックしているだけ。つまり、with_othersには20件しか表示されない仕様なので、timelineの流れの速いfollowing(ものすごくたくさんの人をfollowしている人)のwith_othersには発言直後でも自分の発言が含まれないことがあることに注意してください。
自分の発言の30秒から1分後ぐらいにまわすのがいいかと。ただし、twitterのサイト状況によっては結構時間がかかったり、開始まもなくエラーが出るので気長にどうぞ。(早くても3分はかかるかも)
バグっているところや、コードに関するつっこみがあればよろしくお願いします。

使い方は

ruby chekker.rb -p

などとすればOK. -pオプションをつけると下のように、自分の発言が表示されていなかったユーザ一覧の最後に@つき空白区切りでユーザを並べます。-pオプションをつけなければ1行に1ユーザが表示されるだけです。

自分の発言に@をつけるとみんなに見えやすくなるというのも聞くので、twitterの中身はどうなっているか興味津々です。

日記のデザインを変えてみた

春だよ、新学期だよ!ということで日記のデザインをid:gomi-box女史の作ったものに変更してみた。
こういうデザインセンスって本当に素敵だよなあ。


自分が解決したいという問題を設定するのって難しい。研究テーマとか決まるのだろうか。
春ですよ、春。

ニコニコ動画の新着投稿動画をチェックし続けるRubyスクリプトVer2

こちらはログイン不要!
おととい書いた日記でsanadanさんから

非公式ですけど、
http://www.nicovideo.jp/newarrival?rss=atom
ならログインしなくても新着情報がとれるので、そっちを使った方がすっきりしますし、ニコニコ動画のサーバー負荷も少ないんじゃないかと思いますけどどうでしょう?

というコメントを頂いたので、そっちで書き直してみました。ログインが不要なのでニコニコ動画アカウントを持ってなくても利用できます!

nico_newarrival2.rb

require 'rubygems'
require 'hpricot'
require 'open-uri'
require 'date'
require 'kconv'

str = []
f = open(ARGV[0], "r")
while l = f.gets do
  str << l.chomp.toutf8
end
while true do
  targets = []
  newarrivals = Hpricot(open('http://www.nicovideo.jp/newarrival?rss=atom'))
  (newarrivals/:entry).each do |d|
    title = (d/:title).inner_text
    link = (d/:link)[0].attributes['href']
    str.each do |s|
      if Regexp.new(s) =~ title
        detail = Hpricot(open("http://www.nicovideo.jp/api/getthumbinfo/sm#{link.match(/\d+/).to_s}"))
        if (detail/:nicovideo_thumb_response)[0].attributes['status'] == "ok"
          status = "ok"
          first_retrieve = DateTime.strptime((detail/:first_retrieve).inner_text)
          length = (detail/:length).inner_text
          view_counter = (detail/:view_counter).inner_text
          comment_num = (detail/:comment_num).inner_text
        else
          status = "fail"
          first_retrieve = nil
          length = nil
          view_counter = nil
          comment_num = nil
        end
        targets << [link, status, title, first_retrieve, length, view_counter, comment_num]
      end
    end
  end
  if !targets.empty?
    targets.each do |t|
      if t[1] == "ok"
        puts("#{t[2]}\n
             #{t[3].strftime("%m/%d %H:%M:%S")}  length: #{t[4]}   view: #{t[5]}   comment: #{t[6]}\n
             #{t[0]}")
      else
        puts("#{t[2]} deleted!")
      end
      puts "\n"
    end
    puts "**************************************************************************"
  end
  sleep 30 #下の追記に書いた通り頻繁にアクセスする必要はありませんでした
end

3/5 2:00追記: 新着投稿動画一覧は10分ごとの更新のようなので、たまたま自分が見た30秒後に更新されることはありますが、頻繁にチェックする必要がまったくないようです。そのあたりは適当に調整してください。
koizukaさん、ご指摘ありがとうございました。


やはりログイン不要な分こちらの方がいいですね。link変数や30秒ごとにアクセスするなど、中身も微妙に変えてあります。Hpricotを入れてない人は gem install hpricot で。mechanizeを入れてあれば自動的に入っています。


実行方法などは前回と同様に、

ruby nico_newarrival2.rb hogelog.txt

でOKです。
クラスとか作らずにどぱーと書いてしまいました。相変わらずコードは汚いですね。。。


調べてみると、今回利用したような新着投稿動画情報をRSSフィードからとって来られるようなことは開発者の方のブログに書いてありました。知らなかったです。
新着動画のRSSを出してみた - こたにき
最近知りましたが、ニコニコ動画まとめwikiというのもあります。
http://nicowiki.com/
バグっているところや変なところがあれば教えてください。

ニコニコ動画の新着投稿動画をチェックし続けるRubyスクリプト

ニコニコ動画にアップされた最新の動画は新着投稿動画(http://www.nicovideo.jp/newarrival)でチェックできるわけだけど、自分で常にチェックするのは面倒だし、しかし目当ての動画がアップされたら諸事情で早急に見たい、ということがあるかと思います。
ということで、1分毎に目当ての新着投稿動画を動画タイトルをもとにチェックし、見つけたらコマンドライン上で知らせるRubyスクリプトを書いた。
3/5 2:00追記: ログイン不要なものをこちらに書きました。また新着投稿動画は10分毎に更新のようなので頻繁にアクセスする必要はまったくありませんでした。

mechanizeを入れていない場合は、gem install mechanize で。


nico_newarrival.rb

require 'rubygems'
require 'mechanize'
require 'open-uri'
require 'kconv'
require 'date'

class NicoNewarrival

  def initialize
    @agent = WWW::Mechanize.new
    @agent.user_agent_alias = 'Mac Safari'
    login_page = @agent.get('http://www.nicovideo.jp/').forms[0]
    login_page['mail'] = 'hoge@foo.com' #ここに自分のメールアドレスを入力
    login_page['password'] = 'password' #ここにパスワードを入力
    @agent.submit(login_page)
    @agent
  end

  def check_newarrival
    str = []
  #コマンドラインで指定したファイルを読み込む
    f = open(ARGV[0], "r")
    while l = f.gets do
      str << l.chomp.toutf8
    end
    #新着投稿動画をチェックし続ける
    while true do
      targets = []
      newarrival = @agent.get('http://www.nicovideo.jp/newarrival')
      (newarrival/"/html/body/div[3]/div/table[2]/tr").each do |r|
        (r/:td).each do |d|
          title = (d/"/div/div[2]/p/a").inner_text
          sm = (d/"/div/div[2]/p/a")[0].attributes['href'].match(/\d+/).to_s
          str.each do |s|
            if Regexp.new(s) =~ title
              doc = Hpricot(open("http://www.nicovideo.jp/api/getthumbinfo/sm#{sm}"))
              if (doc/:nicovideo_thumb_response)[0].attributes['status'] == "ok"
                status = "ok"
                first_retrieve = DateTime.strptime((doc/:first_retrieve).inner_text)
                length = (doc/:length).inner_text
                view_counter = (doc/:view_counter).inner_text
                comment_num = (doc/:comment_num).inner_text
              else
                status = "fail"
                first_retrieve = nil
                length = nil
                view_counter = nil
                comment_num = nil
              end
              targets << [sm, status, title, first_retrieve, length, view_counter, comment_num]
            end
          end
        end
      end
      #目的の動画が見つかったら表示
      if !targets.empty?
        targets.each do |t|
          if t[1] == "ok"
            puts("#{t[2]}\n
                 #{t[3].strftime("%m/%d %H:%M:%S")}  length: #{t[4]}   view: #{t[5]}   comment: #{t[6]}\n
                 http://www.nicovideo.jp/watch/sm#{t[0]}\n")
          else
            puts("#{t[2]} deleted!")
          end
          puts "\n"
        end
      puts "**************************************************************************"
      end
      sleep 60
    end
  end

end

if __FILE__ == $0
  NicoNewarrival.new.check_newarrival
end

Windowsコマンドプロンプトsjisしか表示できないので、タイトルを表示する際に文字コードの変換が必要。具体的にはタイトルを表示している2カ所を下のようにしておくことに注意。

puts("#{t[2].tosjis} ......)

使い方はまず適当なテキストファイルに、目当ての動画のタイトルの一部分など以下のように書く。

nicotitles.txt


(ス|す).*(マ|ま).*(ブ|ぶ).*(ラ|ら)

改行で区切る。正規表現が使えるので上みたいに書けば多少の表記揺れなどは吸収できるのがポイント。

ruby nico_newarrival.rb nicotitles.txt

のように引数に目当てのタイトルを書いたテキストファイルを指定して実行する。


自分のidとパスワードに変更するのを忘れずに。
あとニコニコ動画の認証の性質上、同一アカウントで複数の場所からログインができないので、このスクリプトを走らせつつニコニコ動画を見る人はもう一つ別のアカウントがあったほうが便利です。



これを実行するとCtrl-cなどでプログラムを強制的に止めるまで、下のような画面が延々と続く。


まだ十分にテストとかしていないのでバグとかもあると思いますが、見つけしだい修正したいと思います。

今年もシリコンバレーに行ってきます

再来週から一週間ほどシリコンバレー、サンフランシスコに行けることになった。
去年はJTPAシリコンバレーツアーに参加したのだが、今年はJTPAとは直接関係なく何人かの有志でスタンフォードやいくつかの企業を訪問する予定。
去年行くことのできなかったところにも行けそうなので非常に楽しみ。サンフランシスコ観光も去年はろくにできなかったのでいろいろと見てきたいなあ。

と、どうにかしてうるう年のレアな今日、ブログを更新したかったのでよかった。