Win32OLE 活用法 【第 6 回】 Web 自動巡回

書いた人: cuzic

はじめに

これまで Excel, ADODB, Outlook などのアプリケーションを Ruby で扱う方法について 学んできました。

今回は Web の自動巡回をテーマにしながら、Windows Script Host と Internet Explorer の COM オブジェクトを扱う方法について学んでいこうと思います。

この記事では Web の自動巡回を Internet Explorer を制御することによって行います。 IE を制御するための方法として、Internet Explorer の COM オブジェクトを 操作する方法と、キーボードをシミュレーション、つまりキーボード操作を すでに特定のアプリケーションに送ることによって制御する方法について 学んでいきます。

Web 自動巡回の方法について

そもそも Web の自動巡回をしたいだけのときに、Internet Explorer を 操作する必要があるとは限りません。

‘SPIDERING HACKS’ に掲載されているような方法を用いることで、ウェブを縦横無尽に自動収集させることで、 自分の必要な情報を集めていくような方法があります。

Internet Explorer を使う方法以外にも Ruby には Net/HTTPopen-uri があります。他にも RAA には http-access2 が登録されています。 これらのライブラリを利用する方法は、ページを取得して必要なデータを抽出するというような スパイダリングのような用途だけであれば、Internet Explorer を使うよりもずっと向いています。 Win32OLE を用いる方法はスパイダリングにはあまり向いていません。

それではどのようなときに Win32OLE を用いて Internet Explorer を制御することが 有効になるでしょうか?

まず、ブラウザを表示させたいときがあります。 自分の欲しい情報のところだけを切り出して、スパイダーに収集させたいというときも あるでしょうが、単純に順繰りに Web ページを巡回したいだけのときもあるでしょう。 また、ユーザ名とパスワードの入力のみを自動化させたいというような用途もあるかも しれません。 ユーザ名とパスワードの入力を自動化させることによって、ちょっとした面倒さを なくしたり、エンドユーザにパスワードを教えないまま操作可能にしたいという要望を 満たしたりできます。

他に Java Script を用いているようなサイトから情報を引き出したいときが考えられます。 このような場合はどんなに Net/HTTP などのライブラリを使ったとしても効果がありません。

最近とみに流行している Ajax を使ったサイトなどの場合は、モダンなブラウザを用いて テキストボックスに入力などを行わないと必要な情報の取得はできません。

この記事では、執筆時点で Ajax がホットな話題ということもありますので、 Ajax サイトからのデータの取得方法について説明していこうと思います。

今回学ぶ内容について

今回学ぶ方法は次のとおりです。

  • Internet Explorer の COM オブジェクトを Ruby から生成・制御
    • テキストボックスの値を取得
    • HTTP の POST メソッドを使ってページを表示。
  • Windows Script Host を用いてキーボードシミュレーションを行い IE を制御する方法
    • クリップボードを使っての情報の受け渡し

COM オブジェクトを操作する方法というのは今までずっと学んできましたね。 Excel や ADODB や Outlook のオブジェクトを操作することでそれらの アプリケーションを自動制御することができました。

この COM オブジェクトを操作する方法について Internet Explorer についても学びます。 このときに、テキストボックスの値の取得や設定、他に HTTP の Post メソッドでの ページの移動について学んでいきます。

あと、もう1つ学ぶのは、Windows Script Host を用いて、自動制御する方法です。 この記事をお読みならばご存知の方も多いのではないかと思いますが、Windows Script Host には、SendKeys というメソッドがあります。これを用いることで、定型的な入力を自動的に 行い、外部のアプリケーションを自動制御することができます。

その外部のアプリケーションとデータのやりとりを行うときは、 Windows のクリップボードの 機構を用いることで、相互にデータをやりとりし、分岐することも可能になります。

Ajax サイトでの自動制御

COM オブジェクトとしての操作

