Web から情報を取得したいというニーズは、いまや当たり前のものになりました。
対象サイトが Web API を提供している場合、話は簡単です。しかし Web API を提供しているサイトばかりではありません。というより、数で言うなら Web API を提供していないサイトの方が圧倒的多数です。そんなサイトの中にほしい情報があった場合は、否応なく Web ページを直接取得して情報を解析・抽出する必要があります。
原始的と言うか地道と言うか、とにかくこのベタなやり方を称して「Web スクレイピング」(文脈によっては単に「スクレイピング」)と呼びます(海外では Web harvesting と呼ぶ場合も多いですが、なぜか日本ではこっちはほとんど用いられないようです)。
Access VBA で Web スクレイピングを行う方法については、本サイトにすでに関連トピックが二つ存在します。
いずれも公開後5年を経過(本トピック執筆時点)し、内容が古くなっていることから、現在主流である WinHttp ライブラリにフォーカスして、新たにトピックを起こすこととしました。
また今回は、概念や基本サンプルのみの構成ではなく、スクレイピングを実際に行うプロセスを追うことで、より実践的な内容を心がけました。
Web 開発者であれば基本すぎていまさら誰も書かないようなことばかりですが、EUC 中心の VBA においては、知らない人はまったく知らないというのがこのスクレイピングの世界です。これを機会に、オフィスの中だけでなく、開いた外の情報とリアルタイムにつながる Access アプリ開発の一助にしていただければ幸いです。
なおトピック中で使用するサンプルコードは、本サイトの他トピックと同様、エラーハンドリングは最小限もしくは省略しています。実際にお使いになる場合は、必要と思われるエラー処理を各自で実装してください。以後、サンプルコードごとに断り書きを付けることは特にいたしませんので、あらかじめご承知おきください。
まず最初に、使う道具について最低限の知識を入れておきましょう。
WinHTTP は、古参の HTTP ライブラリ WinInet の後継ライブラリです。ただし、必ずしも下位互換ではないため、場合によっては WinInet との使い分けが必要になる場合もあるようです。
WinHTTP ライブラリについての詳細は、下記でご覧いただけます(日本語版の公式ドキュメントは探しきれませんでした。所在をご存知の方はお知らせください)。
WinHTTP ライブラリに関する疑問の九割は、上記のどこかに答えが載っています。
不明点があれば、真っ先に上記 msdn を確認する癖をつけましょう。
これによると、WinHTTP ライブラリの最新バージョンは WinHTTP 5.1 で、以下の OS からサポートされています。
ということは、Vista や Windows 7 なんかでは当然使えるとして、Windows ME/98/95 なんかでは使えないということになるようです。もし現状で 9X 系 OS もサポートしたいのであれば、WinInet ベースと言われる XMLHTTP ライブラリの方がよいでしょう(MS もそう言っています)。
セキュリティの観点からは、かつてないほどウィルスの脅威が叫ばれているこのご時勢、サポートの切れた 9X 系 OS に少なくとも HTTP アクセス可能な場所で現役でいてはほしくないという思いはありますが、現実には完全否定するのも難しいでしょうから、後方互換性を考慮するなら WinHTTP と XMLHTTP を両方ハンドリングする必要があります。
WinHTTP と XMLHTTP の細かな違いはいろいろありますが、簡単に言えば XMLHTTP は WinInet ベースのため、ユーザーレベルで動作し、IE のインターネットオプションの影響を受けます。実地に確認はしていませんが、動作原理から考えるとおそらくログオン ユーザー レベルのキャッシュの影響も受けると思われます。
一方 WinHTTP は WinInet に依存せず、サービスの一部として使用されることを想定して設計されたため、システムレベルで動作します。このことから、ユーザーレベルの IE のインターネットオプションに動作が左右される恐れはありませんが、逆にプロキシ設定や認証周りの設定を IE から勝手に引き継ぐことも無いわけで、セッションごとに適切な設定が要求されるでしょう。したがって、今まで XMLHTTP を使っていて、認証周りの設定を特に意識しなくてもなんとなく IE から自動で設定を取得して接続できていたものが、WinHTTP に切り替えたとたんに動かなくなる、という事態が予想されます。この辺は、WinHTTP を使うのであれば開発側でハンドリングしなければなりません。
ここで WinHTTP のすべてを解説するのは不可能ですし、そもそも自分自身、必要に迫られたときに必要な部分を調べるだけで今に至っているので、ライブラリの全貌を知悉しているわけでもなんでもありません。繰り返しになりますが、詳細は前述の msdn および サポート技術情報にて各自がそのとき必要と思われる事項について調査するようにしてください。
WinHTTP ライブラリはインターフェイスとして API と COM が提供されていますが、VBA のような COM ありきの言語から使うのであれば、圧倒的に COM の方が相性がよいので、基本的な使い方はこんな感じになります。
あくまで基本例なので、このまま使うといろいろ問題がありますが、イメージはつかめるのではないでしょうか。
変数 sHTML の中には、ちょうど当サイトのトップページをブラウザに表示させて、IE なら [表示]-[ソース] を選んだときに表示されるのと同じ HTML ソース文字列が格納されます。
実際には、NT 系 OS でも古い環境になると WinHTTP 5.0 や、さらにその前のバージョンなし ProgID のライブラリしかない可能性もあります。またすでに触れたとおり、9X 系ではそもそも WinHTTP ではなく XMLHTTP を使う必要があります。したがって、あるていど汎用性を考慮するなら、上位のライブラリからトライしていって、インスタンスを作成できた時点でそれを返却するような仕組みがどうしても必要になります。
というわけで、個人的には以前から CreateWinHttpRequest() という、その辺をラッピングした関数を使っているので、このトピックにおいてもそれを使うものとします。
モジュール自体はサンプルに含まれていますが、該当箇所のみ抜きだすとこんな感じです。
なおサーバ製品向けに ServerXMLHTTP というクラス(名前に "XMLHTTP" を含みますが、WinInet ではなく WinHTTP ベース)もありますが、Access VBA をサーバで動かすというのはちょっと考えにくいので、対象に含めていません。
一応せっかちさん向けに警告しておきますが、上のコードをコピってもサブルーチンがないのでコンパイルできませんよー。イメージをつかんでもらうために載せているだけなので、現物はちゃんとサンプルを落としてモジュール mCreateObject ごと持っていってください。
それから、基本的な GET、POST はどのクラスでも使えますが、バージョンに依存する拡張機能は、当然ですがあるバージョン以前では使えない、ということが出てきます。自分の使うメソッドやプロパティがどのバージョンまで(から)有効か無効かは、各自 msdn やオブジェクト ブラウザ、開発・テスト環境等で調査・検証してください。特に XMLHTTP は WinInet ベースになるため、WinHTTP とはプロキシ制御やクッキー、キャッシュの取り扱いが根本的に異なると考えられます。CreateWinHttpRequest() 関数は、とにかく基本的な HTTP アクセスを可能にするインスタンスを(それが何であれ)実行環境から取得するのが目的の関数です。いかなる環境下でも WinHttp の全機能を使えるようにする、というような魔法の何かではまったくありません。そのあたり誤解なきよう、ここで特に強調しておきたいと思います。
CreateWinHttpRequest() は個々のニーズに応じて改変してください。たとえば WinHttp 5.1 から追加された新機能に依存するコードを書くなら、下位バージョンのライブラリを返されても困るはずです。むしろストレートにエラーにしてくれたほうがいいわけで、その辺は適宜ご判断ください。
さて、今回サンプルとして取り上げるサイトは、@ Web API を提供していなくて、Aそれなりにニーズがありそうなもの、がふさわしいといえます。ウチのようなマイナーなサイトは、@は満たしていてもAで失格ですね。はっはっは。
この手のネタで真っ先に出てくるのは昔から「株」と相場が決まっていますが、株関連はあちこちでやり尽くされていて新味がないので、今回は目先を変えて JRA の競馬情報を例にとってみたいと思います。
厳密には JRA-VAN が有りますから@に該当するかどうか微妙なところですが、有料サービスを利用したくない人にとっては無いのと同じですから、スクレイピングのニーズはそれなりにあるのではないでしょうか。
なお YU-TANG は競馬をやらないので、JRA のサイトには今回の企画のためにはじめてアクセスしました。
予備知識はまったくありませんので、調査や試行錯誤のプロセスをありのままお伝えしたいと思います。
まず対象サイトがスクレイピングを明示的に禁止していないかどうか確認する必要があります。
スクレイピングは人間による Web サーフィンと違って、寄り道せずに必要な情報だけ持っていきます。
サイトによっては、途中でいろいろ広告を見てもらわないと商売にならない、情報のタダ取りでは困る、という姿勢を明確に打ち出している場合もあります。バーチャルとはいえ、ひとつの大きな社会ですから、その一員として、相手の嫌がることはしないのが大原則です。
もっともスクレイピングの認知度は一般的にはまだまだ低いですから、サイトポリシーでわざわざそれについて触れているところは極めてまれです。
JRA の場合はHPの利用についてというページがありますので、そこを一読してスクレイピングが明示的に禁止されていないことを確認すればよいでしょう。
はい、確認しました。
OK です(あくまで 2008/12 末時点の話です)。
ちなみにまったくの余談ですが、スクレイピングを明示的に禁止している数少ないサイトのひとつに moug があったりします。禁止になった切っ掛けになんとなく本記事が噛んでいる気もしますが、たぶん気のせい気のせい…。
続いて、何を取得するのか明確にしましょう。
これは一般的な手順ではありません。ふつうは取得したいコンテンツが先に決まっているはずですから、これはあくまで、見たこともないのに JRA を先に決めてから訪れるという「サイコロの旅」的 場当たり進行の為せるわざです。良い子は真似しないように。
ではトップページを物色。
左上のサイドメニューの中に、「出馬表」というのがあります。
これなんかいいんじゃないでしょうか。
というわけで、クリックしてみます。
「出馬表 開催選択」という画面へ遷移しました。
上の方に「今週の出馬表」という欄がありますね。
ここに直近の土日のレースが載っているようです。
とりあえず先頭の「5回中山7日」というリンクをクリック。
「出馬表 レース選択」という画面へ遷移しました。
レース別にずらっと並んでいるようです。
1 レースずつ見ていると面倒なので、右上にある「全てのレースを表示」というリンクをクリックしてみます。
「出走馬一覧」という画面に、全 12 レースの出馬表がずらりと出てきました。
これは「5回中山7日」の分だけですが、こういう表が他にも 5 つ、全部で 6 つあるわけですね。なるほど。
決まりです。
今回のスクレイピングは、JRA のサイトから最新の全出馬表を取得することを目標にしてみましょう。
取得した後どうするのかはよく分かりませんが、好きな人はいろいろ分析したりするんでしょうね、きっと!
まず小手調べに、JRA のトップページを取得するとしましょう。
出馬表をスクレイピングする上では「出馬表 開催選択」画面へ直接行けばいいので、必ずしもトップページを取得する必要はなさそうですが、一応いまさっき手動で行ったプロセスを再現してみるという意味でトライしてみます。
今回は「Microsoft WinHTTP Services, version 5.1」に参照設定して、アーリーバインディングでコーディングしてみます。
今回、ライブラリが自分の環境に存在するのは分かっているので、開発時はインテリセンスによるコード補完の効くアーリーバインディングの方がラクです。
不特定多数の環境に配布するような場合は、リリース時に参照設定を外してレイトバインディングに変更すれば問題ないでしょう。一応それを見越した仕掛けも施しておくこととします。
では標準モジュール全コードです。
解説です。
モジュールレベルで条件付きコンパイル定数 EARLY_BINDING を宣言して、True を設定しています。
これは 16〜20 行目で次のように使用されています。
これにより、WinHTTP 5.1 が存在しない環境も想定してリリースする場合は、条件付きコンパイル定数 EARLY_BINDING に False を代入するよう書き換え、参照設定を外してコンパイルするだけで済みます。
各オブジェクト変数の宣言部は一切書き換え不要です。
一箇所、二箇所ていどだと さほどメリットを感じないかもしれませんが、こうした宣言がプロジェクト全体に数十箇所も分散していると、けっこう大きな違いになるのはお分かりいただけるかと思います。
WinHTTP ライブラリとはで説明した下位互換用の汎用関数 CreateWinHttpRequest() を使って、実行環境における最新の HTTP スタックを取得します。
実行環境があらかじめ特定できる場合は、
なり
なり決め打ちしてしまっても問題はないでしょう。
肝の、HTML ソース取得部分です。
インターネット接続を "GET" で Open して、リクエストを Send します。解説というか、見たまんまですが。
ちなみに Open メソッドの第三引数 Async に False を指定して同期実行していますが、True を指定すると非同期実行になります。そうするとたとえば、ユーザーによる途中キャンセルなどを受け付けることができるようになりますが、ここでは取り上げません。東証一部上場全銘柄の日足6か月分とかをダウンロードしようとしているわけではないので、このていどはすなおに待ちましょう。
ステータスを確認して、OK 以外はエラーにします。
HTTP ステータスでは 200 番台は正常に分類されていますが、今回は 200 以外想定していないので、それ以外の値が返された場合は一律エラーにしています。何が適切かは状況によって異なるでしょうから、処理の用途に応じて変更してください。HTTP ステータス コードについては解説が豊富にありますので、各自で検索してください。
JRA のサイトは文字コード Shift-JIS なのですが、VBA 6.0 の内部処理文字コードは Unicode なので、変換をかけます。
変換しないと、取得した HTML の日本語が文字化けします。
これがたとえば Google や Yahoo! JAPAN のように文字コードが UTF-8 のサイトであれば、ふつうに
で、文字化けせずに HTML ソースを取得できます。
なお euc-jp の場合は、VBA に組み込みの変換関数がありません。検索すれば、COM コンポーネントによる変換方法や文字コード変換ルーチンがたくさん見つかりますので、好きな手法で responseBody から変換するとよいでしょう。
この辺は、対象サイトの文字コードに応じて切り替えてください。
ちなみにレスポンスヘッダーや HTML ヘッダーを調べることで、動的に文字コードを判別することも(あるていど)可能ですが、スクレイピングの場合は事前に対象サイトを特定して調査するのがふつうです。実行時でないと文字コードがはっきりしないという状況はちょっと考えにくいので、この辺は決め打ちでもいいかな、と思います。もちろんどれだけ事前に調査しても、サイトのリニューアル時に文字コードが変わる可能性は常に残りますが、そういう場合は文字コードだけではなくデザインも大きく変わるのがふつうです。そうなればどのみちスクレイピング ロジック自体の見直しが必要になるでしょうから、文字コード判定にだけ力を入れてもあんまり意味無い気がします。スクレイピングの宿命として、何かが変わったときにはやるき茶屋か庄や並みによろこんでコードを修正する覚悟で臨みましょう。
とりあえず、今回はトップページから取りたいものが特に無いので、イミディエイト ウィンドウに流して終わりです。
文字数が多いので、HTML ソースの前半はスクロールアウトしてしまうと思いますが、文字化けしていないことさえ確認できれば、まあいいでしょう。
続いて「出馬表 開催選択」画面の取得です。
URL を確認しましょう。
.html ファイルのようですね。URL は固定っぽいので、ダイレクトにこのページを取得すれば良さそうです。
試しにブラウザのアドレスバーに URL を手入力してみて、「出馬表 開催選択」画面が表示されることを確認しておきましょう。
……アレ?
URL は間違っていないはずですが、なんで手入力するとパラメータエラーですか?
ちょっと待ってください。もう一度 URL を見直してみます。
「出馬表 開催選択」画面
「出馬表 レース選択」画面
「出走馬一覧」画面
。。。。。
全部 同じ URL じゃないですか!
ちなみに先ほどの JRA トップページを取得するコードの URL を上の URL に変えて流してみましたが、やはりパラメータエラーの HTMLソースが取得できただけで、出馬表は影も形もありませんでした。
さて、どうしましょう。
というところで、次回へと続く…
サンプル MDB のダウンロードは下記からどうぞ。
ZIP 形式で圧縮されています。Windows XP 以降の OS 上であれば、特に外部ソフトを使わず解凍できます。
Windows XP 未満の OS で解凍用のソフトが無い場合は、Vector 等のソフトウェアライブラリから入手する必要があります。
なお本文中でも触れているように、アーリーバインディングでコーディングしています。
WinHTTP ライブラリが存在しない環境(Windows ME/98/95)で実行する場合は、WinHTTP ライブラリの参照設定を外し、モジュール頭部の
を
に書き換えてから実行してください。