2007-02-05
■ [ruby] RubyでHTMLとWebを操作するためのライブラリ、HpricotとWWW::Mechanize
今日は、RubyでWebサイトを解析するときに強い味方となるライブラリ、HpricotとWWW::Mechanizeを紹介します。
どちらも非常に強力なので、覚えておいて損はないよ!
以下ではまずHpricotでHTMLを解析・編集する方法について解説します。 次に、「はてなダイアリーの自動更新」を例にWWW::Mechanizeの使い方を解説します。
Hpricot
HpricotはHTMLを解析するためのライブラリです。
例えば「あるページのリンクだけを全部抜き出したい」と思ったとき、どうしますか?scrAPIを使う?でもscrAPIはやっぱり ちょっと使いたいだけなのにパーザ(Scrape)用のクラスを定義するのが面倒なんだよね!
Hpricotなら、たったこれだけで解析完了です。
require 'hpricot' require 'open-uri' doc = Hpricot( open("http://www.kmc.gr.jp/").read ) (doc/:a).each do |link| puts "#{link.inner_html} → #{link[:href]}" end
HTMLを読み込むには「Hpricot()」という関数にHTMLデータの文字列を与えればOK。 例ではOpenURIを使ってネットから取得しています。
タグを検索するには、読み込んだデータの「/」というメソッドにシンボルか文字列を渡します。
- tableタグを全て探す
- doc/:table
- <div id="hoge">を全て探す
- doc/"div#hoge"
- <span class="moge">を全て探す
- doc/"span.moge"
CSSに慣れている人なら気付いたと思いますが、2番目、3番目の書き方は「CSSセレクタ」と呼ばれるものです。 Hpricotでは他にもさまざまなCSSセレクタを 使用することができます。*1
- <table id="list">の中のtdタグを全て探す
- doc/"table#list td"
- "index.cgi?"から始まるリンクを全て探す
- doc/"a[@href^='index.cgi?']"
- テキストに"new"という文字列が入っているリンクを全て探す(※version 0.5以降)
- doc/"a[text()*='new']"
上の例はどれも「〜なタグを全て探す」でしたが、条件に当てはまるものを一つだけ取り出したいこともありますよね。 そんな時はdoc.at()が使えます。
- 一番最初のリンクを探す
- doc.at(:a)
ElementsとElem
タグ一覧はHpricot::Elementsのインスタンスになります。これはArrayのサブクラスなので、 firstで最初の要素を取り出したり、eachでループを回したり、find_allで条件に合うものだけ抜き出したりできます。
またそれぞれのタグはHpricot::Elemのインスタンスになります。
Elemには以下のようなメソッドが定義されています。
- attributes : 属性一覧
- ["href"] : 属性hrefの値を得る
- name : タグ名
- parent : 親要素
- containers : 子要素(タグのみ)
- children : 子要素(テキストやコメントも含む)
- css_path : その要素のCSSパス
- previous_sibling, next_sibling : 隣の要素(タグのみ)
- previous_node, next_node : 隣の要素(テキストやコメントも含む)
- each_child{|elem| ..} : 子要素について繰り返す
- inner_html, inner_text : 内部のHTML, 内部のテキスト
- to_html : そのタグも含めたHTML
- to_original_html : そのタグも含めたHTML(なるべく元のHTMLと同じように出力する)
- to_plain_text : 読みやすいテキストに整形する
実はElemやElementsにも「/」や「at」が定義されているので、「/」による検索結果に対し「その中をさらに探す」なんてこともできます。
例:
- divタグの中のリンクを全て探す
- doc/:div/:a
- 各divタグの中の最初のリンクを探す
(doc/:div).each do |div| first_link = div/:a end
HTMLに変更を加える
Hpricotの凄いところはHTMLへの変更もサポートしている点です。 linkをあるリンクタグだとすると、link.inner_html に文字列を代入すると中身のHTMLが変更されたり、 link[:href] = "foo.html" とするとリンク先が"foo.html"になったりします。
これらの変更は、to_htmlなどが生成するHTMLに反映されます。 例えば「サーバ移転したのでexample.jpへのリンクをexample.orgに直したい」という場合は、
doc = Hpricot(ARGF.read) (doc/:a).each do |link| link[:href].gsub! %r(http://example.jp/), "http://example.org" end print doc.to_html
という感じで簡単にフィルタスクリプトを書くことができます。
変更系のメソッドには以下のようなものがあります。
- ["href"]= : 属性hrefの値をセットする
- inner_html= : 内部のHTMLをセットする
- name= : タグ名をセットする
- children= : 子要素をセットする
余談
「/」とか個性的なメソッド名が多いHpricotですが、実は「もっと普通の名前」も用意されています。 例えば「/」には「search」という別名がありますし、 HTMLの読み込みは Hpricot.parse(html) とも書けます。 でもやっぱり短い方を使っちゃうんだよね…!(便利だから)
WWW::Mechanize
WWW::Mechanizeは、Webサイトへのアクセスを自動化するためのライブラリです。
例えばはてなダイアリーに日記を投稿するスクリプトを書くとしましょう。 Net::HTTPだとまず投稿用のURLを調べて、フォームタグの名前を調べてPOSTして、あれ認証ってどうやるんだっけ…?なんて いろいろなことを考えないといけませんが、WWW::Mechanizeを使えば「普段ブラウザを操作するのと同じような感覚で」 スクリプトを書くことができます。
では、ブラウザから日記を書くときの手順を思い出してみましょう。
- http://d.hatena.ne.jp/(ユーザid)/ を開く
- おおっと、ログインしてなかった。右上の「ログイン」をクリックする
- ユーザー名とパスワードを入力し、「ログイン」を押す
- 「自動でページを移動しています(移動しないときはこちらのリンクを…)」
- 「日記を書く」をクリック
- textareaに日記本文を入力し、「この内容を登録する」を押す
WWW::Mechanizeなら、これをそのままスクリプトに落とすことができます。
(1) http://d.hatena.ne.jp/(ユーザid)/ を開く
require 'mechanize' require 'kconv' #あとでUTF-8を扱うので agent = WWW::Mechanize.new diary_page = agent.get("http://d.hatena.ne.jp/(自分のはてなid)/")
newでインスタンスを作って、getでWebページを取得しています。簡単ですね。
(2) おおっと、ログインしてなかった。右上の「ログイン」をクリックする
login_link = diary_page.links.text("ログイン".toeuc) login_page = agent.get(login_link.href)
diary_page.linksでページ中のリンク一覧が取得できます。ここでは、全てのリンクの中から テキストが"ログイン"に一致するものを探しています。
login_linkはMechanize::Linkのインスタンスで、login_link.hrefでURLが得られます。agent.getにこのURLを渡してログインページを開きましょう。
(3) ユーザー名とパスワードを入力し、「ログイン」を押す
login_form = login_page.forms.first login_form['key'] = "(ユーザ名)" login_form['password'] = "(パスワード)" redirect_page = agent.submit(login_form)
login_page.formsでformタグの一覧が取得できます。ログインページにはformが一つしかないので、firstで最初のformを選んでいます。
次の行では、<input name="key" ...> というインプットボックスにユーザ名を入力しています。 パスワードも同様に入力します。
agent.submitにこのフォームを渡すとフォームの内容が送信され、送信結果のページが返ってきます。
(4) 「自動でページを移動しています(移動しないときはこちらのリンクを…)」
diary_link = redirect_page.links.text("こちら".toutf8) diary_page = agent.get(diary_link.href)
ログインすると、おなじみの「移動しないときはこちらのリンクを…」というリダイレクトページになります。 WWW::Mechanizeにはリダイレクトを自動で追跡してくれる機能がある…のですが、このリダイレクトページは200 OKなので 自動追跡が効きません(´・ω・`) 仕方がないので、手動でリンクをクリックしましょう。 またこのページはさっきとちがってUTF-8なので、"こちら"もUTF-8に変換しておきます。
(5) 「日記を書く」をクリック
edit_link = diary_page.links.text("日記を書く".toeuc) edit_page = agent.get(edit_link.href)
ここまで来たらあと一歩です。"日記を書く"のリンクを探し、クリックします。
(6) textareaに日記本文を入力し、「この内容を登録する」を押す
edit_form = edit_page.forms.name("edit").first edit_form["body"] += "\n*Rubyから日記を更新してみるテスト。" ok_button = edit_form.buttons.name("edit") agent.submit(edit_form, ok_button)
さあ、いよいよ日記の書き込みです。編集画面には複数のフォームがあるので、edit_page.form("edit") で最初の <form name="edit" ...> というタグを見つけています。また、このフォームには「確認する」と「登録する」という複数のsubmitボタンがあるので、 登録ボタンを探してsubmitに渡しています(「こっちのボタンを押してください」という意味です)。
http://d.hatena.ne.jp/(はてなid)/ を見てみてください。新しい日記が書き込まれましたか?:-)
WWW::Mechanizeではこのように、ブラウザを操作するような感覚でスクリプトを書くことができます。
簡易リファレンス
pageには以下のようなメソッドがあります。
- links : リンク(aタグ)一覧
- forms : formタグ一覧
- form("foo") : name="foo"である最初のformタグ
- title : ページタイトル(titleタグの中身)
- header : HTTPのレスポンスヘッダ
- root : ページの内容を表すHpricotドキュメント
linksやformsはMechanize::Listのインスタンスを返します。これはArrayのサブクラスなので、配列のように扱うことができます。 また簡便のため、「hrefが"index.cgi"であるものを全て探す」という操作を links.href("index.cgi") のように書けたり、 「name属性が"hoge"であるものを全て探す」という操作を forms.name("hoge") と書けるようになっています。
linkには以下のようなメソッドがあります。
- href : リンク先のURL
- text : aタグの中身のテキスト
- node : aタグを表すHpricot::Elem
- click : リンクをクリックし、結果のページを返す (newpage = agent.get(link.href) が、 newpage = link.click のように書ける)
formには以下のようなメソッドがあります。
- []=("foo", "bar") : name="foo" であるフィールドに値"bar"をセットする
- submit : フォームをsubmitし、結果のページを返す (newpage = agent.submit(form) が、 newpage = form.submit のように書ける)
より詳細なリファレンスはWWW::Mechanize 日本語リファレンスを参照してください。
インストール
さて、そろそろ実際に使ってみたくなったでしょうか?:-) rubygemsをインストール済みなら、
gem install hpricot gem install mechanize
と、簡単なコマンドでインストールできます。
- 前述のとおりmechanizeはhpricotに依存しているので、 mechanizeを入れればhpricotは自動的に入ります。
- Hpricotはつい最近version 0.5が出たので、昔インストールしたことがある人もアップデートをお勧めします。
- Hpricotは途中でUnix版を入れる(ruby)かWindows版を入れる(mswin32)か聞かれるので、自分の使っている方を選んでください。
rubygemsを使っていない人はアーカイブをダウンロードし、中に入っているinstall.rbを実行すればOKです(たぶん)
- Hpricot : http://code.whytheluckystiff.net/dist/
- WWW::Mechanize : http://rubyforge.org/projects/mechanize
まとめ
本稿では、RubyでWebから情報を得るときに役立つ2つのライブラリ、HpricotとWWW::Mechanizeを紹介しました。
これらを使うことで、HTMLのスクレイプやWebアクセスの自動化など今まで「面倒そうだなぁ」と思っていた処理が非常に簡単に書けるようになります。 Webでの情報収集を自動化したくなったとき、この2つのライブラリのことを思い出してもらえれば幸いです。
*1 さらにHpricotではXPathも使える…のですが、たいていはCSSセレクタで間に合うと思います。
■ [ruby][hiki] HikiReload
WWW::Mechanizeの使用例として、「テキストファイルが保存されたら自動的にWikiを更新する」というスクリプトを載せておきます。 上の記事もこのスクリプトを使って下書きしましたが、自分のお気に入りのエディタを使えるので非常に便利でした:-)
フォームタグを名前で探しているのでHiki限定ですが、他のWikiエンジンへの対応も難しくないと思います。 ぜひチャレンジしてみてください。
#!/usr/bin/env ruby require "mechanize" class HikiReload POLL_SECONDS = 3 def initialize(url) @agent = WWW::Mechanize.new @url = url end def poll(path) loop do mtime = File.mtime(path) while File.mtime(path) == mtime sleep POLL_SECONDS end puts "file #{path} changed.." submit(File.read(path)) puts "submitted." end end def submit(str) editpage = @agent.get(@url) form = editpage.forms.first form.contents = str result_page = @agent.submit(form, form.buttons.name("save")) puts result_page.root.to_html end end url, file = "http://example.jp/hiki/?c=edit;p=FrontPage", "temp.txt" HikiReload.new(url).poll(file)
使い方
- hikireload.rbという名前で保存(urlは自分のWikiに書き換える)
- temp.txtに下書きを書く
- ruby hikireload.rbで起動
- temp.txtを編集すると、エディタで保存するたびにWikiが更新される
エディタでWikiを編集するためのソフトウェアはemacsのhiki-modeなどいくつかありますが、このアプローチだと エディタを選ばない(emacsでもVimでもxyzzyでも秀丸でもなんでも使える)のが利点だと思います。
2007-02-06
■ [prog] C#2.0時代のゲームプログラミング(49) 〜 delegateを用いたcontinuation
C#の無名delegate(クロージャっぽいやつ)とCPS変換の話。
11月ごろから書こうと思いつつ放置しているネタに「call/ccを使ってRPGのイベントを実装する(した)」という話が あるんだけど、そうか、実は
- call/ccでやる
- yieldでやる
- CPS変換でやる
という3つの似た選択肢があるんだな。2とか3だとどういうコードになるんだろ…。あとで考える。
2007-02-09
■ [tDiary] スパムうぜー
仕方がないのでとりあえずgmail.comを弾いてみる。
「この日の日記を隠す」にしたのに、その日付へのコメントスパムが止まない。どうなってるんだ。
あと本文に"Drugs sucks"が含まれるのも弾くようにした。俺に言われても困る。
■ [web] favicon2dotsと実物のdot'sを比べてみるテスト
http://favicon2dots.com/show/5c1b77f8ee9b8e2c2ef68a0b2f2a034b78daeea9
http://sorakaze.net/blog/archives/2006/11/post_69.html
おお、なかなかそれっぽい。
おまけ
部員が作ってくれたDown!!のゲームオーバー画面です。
こんなんが机の上に飾ってあったら非常に嫌だw
■ [haskell] Liskell - clemens.endorphin.org
Lispの皮をかぶったHaskell、らしい。
- Haskell … 副作用なし+型推論(+遅延評価)によって安全なプログラミングが可能
- Lisp … マクロによって柔軟なメタプログラミングが可能
じゃあLispのS式をHaskellに変換したら最強なんじゃね?wwwというプロジェクトのようだ。
実装はS式をHaskellの構文木に変換し、それをGHCに渡すようになっているとのこと(http://clemens.endorphin.org/weblog/archives/2007-01.shtml#e2007-01-31T12_21_00.txt)。
非常に面白そうなんだが、LispとHaskellと両方知ってないといけないとか敷居が高いにもほどがある(笑)。
以下は書きかけの論文より、クイックソートの例。
(define (quicksort xs) (case xs (nil nil) ((: x xs) (++ (quicksort (filter (< x) xs)) '(x) (quicksort (filter (>= x) xs))))))
うーん、LispのようでLispでなく、パターンマッチや(< x)のあたりにHaskellの匂いが。まさにLiskell。
ところでHaskellでマクロを使いたいっていう需要はどれくらいあるんだろう。
□ 色々ありますが、 [v-language とかでしょうか。 http://code.google.com/p/v-language/]
2007-02-14
■ [haskell][ruby] 最も徳の高いfactorial
via http://d.hatena.ne.jp/omochist/20061219/1166544123
コメント欄より:
factorial n = product [1..n]
Haskell格好良すぎてずるい。
Rubyならここはinjectですよね。
def factorial(n) (1..n).inject{|a,b| a*b} end
2007-02-15
■ [ruby] 海外で入れてるgemを曝すのが流行ってるらしいらしいですよ
ということでやってみた。
yhara@meteor:~/src/ruby/gem_survey % gem list|grep '^[a-zA-Z]' activesupport (1.4.0) hoe (1.1.7, 1.1.6, 1.1.3) hpricot (0.5, 0.4.92, 0.4) mechanize (0.6.4, 0.6.3, 0.3.1) narf (0.7.3) newgem (0.7.2) rake (0.7.1) redgreen (1.2) rspec (0.7.5.1, 0.7.5, 0.7.4, 0.2.0) rubyforge (0.4.0, 0.3.2, 0.3.1) scrapi (1.2.0) sources (0.0.1) tidy (1.1.2) wirble (0.1.2) ZenTest (3.4.3, 3.4.2)
すくねえw
まぁgemで入れたライブラリでちゃんと使ってるのってrspec, hpricot, mechanizeくらいだもんなぁ。
海外で晒されているリストには聞いたことないgemも一杯あってどれがどういうgemなのか知りたくなったけど、 一個ずつ調べるのも面倒なのでスクリプトを書いてみた。
(1)gemリストをtxtに保存
yhara@meteor:~/src/ruby/gem_survey % cat bryan.txt actionmailer (1.3.2, 1.3.1, 1.2.5) actionpack (1.13.2, 1.13.1, 1.12.5) ...(省略)
(2)こんなスクリプト
yhara@meteor:~/src/ruby/gem_survey % cat survey.rb ARGF.each do |line| name = line.split.first puts `gem query -r -n "\\A#{name}\\z"`.split(/\n/).slice(3..-1) || "(gem #{name} not found)" $stdout.flush end
(3)実行すると各gemの1行解説が読めます
yhara@meteor:~/src/ruby/gem_survey % ruby survey.rb < bryan.txt| tee tmp.txt actionmailer (1.3.2, 1.3.1, 1.3.0, 1.2.5, 1.2.4, 1.2.3, 1.2.2, 1.2.1, 1.2.0, 1.1.5, 1.1.4, 1.1.3, 1.1.2, 1.1.1, 1.0.1, 1.0.0, 0.9.1, 0.9.0, 0.8.1, 0.8.0, 0.7.1, 0.7.0, 0.6.1, 0.6.0, 0.5.0, 0.4.0, 0.3.0) Service layer for easy email delivery and testing. actionpack (1.13.2, 1.13.1, 1.13.0, 1.12.5, 1.12.4, 1.12.3, 1.12.2, 1.12.1, 1.12.0, 1.11.2, 1.11.1, 1.11.0, 1.10.2, 1.10.1, 1.9.1, 1.9.0, 1.8.1, 1.8.0, 1.7.0, 1.6.0, 1.5.1, 1.5.0, 1.4.0, 1.3.1, 1.3.0, 1.2.0, 1.1.0, 1.0.1, 1.0.0, 0.9.5, 0.9.0, 0.8.5, 0.8.0, 0.7.9, 0.7.8, 0.7.7, 0.7.6, 0.7.5) Web-flow and rendering framework putting the VC in MVC. ...(省略)
■ [Plagger] Publish::PipeがYAMLを吐けるようにしてみた
PRaggerに対して「それCustomFeed::Script/Filter::Pipe/Publish::Pipeでよくね?」と思ってたんだが、 実はPublish::PipeだとタイトルとURLしか扱えないことに気付いた。
というわけで、Publish::PipeがYAMLを吐けるようにするパッチ。
5a6 > use Data::Serializer; 20,22c21,29 < for my $entry ($args->{feed}->entries) { < print $out $self->convert($entry->title) . "\n"; < print $out $self->convert($entry->permalink) . "\n\n"; --- > if ($self->conf->{yaml}) { > my $serializer = Data::Serializer->new(serializer => 'YAML'); > print $out $serializer->raw_serialize(Plagger::Walker->serialize($args->{feed})); > } > else { > for my $entry ($args->{feed}->entries) { > print $out $self->convert($entry->title) . "\n"; > print $out $self->convert($entry->permalink) . "\n\n"; > }
config.yamlに
- module: Publish::Pipe config: command: ruby sample_publish_pipe.rb yaml: 1
みたいに書いておくと(yaml: 1 が重要)、
--- author: yhara description: '' language: ~ link: http://mono.kmc.gr.jp/~yhara/d/ meta: {} source_xml: "<?xml version=\"1.0\" encoding=\"UTF-8\"?>(略)" tags: [] title: Greenbear Diary type: feed url: http://mono.kmc.gr.jp/~yhara/d/index.rdf entries: - author: yhara body: "<h3>(中略)ツッコミを入れる</a></p>" date: 2007-02-15T06:26:38+09:00 enclosures: [] feed_link: http://mono.kmc.gr.jp/~yhara/d/ id: http://mono.kmc.gr.jp/~yhara/d/?date=20070215#p01 link: http://mono.kmc.gr.jp/~yhara/d/?date=20070215#p01 meta: {} rate: 0 summary: "ということでやってみた。(略)" tags: - ruby title: "海外で入れてるgemを曝すのが流行ってるらしいらしいですよ" widgets: [] - author: yhara ...
みたいなYAMLがRubyスクリプトに入力されます。あとは require 'yaml'; data = YAML.load(ARGF.read) とかしてお好きにどうぞ。
もちろんYAMLが読める言語ならPythonでもJavaでもPHPでもなんでもOKです。
2007-02-17
■ [ruby][event] Ruby勉強会@関西-14
行ってきました。昼70人夜50人とか凄いよなぁ。ほんと。
Ruby 実行環境を取り巻く世界
#衝撃検知機能を実装したり、スライドいじったりしてて聞いてる余裕がありませんでした^^; ごめんなさい。
Plagger meets Ruby
自分の。Plaggerネタもちょっと時期を逃したかなぁ…と思ってるうちにPrhaggerとかPRaggerとかYahoo! Pipesとか 盛り上がってきてわーい。という。
以下自分用メモ
受けたネタとそうでないもの
- 「あまりよく知らない」の層が思ったより多かった(半分くらい?)。
- ていうかRuby関西の異様なまでの客層の広さを忘れてた
- ○○やさんは小ウケ(客層的にまぁそうだわな…)
- 振動検知のアレは非常に受けが良かった(拍手された…!) 直前のプレゼン中に頑張って実装したんだけど(西本さんごめんなさい^^;)、頑張って良かった。
- Tateyomiはまあまあ。
- ポーランドもまあまあ。
- Rの発音はわりとw
Keep
- スライドの「余り」を用意するのは有用。(両方使えるとは思ってなかったけど… :-) 運が良かった。かずひこさんに感謝)
- 1枚あたりの文字密度が低い*1ほうが話しやすい。量は増えるけど。頑張ってスライド作る。
- ブラウザ開いて見せるより、スクリーンショットをpptに貼っておくべき(楽だし手順で混乱しなくて済む)。
- デモはちゃんと動いた。何回も試験しといて良かった(直前になってバグに気付いたりとか^^;)
Problem
- 準備遅すぎすぎ(略)すぎすぎる。猛省。ていうか前日になってから作り始めるとか意味不明(結局一睡もできず)
- 徹夜すればなんとかなるかも…とか思った時点で負け
- スクリーン見すぎだったと思う。心に余裕を持ちたい。
Try
- 事前に1回くらい練習したい。
- 小ネタも全部実装してデモする(もしくはスクリーンショット)。
- (できれば)客層を考える。
- あとWiiリモコン使いたいw
発表資料のアップロードはちょっとお待ち下さい。今週半ばには多分。
Rubyリファレンスマニュアル刷新計画の進捗報告
参加したい!
letmesee + royal-fpwで超快適フランス語辞書生活
let me seeの存在は知っていたのですが、 使ったことがなかったので(CD-ROM辞書持ってないし)手を上げるのをためらっちゃいました。上げたらよかったか。
Ruby 初級者向けレッスン 第11回
irbについて。
隣の人(大学2回生)と一緒に。なんでもゼミの課題で「複数人で本をひとつ書け」*2 というのがあるらしく、 RubyかRailsの本にしようかと考えているそうな。凄いなぁ。頑張ってください。
懇親会
- 氏久さん曰く2/24の夜に京都でLiveCodingがあるとのこと。見に行かねば。
- はこべさんの932は草津?と思ってたらBKC生とのこと。近ぇ!(笑)(でも厳密にいうと373932ですよね)
- Rubyがシェアを拡大していく様子は初期のJavaを思い起こさせるらしい(こんなにJavaが普及するとは/Javaはむしろ軟派(?)な言語だった)
最後に一言
関西を離れるスタッフの方、これまでどうもお疲れ様でした m(__)m *3
2007-02-18
■ [ruby] Hirameki Inspiration - 超手抜き版 はてなダイアリーライター(ruby版)を作ってみた。
カコイイ!:-)ちなみにこのエントリもスクリプトで投稿してみました:-)
class BlogEngine def initialize(url, id, password) end end class HatenaDiary < BlogEngine def login end def post(content) end end class TDiary < BlogEngine def login end def post(content) end end
みたいな感じで他のブログにも対応させられないかな。
2007-02-20
■ [Plagger][ruby] Plagger meets Ruby
この前のRuby勉強会@関西-14で発表したときの資料を公開します。
PlaggerのプラグインをRubyで書く方法について、図とか例とかを交えつつ解説する、という感じでやらせてもらいました。 30分弱。
いやー、feedalizerとかfeedtoolsとかHpricotとMechanize便利すぎという話も入れようかと 思ってたんだけど、時間的にも容量的にも余裕がなかったので残念ながらカット。
スライドのデザインは http://gift.her.jp/pp_template/ のものを使わせてもらいました。スライド作成意欲が2割くらいアップしました。 ありがとうございます。
まとめを以下に置いておきます。
RubyでPlaggerのプラグインを書こう
(1)RubyからPlaggerに入力
- module: Subscription::Config config: feed: - url: "script:ruby make_feed.rb" - module: CustomFeed::Script
Ruby側では、フィードのXMLかYAMLを出力する
print YAML.dump( "title" => "hogehoge diary", "entry" => entries.map{|ent| { "title" => ent.title, "author" => ent.author, "link" => "http://.../#{ent.id}", "date" => ent.date, "body" => ent.message.toutf8, } })
(2)Rubyをフィルタにする
- module: Filter::Pipe config: command: "ruby some_filter.rb" encoding: euc-jp
Ruby側では、HTMLを受け取ってHTMLを返す*1
例(案):Filter::RubyMan Filter::Tateyomi
(3)PlaggerからRubyへ出力
- module: Publish::Pipe config: command: 'ruby feed_writer.rb'
Ruby側では、YAMLを受け取る(ただしパッチが必要→[Plagger] Publish::PipeがYAMLを吐けるようにしてみた)
例:publish_rubysdl.rb
*1 yamlにtext-only: 1 を付け加えれば、テキスト部分ごとに受け取ることも可能
2007-02-24
■ [ruby] Webサービス経由で足し算を行うライブラリ:jitor-int.rb
LiveCoding中に氏久さんが作ったIntというサービスを利用するライブラリを作ってみました。
Intは整数の演算を行うWebサービスで、例えば http://int.jitor.net/add/3/4 にアクセスすると"7"と書かれたページが 得られます。
さて、これをRubyから使うにはどういうAPIが良いでしょうか?JitorInt.add(3,4) とか? いやいや、こんな長いのは覚えにくいし、足し算を行うのにこんなのをいちいち書いてられませんよね。 「良くつかうものは短い名前にする」という原則に従えば、ここは組込みのFixnum#+をオーバーライドするしか ありません。
というわけで、jitor-int.rbです。
require 'open-uri' class Fixnum def use_orig alias :+ :add_orig result = yield alias :+ :add_jitor result end def add_jitor(other) url = "http://int.jitor.net/add/#{self}/#{other}" puts "accessing to #{url}..." result = use_orig{ open(url).read } p result result.to_i end alias :add_orig :+ alias :+ :add_jitor end
これを使うと、例えば
require 'jitor-int' p 1 + 2 + 3
のようなスクリプトが
accessing to http://int.jitor.net/add/1/2... "3" accessing to http://int.jitor.net/add/3/3... "6" 6
のような感じで動きます。require 'jitor-int' と書くだけで、Webサービス経由で足し算できるようになります。簡単ですね :-)
このライブラリがあれば、突然Rubyで足し算ができなくなっても安心!…と言いたいところなのですが、 実はopen-uriが中で Fixnum#+ を使っているため組込みの「+」は残念ながら必要です。
また普通にオーバーライド しただけでは無限再帰になってしまうので、Webにアクセスするところだけ + の定義を組込みのものに戻しています。
おまけ
「-」「*」「%」にも対応したバージョンを以下に貼っておきます。(「/」がないのは仕様です。)
require 'open-uri' class Fixnum SYMS = { :+ => :add, :- => :sub, :* => :multi, :% => :mod } def switch_method(sym, method) self.class.instance_eval do alias_method sym, method end end def use_orig SYMS.each{|sym, name| switch_method(sym, "#{sym}_orig")} result = yield SYMS.each{|sym, name| switch_method(sym, name)} result end def jitor(type,a,b) url = "http://int.jitor.net/#{type}/#{a}/#{b}" puts "accessing to #{url}..." result = use_orig{ open(url).read } p result result.to_i end SYMS.each do |sym, name| define_method name do |other| jitor(name, self, other) end alias_method "#{sym}_orig", sym alias_method sym, name end end
2007-02-25
■ [Pragger] PRaggerだってピザを頼みたい
ですよね!
というわけで notify_demaecan.rb というプラグインを書いてみました。
Notify::Pizzaより高機能な点として、ピザのサイズが選べることが挙げられます(笑)。 例えばconfigに「size: L」のように書いておくと、Lサイズのピザの中からランダムで一つが選ばれます。 これでパーティーの際にも安心。
注意:
- テストしてません(ピザを頼む用事がなかったので)。 多分いけるとは思う。
- エラー処理が適当です。
- まだ寿司には対応してません。(対応は簡単なんだけど、失敗すると\8,000-とかかかるので危ないw)
- townという引数がありますが実装されてません。会員登録のときに住所も登録しておいてください
- 文字コードについて真面目に考えてません。出前館のWebページはSJISです
というわけで、どなたか実際に試していただけると有難いです^^;
require 'rubygems' require 'mechanize' class DemaeCan def initialize(user, pass) @user, @pass = user, pass @agent = WWW::Mechanize.new end def order(type="ピザ", size="L", town="0") #start page = @agent.get("http://demae-can.com/index.php?action=dream_login_index") #login form = page.forms.first form["id"] = @user form["pass"] = @pass page = form.submit unless page.root.to_html.include? "http://demae-can.com/search/shop_list.html\?word=0" raise ArgumentError, "login failed." end #select type page = @agent.get("http://demae-can.com/search/shop_list.html\?word=#{town}") link = page.links.find{|link| link.node.to_html.include?(type)} page = link.click #select shop link = page.links.find{|link| link.href =~ /shopcode/} page = link.click #select size forms = page.forms.find_all{|form| (form.form_node/"td.mini").find{|td| td.inner_text =~ /#{size}サイズ/} } page = forms[rand(forms.size)].submit #select number page = page.forms.first.submit #select time page = page.forms.first.submit #submit!! form = page.forms.first form["pass"] = @pass page = form.submit end end def notify_demaecan(config, data) demae = DemaeCan.new(config["user"], config["password"]) demae.order(config["type"], config["size"]) end =begin - module: notify_demaecan config: user: AABCDEF password: pAssw0rD type: ピザ size: L =end
#将来的にはgem化して
gem install demaecan
とかできると格好いい…かも。
□ 権 [すごいですね。ruby だんだん好きになっちゃいましたよ。 ruby中毒になった感じ]
□ ザッピング運営事務局 [※このコメントは、 ザッピングをご利用して頂いているユーザー様を対象にお送りしております。 既にタグを変更して頂いて..]
□ ケムス [良く勉強になりました。 ありがとうございます。]