Internet Explorer のオブジェクトを操作することによって、Java Script を 用いているサイトを自動的に巡回するような方法についてここでは学びます。

今回、自動制御する対象とするのは、Ajax サイトとして非常に有名な Ajax を使った郵便番号検索 を例に、Java Script を使われているサイトでの巡回について説明します。

説明が遅れましたが、Ajax というのは Asynchrous JavaScript + XML の略です。 JavaScript + CSS を使った Dynamic HTML の機構と XMLHttpRequest を用いて サーバ側のアプリケーションを組み合わせた技術を Ajax と呼ぶようです。 Google Maps で用いられてその画面遷移なしでの ナビゲーションを行うことが実に画期的なテクノロジーです。

Ajax は今までの Web ブラウジングに全く新しいエクスペリエンスをもたらしました。 Ajax は非常に快適な操作性が特徴ですが、その反面、いままでの Web ブラウズとは 全くことなるパラダイムにしたがって動作するため、従来と同じ方法で Web ページの 巡回を行えません。

Ajax を用いたサイトは、モダンなブラウザで閲覧することが前提となるからです。

このようなサイトを自動的に巡回するにはまさにそのモダンなブラウザを自動制御する ことが有効な方法です。Java Script や CSS を使ったアクションを勝手に行って くれるからです。 そして、私たちはきわめて自動制御に向いたブラウザをすでに持っています。 Internet Explorer です。

IE は COM を用いることで、自動的にテキストボックスの入力や JavaScript の命令を 実行させることができます。 それではこれからそのための方法について学んでいきましょう。

COM を使った方法

COM を用いることで、郵便番号検索のサイトを自動的に制御させるスクリプトは 次のものです。

このスクリプトでは郵便番号に適当な郵便番号を自動入力させて、その都道府県や 市区町村を標準出力に出力させています。

zip_com.rb

require 'win32ole'

ie = WIN32OLE.new("InternetExplorer.Application")
ie.Navigate "http://apollo.u-gakugei.ac.jp/~sunaoka/ajax/ajaxzip/"

ie.Visible = true

while ie.Busy == true
  sleep 1
end

zip = ie.Document.getElementByID("zip")
zip.Value = "1000001"
zip.FireEvent("onkeyup")

sleep 1
%w(pref city ville).each do |name|
  puts "#{name} #{ie.Document.getElementByID(name).Value}"
end

Win32OLE活用法第一回で すでに Internet Explorer を使った例について紹介しています。

そのときは、ほとんど説明は行いませんでしたが勘の良い方は説明がなくても 理解できているかもしれません。

Internet Explorer の使い方を調べるときは、2つのタイプライブラリを オブジェクトブラウザで調べることになります。

“Microsoft Internet Controls” と “Microsoft HTML Object Library” です。 自分で Internet Explorer を制御するときはこれらのタイプライブラリを 調べてください。

DHTML の機構に従うと、getElementByID を用いて特定のエレメントを ID を 用いて取得できます。このスクリプトでは ID が “zip” のエレメントを 取得しています。

zip = ie.Document.getElementByID("zip")

ID が “zip” のエレメントは、 Ajax を使った郵便番号検索 のページの HTML ソースを見るとどれか分かります。 郵便番号という文字列の右にあるテキストボックスが ID が zip のエレメント になります。

zip というオブジェクトを第一回でやったように WIN32OLE#ole_obj_help メソッドを 用いて調べると、

irb(main):009:0> zip = ie.Document.getElementByID("zip")
=> #<WIN32OLE:0x1016e728>
irb(main):010:0> zip.ole_obj_help
=> DispHTMLInputElement

DispHTMLInputElement というオブジェクトであることが分かります。

DispHTMLInputElement は、オブジェクトブラウザで調べるときは、 DispHTMLInputElement というオブジェクトはでてきませんので、 HTTMLInputElement を調べる必要があります。 Simple OLE Browser や Python Object Browser で調べる場合は、 DispHTMLInputElement を調べることができます。 そうすると、このエレメントが提供するメソッドやプロパティ、イベントが 分かります。

