2007-01-15 [長年日記]

String#scrape

どうせお前らはあれだろ、scrAPI は強力だし、これこそ自分がまさに待望してた道具、使いこなすぜ!と意気込んでるんだけど、どれだけ決意してもあの複雑な引数に毎回挫折しちゃって、挫折つーかちょっと使いたいだけなのにパーザ(Scrape)用のクラスを定義するのが面倒なんだよね、みたいな言い訳を毎回自分にしつつ、結局使いこなせてない脳内ゆとり世代のお前らなんだけど、まぁ実際引数に無駄に色んな機能を詰め込み過ぎてる感は否めないし、というかextractorのsrcとdstはどう見ても直感的に逆だろ、grepみたいに使わせろよ使えない1だな、みたいな愚痴をこぼしてたら、むしろCSS3なgrepとして使えるだけでいい事に気付いて、You、Stringクラスに入れちゃいなYO!

String#scrape の定義

require 'scrapi'

class String
  def scrape(pattern, options = {}, &block)
    options = {:extract=>options} unless options.is_a?(Hash)
    options[:parser_options] = {:char_encoding=>'utf8'}.merge(options[:parser_options]||{})
    extract = options.delete(:extract) || block && :element || :text
    scraped = Scraper.define do
      process pattern, "matches[]"=>extract
      result :matches
    end.scrape(self, options) || []
    block ? scraped.map{|i| block.call(i)} : scraped
  end
end

(※ 以下、$KCODE = 'u' を前提)

使用例

HTMLテキストデータ

...
<tr>
  <td class="style_td">スライム</td>
  <td class="style_td">基本のスライム</td>
  ...
</tr>
<tr>
  <td class="style_td">バブルスライム</td>
  <td class="style_td">緑の溶けてる奴</td>
  ...

grep のように使う

html.scrape("tr td")
=> ["スライム", " 基本のスライム", ..., "バブルス ライム", "緑の溶けてる奴", ...]

CSS3をエンジョイ

html.scrape("tr td:nth-child(1)")
=> ["スライム", "バブルスライム", ...]

朝日COMのRSS代わり

主要ニュースのタイトルを取得

require 'open-uri'
html = NKF.nkf('-w', open("http://www.asahi.com/").read)
html.scrape("#con1 li a:nth-child(1)")
=>
["暖冬で原油価格が下落 日本は灯油在庫が過剰",
 "三菱電機、エアコンのリサイクル料金値下げ",
 "1万2千羽の焼却始まる 宮崎の鳥インフルエンザ",
 "顧客の3000万円詐取 容疑の公認会計士を逮捕",
 "楽天新人の田中投手、合同自主トレでブルペン投球"]

属性値へのアクセス

第二引数に extractor (専門用語)を指定することができる。extractor の書式では"@" プレフィクスが属性値を意味するので、リンクを取得する場合は "@href" を指定する。

リンク先を抽出

html.scrape("#con1 li a:nth-child(1)", "@href")
=>
["/life/update/0115/014.html",
 "/life/update/0115/013.html",
 "/national/update/0115/SEB200701150016.html",
 "/national/update/0115/TKY200701150337.html",
 "/sports/update/0115/207.html"]

element への直接アクセス

String#scrape は戻り値に文字列の配列を返すが、scrAPI 自体は内部では HTML 要素クラスで管理している。その element オブジェクトを直接操作した場合は、ブロック引数を利用する。

elementクラスの確認

html.scrape("#con1 li a:nth-child(1)"){|e| e.class}
=> [HTML::Tag, HTML::Tag, HTML::Tag, HTML::Tag, HTML::Tag]

element は親と子の全ての参照を持つため、"{|e| e}" とかやるのは表示が大変なことになるので危険。(= 一度やって体で覚えるのもあり)。実際の用途としては、「リンク名とリンク先を配列に入れる」場合などに便利。

scrAPIのelementを直接参照

html.scrape("#con1 li a:nth-child(1)"){|e| [e["href"], Scraper::Base.text(e)]}
=>
[["/life/update/0115/014.html",
  "暖冬で原油価格が下落 日本は灯油在庫が過剰"],
 ["/life/update/0115/013.html",
  "三菱電機、エアコンのリサイクル料金値下げ"],
 ["/national/update/0115/SEB200701150016.html",
  "1万2千羽の焼却始まる 宮崎の鳥インフルエンザ"],
 ["/national/update/0115/TKY200701150337.html",
  "顧客の3000万円詐取 容疑の公認会計士を逮捕"],
 ["/sports/update/0115/207.html",
  "楽天新人の田中投手、合同自主トレでブルペン投球"]]

element がどういうアクセサメソッドを理解するかについて興味があれば、scrAPI のドキュメント or ソースを参考に。でも、Scraper::Base.text は便利なので、もう HTML::Tag に持たせておくのもよいかも。

textは便利なので定義しちゃう

class HTML::Tag
  def text() Scraper::Base.text(self) end
end

さっきのコード(リンク名とリンク先を取得)

html.scrape("#con1 li a:nth-child(1)"){|e| [e["href"], e.text]}
本日のツッコミ(全1件) [ツッコミを入れる]
_ someeda (2007-01-26 02:41)

ああ、これ凄い便利かも。<br>scrapi-simpleとか名前はなんでもいいけど、gemにしてみません?<br>HTML::Tag#text() は本家で是非取り込んで欲しいですね。


サイト内検索 (by Google)

| JRuby | Rails | Berryz | ℃-ute | エッグ | jQuery |

過去

2007年
1月
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31

未来

コンタクト