2010-01-26

Mechanizeで無茶をする

mechanize-0.9.3 Documentation

自分にとって Mechanize による自動化はたいがい無理を通す行為である。分かりやすく言えば API なんかない、あるいはあっても足りないみたいな状態で、それでもどうにか自動化したいから Mechanize を使う。

Mechanize が持っている標準的な機能だけで済んでいる場合はまだかなりマシで、実際のところ無理というか「無茶」なレベルに突入してしまうことが、なぜかそれなりにあったりする。具体的には HTML が壊れているのでパースに失敗して、あるはずの要素がなくなっていたりする場合などである。

今回はそんな無茶の一部をご紹介。

パーサを Hpricot に変える

ずばり基本でしょう。

Mechanize は 0.9 以降デフォルトパーサを Hpricot から Nokogiri に切り替えているが、そもそも Nokogiri は HTML 用にできていない。XML 用の道具に、 Hpricot によく似たインターフェイスを付けたものである。HTML は自由度の高い書式で、XML 用のノコギリでは歯こぼれを起こすことがよくある。

そこでこの設定(0.9.0 〜 0.9.2)。

require 'hpricot'
WWW::Mechanize.html_parser = Hpricot

cf.

0.9.3 (以降?)はサブクラスの利用に注意

html_parser がインスタンスのアクセッサとして定義されたのでインスタンスごとに parser をセットできるようになったのはいいんだけど、Mechanizeクラスオブジェクトのインスタンス変数を self.class で参照して自身に書き戻しているので、

サブクラスに反映されない

状態になっている。(少なくとも Ruby 1.8.7 では parser が nil になって動かなかった。)定義部分は以下のような感じ。

class Mechanize
  ...
  @html_parser = Nokogiri::HTML
  class << self; attr_accessor :html_parser, :log end
  def initialize
    ...
    @html_parser = self.class.html_parser
  end
  ...
end

動かすとこんな感じ。

$ cat sub_mechanize.rb
class SubMechanize < WWW::Mechanize
end
$ irb
irb(main):001:0> require 'mechanize'
=> true
irb(main):002:0> a = WWW::Mechanize.new
=> (snip)irb(main):003:0> a.html_parser
=> Nokogiri::HTML
irb(main):004:0> require './sub_mechanize'
=> true
irb(main):005:0> b = SubMechanize.new
=> (snip)
irb(main):006:0> b.html_parser
=> nil

恐らく Mechanize のインスタンスについては html_parser のセット方法に互換性をとりつつインスタンスごとに設定できるようにしたかったためにこうなったんだろうけど、いつもデバッグしやすいように独自のサブクラスを噛ましていた1ので、まったく parse できない現象にハマってしまった。仕方ないのでサブクラスの中で独自に定義することにした。

class SubMechanize < WWW::Mechanize
  def initialize
    super
    @html_parser = Hpricot
  end
end

こんなんでいいのかな。

cf. RubyのMechanizeの0.9.3が6月8日に出てたっぽい - きたももんががきたん。

Field を作る

これはまだ初級。

Form#add_field!( name, value )

無駄に JavaScript に分離したフォームの場合、必要な field が HTML 上に存在しないことがよくある。その値を無理矢理 form 上に反映するために使う。

FileUpload を作る

これに気づいたときにはけっこう嬉しかった。add_field! では Field は作れても FileUpload は作れないから。

どうやるかというと、Form オブジェクトに対して instance_eval を使う。

Form#enctype = 'multipart/form-data'
Form#instance_eval {
  @file_uploads << WWW::Mechanize::Form::FileUpload.new( name[, filename] )
}

もともと file_upload が存在しない form として解釈した場合は enctype が違うことがある(というか指定してないかも)ので手で変更してあげると吉。

Form を作る

instance_eval() を思い出してしまえば簡単。もう form そのものを解釈できませんでしたという凶悪な HTML 向け。

Page#instance_eval {
  @forms << WWW::Mechanize::Form.new( node[, mech, page] )
}

node には Hpricot::Elem オブジェクトを入れてあげれば ok.

このとき、Hpricot::Elem になれば元の文字列はなんでもよいので、

Form.new( Hpricot( String#scan( /<form.*?>.*?<\/form>/m ).to_s ).at( 'form' ) )

みたいなこともできる。HTML が壊れているので正規表現でいったん form だけ引っこ抜いて、それを Hpricot オブジェクトに戻してやって Form を作る。途中の整形も思いのまま。

Page を作る

Page を作るのはちょっと手が掛かる。以前作ったものを gist に置いてあるので参考になれば嬉しい。

  1. http://gist.github.com/76140 

About

例によって個人のなんちゃらです

Recent Posts

Categories

Tool 日々 Web Biz Net Apple MS ことば News Unix howto Food PHP Movie Edu Community Book Security Text TV Perl Ruby Music Pdoc 生き方 RDoc ViewCVS CVS Rsync Disk Mail FreeBSD Cygwin PDF Photo Zebedee Debian OSX Comic Cron Sysadmin Font Analog iCal Sunbird DNS Linux Wiki Emacs Thunderbird Sitecopy Terminal Drawing tDiary AppleScript Life Money Omni PukiWiki Xen XREA Zsh Screen CASL Firefox Fink zsh haXe Ecmascript PATH_INFO SQLite PEAR Lighttpd FastCGI Subversion au prototype.js jsUnit Apache Trac Template Java Rhino Mochikit Feed Bloglines CSS del.icio.us SBS qwikWeb gettext Ajax JSDoc Rails HTML CHM EPWING NDTP EB IE CLI ck ThinkPad Toy WSH RFC readline rlwrap ImageMagick epeg Frenzy sysprep Ubuntu MeCab DTP ERD DBMS eclipse Eclipse Awk RD Diigo XAMPP RubyGems PHPDoc iCab DOM YAML Camino Geekmonkey w3m Scheme Gauche Lisp JSAN Google VMware DSL SLAX Safari Markdown Textile IRC Jabber Fastladder MacPorts LLSpirit CPAN Mozilla Twitter OpenFL Rswatch ITS NTP GUI Pragger Yapra XML Mobile Git Study JSON VirtualBox Samba Pear Growl Mercurial Rack Capistrano Rake Win RSS Mechanize Sitemaps Android JavaScript Python RTM OOo iPod Yahoo Unicode Github iTunes God SBM friendfeed Friendfeed HokuUn Sinatra TDD Test Project Evernote iPad Geohash Location Map Search Simplenote Image WebKit RSpec Phone CSV WiMAX USB Chrome RubyKaigi RubyKaigi2011 Space CoffeeScript Nokogiri Hpricot Rubygems jQuery Node GTD CI UX Design VCS Kanazawa.rb Kindle Amazon Agile Vagrant Chef Windows Composer Dotenv PaaS Itamae SaaS Docker Swagger Grape WebAPI Microservices OmniAuth HTTP 分析基盤 CDN Terraform IaaS HCL Webpack Vue.js BigQuery Middleman CMS AWS PNG Laravel Selenium OAuth OpenAPI GitHub UML GCP TypeScript SQL Hanami Develop Document Jekyll