筆者が既存の Web ページを自動巡回するようなスクリプトを作るときは、 irb を使いつつ WIN32OLE#ole_obj_help メソッドを駆使して、 ie.Document.getElementByID で取得したエレメントがどの オブジェクトなのかを調べ、”Microsoft HTML Object Library” の タイプライブラリからオブジェクトブラウザでそのオブジェクトが持つ メソッド等を調べるという方法をよくします。

ここでは zip エレメントの Value プロパティを設定することで、値を更新しています。 オブジェクトブラウザで調べると、HTMLInputElement オブジェクトは FireEvent というメソッドを持っています。 このメソッドを用いることで、JavaScript で定義されているイベントを発生させる ことができます。 Ajax を使った郵便番号検索 の HTML ソースを見ると “onKeyUp” イベントを用いて Ajax を実現している ようなので、FireEvent メソッドの引数に “onKeyUp” を与えることで、onKeyUp メソッドを 発生させています。

zip.FireEvent("onKeyUp")

FireEvent メソッドを実行することで、onKeyUp イベントで登録されている JavaScript が動作して、都道府県や市区町村のところのテキストボックスを 更新できています。

そしてイベントの実行終了を待ったのち、更新されたエレメントの値を取得しています。

sleep 1
%w(pref city ville).each do |name|
  puts "#{name} #{ie.Document.getElementByID(name).Value}"
end

このスクリプトによって、Ajax によって更新されたテキストボックスの値の取得が できています。

これで、Net/HTTP などではうまくいかない情報を IE を用いることで自動的に 取得し、収集できました。

考えられる応用と発展的な話題

ここで紹介したテクニックは、結局画面を閲覧しなければならない場合、たとえば オークションサイトの巡回や、ショッピングなどを簡単に行いたい場合に 非常に応用が利きます。

今回は単純に流行しているトピックを扱いたいという理由で Ajax サイトを 例に説明しましたが、Ajax でない場合でも今回説明した内容は参考にできます。 紙面の関係で説明しませんが、HTMLInputButtonElement オブジェクトなどには click メソッドがあり、クリックしたときの動作を行わせることもできます。

また、今回は全く説明しませんし、筆者自身試したことはありませんが、 HTA (HTML Applications) のようなスタンドアロンな GUI のスクリプト アプリケーションを Internet Explorer のオブジェクトを操作することで 作成できます。 HTA については HTML Applications 概要が参考になります。 また、arton さんが書いた ‘Ruby を 256 倍使うための本 邪道編’も参考になります。

CGI 出身の方が Windows GUI を作るときなどは、それまでの 蓄積がそのまま活きるため検討の価値があるでしょう。

IE が COM に対応しているため、COM で自動制御を行うような応用が発生する ことが予想される場合は便利です。 普通にスタンドアロンアプリケーションを作成したときと比べて、 COM 対応させる手間が省けます。

これはテストの自動化を行いやすいという長所があります。 そのため、アジャイルな開発プロセスで GUI のテストを最初に作りたい場合などに 便利かもしれません。

キーボードシミュレーションによる Internet Explorer の自動操縦

ここまで、IE のオブジェクトを操作することで、Ajax ページを自動的に 制御する方法について学びました。

次は、Windows Script Host の SendKeys メソッド等を用いて、 自動制御させる方法について学びます。

この方法は一般にとても美しくないとされており、嫌っている方も多い方法です。 しかしながら、とても応用範囲が広く強力です。 他に方法はないときにこのような方法があると、自分のレパートリを増やしておくことは 有益でしょう。 この節は、一般的なアプリケーションに対してキーボードショートカットを駆使して、 自動制御を行うときに役に立ちます。

では、サンプルのスクリプトを見てみましょう。

zip_wsh.rb

require 'win32ole'
require 'Win32API'

class Clipboard
  OpenClipboard = Win32API.new('user32', 'OpenClipboard', ['I'], 'I');
  CloseClipboard = Win32API.new('user32', 'CloseClipboard', [], 'I');
  EmptyClipboard = Win32API.new('user32', 'EmptyClipboard', [], 'I');
  IsClipboardFormatAvailable = Win32API.new('user32', 'IsClipboardFormatAvailable', ['I'], 'I');

  GetClipboardData = Win32API.new('user32', 'GetClipboardData', ['I'], 'I');

  GlobalLock = Win32API.new('kernel32', 'GlobalLock', ['I'], 'P');
  GlobalUnlock = Win32API.new('kernel32', 'GlobalUnlock', ['I'], 'I');
  
  CF_TEXT = 1;
  
  def self.GetText
    result = ""
    
    while OpenClipboard.Call(0) == 0
      sleep 1
    end
    begin
      if (h = GetClipboardData.Call(CF_TEXT)) != 0
        if (p = GlobalLock.Call(h)) != 0
          result = p;
          GlobalUnlock.Call(h);
        end
      end
    ensure
      CloseClipboard.Call
    end
    return result;
  end
end

url = "http://apollo.u-gakugei.ac.jp/~sunaoka/ajax/ajaxzip/"
wsh = WIN32OLE.new("Wscript.Shell")
wsh.Run("explorer.exe #{url}",1,true)
sleep 2
wsh.AppActivate("Ajax")
sleep 2
wsh.SendKeys("%D")
wsh.SendKeys("{TAB}{TAB}{TAB}1000001")
sleep 1
wsh.SendKeys("{TAB}^c")
puts Clipboard::GetText()
wsh.SendKeys("{TAB}^c")
puts Clipboard::GetText()
wsh.SendKeys("{TAB}^c")
puts Clipboard::GetText()

このスクリプトも先ほどと同様に郵便番号を自動的に入力させて、 都道府県と市区町村を標準出力に出力させています。

このスクリプトでやっていることは、次のとおりです。

  • Ajax を使った郵便番号検索のページを表示
  • アクティブなウィンドウを「郵便番号検索」のウィンドウにする。
  • TAB キーを3回送ることで、郵便番号のテキストボックスにフォーカスを移動。
  • 都道府県と市区町村のテキストボックスを順にフォーカスを移動し、クリップボードにコピーと取得。標準出力への出力を繰り返す。

クリップボードの操作については、可搬性の高い方法が執筆時点で見つからなかったために 自分で実装しました。 次の節で Win32API を直接たたく以外の方法を説明します。 それぞれの環境に合わせてもっと楽な方法での利用を検討してください。

Win32API ライブラリ を参考にクリップボードを操作しています。 Clipboard クラスについては後で詳しく解説します。

ここではキーボードシミュレーションを行っている箇所について説明します。

url = "http://apollo.u-gakugei.ac.jp/~sunaoka/ajax/ajaxzip/"
wsh = WIN32OLE.new("Wscript.Shell")
wsh.Run("explorer.exe #{url}",1,true)
sleep 2

ここで、Ajax を使った郵便番号検索 を表示しています。 すでに 「Ajax を使った郵便番号検索」のウィンドウを開いている状態であれば、 フォーカスのあったエレメントが違い、必要なタブ数がずれる可能性があるので、 正しく動作しません。そのため、必ずまだ開いていない状態で動かしてください。 今回は、explorer.exe を使いましたが、Internet Explorer が標準のブラウザでは ない場合がありえますので、 C:\Program Files\Internet Explorer\iexplore.exe の方がいいかもしれません。

wsh.AppActivate("Ajax")
sleep 2

タイトルバーに Ajax の文字列があるウィンドウをアクティブにします。

wsh.SendKeys("{TAB}{TAB}{TAB}1000001")
sleep 1

TAB を一度押すことをシミュレーションするときは wsh.Sendkeys(“{TAB}”) と表現します。 このような特殊なキーの入力については、SendKeysメソッド を 参考にしてください。

このスクリプトでは TAB を3回おして、郵便番号のテキストボックスにフォーカスを あわせています。そして、郵便番号を入力しています。 イベントの実行終了等を待つためにスリープしています。

wsh.SendKeys("{TAB}^c")
puts Clipboard::GetText()

そして、次のテキストボックスにフォーカスを合わせます。 ^c は Ctrl+C を押すことを表しています。 Ctrl+C というのはクリップボードへのコピーを意味するショートカットですので、 これでテキストボックスの値をクリップボードにコピーしています。

そして、Clipboard::GetText() によってクリップボードの値を取得して、 標準出力に出力しています。 以下市区町村について同じことを行っています。

wsh.SendKeys("{TAB}^c")
puts Clipboard::GetText()
wsh.SendKeys("{TAB}^c")
puts Clipboard::GetText()

このようにすることでキーボードで制御できるようなことならどんなことでも コントロールできます。

キーボードシミュレーションを行うときのコツ

キーボードの操作をシミュレーションする方法、キーボードの操作を送ることと 待ち合わせを行うだけなので、従来のキーボードでの操作さえ理解できれば 実装できます。その意味で作りやすいかもしれません。

この方法は適切な箇所で適切な時間 待ち合わせを行うことがコツになります。 これはいろいろと試行錯誤を行うしか方法がありません。

今回行ったキーボードシミュレーションは、一方通行で分岐などがない 場合でした。 このような処理で十分なときがありますが、単純にそれだけの場合は、 わざわざ Ruby を使わなくても、キーボードシミュレーション専用のソフトが あります。そちらのソフトに慣れていれば、その方がいいかもしれません。

Ruby で行うことによって、最も意味があるのは、正規表現、ハッシュなど Ruby ならば標準で用意されているライブラリが必要になる場合でしょう。 Ruby の備えた高度なライブラリなら簡単にできるけれども専用ツールでは 実現が困難な場合というのはあるものです。

クリップボードの操作に関して

このスクリプト上では、クリップボードの値を取得するコードが少々長いのが 気になります。これは、Cygwin 版と mswin 版の Ruby で可搬性のあるクリップボードを扱う 機構が執筆時点で見当たらなかったので、自力で Clipboard クラスを実装したからです。 詳しい方、教えてください。

mswin版では、VisualuRuby 計画 の vr/clipboard や Ruby Library Report の第4回 で紹介された win32-utils の win32::clipboard があります。

cygwin 版であれば、cat /dev/clipboard を実行するという方法や getclip というコマンドを 実行する方法が簡単です。 他に、Win32のクリップボード操作用モジュール を使えばクリップボードを簡単に扱えますが、標準ではないため採用しませんでした。

さらにクリップボードを扱う可搬性がある方法として、Win32OLE を使って Internet Explorer の COM オブジェクトを経由する方法があります。

ie = WIN32OLE.new("InternetExplorer.Application")
ie.Navigate("about:blank")
sleep 1 until ie.Busy == false
puts ie.Document.parentWindow.clipboardData.getData("Text")

でクリップボードの値を取得でき、

ie.Document.parentWindow.clipboardData.settData("Text","hoge")

でクリップボードの値を設定できます。

しかしながら、IE のオブジェクトを使わずに、キーボードシミュレーションを行って IE を制御するという趣旨なので、採用しませんでした。

あと他の方法として、AutoIt を用いる 方法もあります。これを使うと COM オブジェクトの形で簡単にクリップボードを 扱うことができます。 AutoIt は今回紹介するようなキーボートシミュレーション以外に、マウスの動作も シミュレーションの機能もあり、単独で役に立つアプリケーションです。 今回は、Ruby でキーボードシミュレーションを行う方法を紹介するという趣旨なので このソフトについてあまり説明しませんが、キーボードシミュレーションを行う場合は 調査する価値があるソフトです。 キーボードシミュレーションのソフトとして他に、 AutoHotKeyもあります。

zip_wsh.rb では、まずこれまで説明したとおり、このように直接 Win32API をたたく ことでクリップボードの値の取得しています。 しかしながら普通は今まで説明したようになんらかのライブラリや COM オブジェクト、 /dev/clipboard などを用いてクリップボードの操作を行うようにしましょう。

Clipboard クラスの詳細について

せっかく Win32API ライブラリを使用したクラスを書きましたので Clipboard クラスに ついて解説しましょう。 それでも Clipboard の操作のための Win32API の詳細は別の文献に譲ることにして ここでは、Ruby の Win32API ライブラリの使い方など、Ruby 的な箇所について 学んでいきます。

  OpenClipboard = Win32API.new('user32', 'OpenClipboard', ['I'], 'I');

の行を例に Win32API を呼び出す方法について学びましょう。 ここで、Win32API を直接呼び出せるように Win32API クラスのインスタンスを 生成しています。 Win32API クラスでは、いくつかのパラメータで初期化することで、Win32API を 直接呼び出すことができます。

OpenClipboard の MSDN のページを 参照しながら、読むと理解が進むかもしれません。 OpenClipboard を Visual Basic で呼び出すときは次のように Win32API を 定義しています。

Private Declare Function OpenClipboard Lib "user32" (ByVal hwnd As Long) As Long

‘user32’ は、dll の名前です。 ‘OpenClipboard’ は、呼び出したい関数の名前。 [‘I’] というのは、’OpenClipboard’ という関数の引数の型を指定しています。 [‘I’] の場合は、整数の引数を1つだけとるということを意味しています。 最後の引数の ‘I’ も整数の引数をとるということを意味しています。

このような Win32API をいくつか定義した後に、実際にクリップボードの値の取得を 行っています。

  def self.GetText
    result = ""

    while (h = OpenClipboard.Call(0)) == 0
      sleep 1
    end
    begin
        if (p = GlobalLock.Call(h)) != 0
          result = p;
          GlobalUnlock.Call(h);
        end
      end
    ensure
      CloseClipboard.Call
    end
    return result;
  end

ここで、getText というクラスメソッドを定義しています。 クラスメソッドの定義に、さまざまなクラスメソッドの定義の方法について 解説されています。

Win32API クラスのインスタンスの使い方は、これを見るとある程度想像がつくでしょうか?

    while (h = OpenClipboard.Call(0)) == 0
      sleep 1
    end

のように Win32API#Call メソッドを用いて、Win32API の呼び出しを行います。

考えられる応用と発展的話題

COM は Windows ではきわめて広範なアプリケーションに実装されていますが、 すべてのアプリケーションに実装されているわけではありません。 たとえば、Outlook Express には実装されていませんし、個別のアプリケーションで 実装されていないものは多いでしょう。

それでも自動制御をしたいような作業がある場合においては キーボードシミュレーションを行うことが有効でしょう。

また自分で作ったアプリケーションの GUI のテストを行いたいという需要もあるでしょう。 こういう場合もキーボードシミュレーションを行うことで、テストの自動化が可能です。 このような用途は多いでしょう。

さきほど説明した AutoIt を用いることができれば、マウスの操作もシミュレーション できます。 そのため、テストスクリプトの作成に大活躍するかもしれません。

それぞれの開発の参考にしてください。

ここでは紹介しませんでしたが、Wscript.Shell というコンポーネントは、 他にもさまざまな便利な機能があります。 たとえば、Popup メソッド は、Ruby のアプリケーションを作っているときに ユーザに何かを確認する必要があるときなどに便利です。 また、Windows Scritp Host にはネットワークドライブの割当などさまざまな 機能が提供されています。 @IT のWindows管理者のためのWindows Script Host入門 などが参考になります。活用してください。

Internet Explorer での POST メソッド

Web 巡回のテーマの最後に少し私自身が以前ハマったこととして、Internet Explorer で Post メソッドを用いて CGI に値を渡す方法について紹介したいと思います。

GET メソッドや CGI ではない単純なページでは、普通に URL を指定するだけなのですが、 POST で CGI を呼び出すときは、COM の型と Ruby の型との間の変換の関係で、 少し複雑な処理が必要になります。 その複雑な処理についてここで解説しようと思います。

次のスクリプトは「人力検索サイトはてな」に自動的にログインを行うスクリプトです。

hatena.rb

require 'win32ole'
include WIN32OLE::VARIANT

ie = WIN32OLE.new("InternetExplorer.Application")
ie.Visible = true

url = "https://www.hatena.ne.jp/sslregister"
def ie.navigate_post url,query_string
  header = "Content-type: application/x-www-form-urlencoded"
  postdata = query_string.unpack("c*")
  navi = self.ole_method("Navigate2")
  ret = self._invoke(navi.dispid, [url, nil, nil, postdata, header], [VT_BYREF|VT_VARIANT, VT_BYREF|VT_VARIANT, VT_BYREF|VT_VARIANT, VT_ARRAY|VT_UI1, VT_BYREF|VT_VARIANT])
  @lastargs = WIN32OLE::ARGV
  ret
end

query_string = "mode=login&backurl=http%3a%2f%2fwww%2ehatena%2ene%2ejp%2f&key=myname&password=mypassword"
ie.navigate_post url,query_string

key=myname&password=mypassword のところに自分のはてなのユーザ名とパスワードを 入力してください。

このスクリプトでは次の特異メソッドの定義が一番重要なところになります。

include WIN32OLE::VARIANT

def ie.navigate_post url,query_string
  header = "Content-type: application/x-www-form-urlencoded"
  postdata = query_string.unpack("c*")
  navi = self.ole_method("Navigate2")
  ret = self._invoke(navi.dispid, [url, nil, nil, postdata, header], [VT_BYREF|VT_VARIANT, VT_BYREF|VT_VARIANT, VT_BYREF|VT_VARIANT, VT_ARRAY|VT_UI1, VT_BYREF|VT_VARIANT])
  @lastargs = WIN32OLE::ARGV
  ret
end

まず、特異メソッドについて軽くおさらいしましょう。

Win32OLE 活用法の第2回ですでに使っていますが、Ruby にはインスタンスごとに 固有のメソッドを定義してあげる方法があります。 一般にメソッドは、クラスに対して定義し、そのクラスに属するすべてのオブジェクト に対してそのメソッドが定義されます。 この特異メソッドの定義では、その特異メソッドを定義したその単体のオブジェクトに 対してのみ、そのメソッドは定義されます。

この場合では、ie というオブジェクトに対してのみ、navigate_post というメソッドを 定義しています。 ie は、WIN32OLE クラスのインスタンスなわけですが、WIN32OLE クラスのすべての インスタンスに対して、 navigate_post が定義されたわけではありません。 それが、特異メソッドの意味です。

特異メソッドは、あるインスタンスにのみメソッドを追加定義したい場合に便利です。

特異メソッドとして定義した navigate_post メソッドは2つの引数をとります。 CGI の url と POST する文字列 query_string です。

ここで、navigate_post メソッドの中で実際に Navigate2 メソッドを呼び出し、 ページの移動を実行する _invoke メソッドの呼び出しについて学んでいきましょう。

  ret = self._invoke(navi.dispid,[url, nil, nil, postdata, header], [VT_BYREF|VT_VARIANT, VT_BYREF|VT_VARIANT, VT_BYREF|VT_VARIANT, VT_ARRAY|VT_UI1, VT_BYREF|VT_VARIANT])

WIN32OLE#_invoke メソッドは自分で Ruby の型から COM の型への変換を指定したいときに 使用できるメソッドです。

WIN32OLE#_invoke dispid,args,types

と _invoke は3つの引数をとります。dispid はメソッドの dispid で WIN32OLE_METHOD#dispid で取得できます。

args は引数の配列を指定し、types は WIN32OLE::VARIANT の定数で args の各々の引数の COM の型を指定します。types は args と同じ長さの配列になります。 VT_〜〜 という定数は WIN32OLE::VARIANT で定義された定数になります。

Navigate2 というメソッドは、第4引数に Post するデータの バイト配列を渡す仕様になっています。 しかしながら、Win32OLEリファレンスにありますように MFC の特殊な仕様のため、バイト配列は VARIANT に ラップすることが推奨されています。その結果、適切な型情報を得ることが できないため、文字列をバイト配列に変換すべきことを正しく判定できません。 Ruby の文字列を VARIANT に変換するときは、デフォルトでは BSTR という COM の型に変換 します。バイト配列には自動的に変換してくれないのです。

そのため、明示的に Ruby の文字列をバイト配列に変換する必要があります。 それが、VT_ARRAY|VT_UI1 という記述です。 このように指定することで、第4引数に VARIANT のときのデフォルトの BSTR ではなく バイト配列を渡すことができます。

ではどうやって、Ruby の文字列からバイト配列を得ることができるのでしょうか?

  postdata = query_string.unpack("C*")

という行で行っています。String#unpack(“C*”) と実行することで、 pack テンプレート文字列で説明されているように 文字列は 8bit 符号なし整数の配列になります。

Navigate2 メソッドの第5引数では、ヘッダを送ることになっています。 Post メソッドのときは “Content-type: application/x-www-form-urlencoded” を 渡します。これは一種のおまじないで POST メソッドのときはこうすると覚えて ください。

考えられる応用と発展的話題

バイト配列に関係する落とし穴は Win32OLE をやっているとときおり現れます。 今回は Internet Explorer の例でしたが、他の COM オブジェクトでも バイト配列を渡すには同様の方法が必要になります。

VARIANT が BSTR を受け付けるようになっているのか、バイト配列を受け付けるように なっているのかは、タイプライブラリを調べるだけでは判別不能です。 そのため、MSDN などでその COM オブジェクトのドキュメントを調べられる場合は、 そうすれば分かりますが、ドキュメントが充実していない場合などは試行錯誤する 以外に方法はないことがあります。 こういうときは、irb を使えばインタラクティブにいろいろと実験ができます。 いろいろと試しながら動かしていってください。

POST データを送ることができるようになれば、Web 巡回の幅が広がるでしょう。

また、VARIANT の型渡し関係で、トラブルが起こることはあります。 そのようなときは、WIN32OLE#_invoke で引数の型を明示的に指定することで 呼び出し可能かどうか試行錯誤してみると解決できる可能性があります。

おわりに

今回は、Web の自動巡回を題材に Windows Script Host と Internet Explorer の 制御について紹介しました。

Windows Script Host にはここで紹介したキーボードシミュレーション以外にも、 非常に多くの機能があります。 たとえば、ネットワークドライブのマウントや特殊フォルダへのアクセスなどが可能です。 Windows Script Host に関するドキュメントは非常に豊富です。 いろいろと調べてください。

次回は最終回です。Python や Perl など他の言語で COM を扱うときどのような違いがあるのかについて紹介していこうと思います。

 参考サイト

著書について

cuzic です。

関西 Ruby 勉強会などで積極的に Ruby コミュニティの活動をしています。 前回は、50人以上の参加者があつまり、非常ににぎやかで楽しかったです。

cuzic ははてなを使ってブログを執筆中です。興味のある方はここをクリックしてください。

Win32OLE 活用法 連載一覧