(\d+)" & _
"(?:.|\n)+?""(?:absmiddle|left)"">([^<]+)"
Set mc = re.Execute(Races(1, i))
For Each m In mc
Debug.Print , m.SubMatches(0), _
m.SubMatches(1), _
m.SubMatches(2)
Next
Next
End If
End Function
実行結果(イミディエイト ウィンドウ)は、こうなりました。
2009年1月17日(土) 1回中山5日
1R
1 1 ストロングロビン
1 2 エビスホウシュウ
2 3 アロウィー
2 4 トキノサミット
3 5 ボスオブザリンド
3 6 フレアリングローズ
4 7 ストロベリータイム
4 8 デイジー
5 9 ホッコーテイオー
5 10 セイユウミシェール
6 11 トーセンメロディ
6 12 クリノハッピー
7 13 レッドジェム
7 14 クールザヒート
8 15 ロジロマンス
8 16 ワイルドイマージュ
2R
1 1 ヴィーヴァアミーコ
2 2 シルクエスポワール
3 3 インディアンボス
4 4 オンワードサクラ
5 5 マイネルスラッガー
5 6 ステルススキャン
6 7 アロハアゲイン
6 8 マアーラウ
7 9 デルマアヌビス
7 10 アイズオブゾロ
8 11 アルマダ
8 12 ヒゼンリバイバル
3R
1 1 アルカディアシチー
1 2 スイートメロン
2 3 ユニヴァースガイ
2 4 ナパ
3 5 シルクライトアップ
3 6 ヴィーヴァジョーコ
4 7 セイウンラピッド
4 8 エアイグアス
5 9 オンワードアコール
5 10 ミスベルツリー
6 11 アドマイヤホーム
6 12 オーマイマミー
7 13 ミュウジックボーイ
7 14 グラスレジェンド
8 15 アイティヤマト
8 16 セイリュウザクラ
4R メイクデビュー中山
1 1 ランチャードーター
1 2 スイートメモリーズ
2 3 ベルモントアイリス
2 4 アンファス
3 5 イグナイトカフェ
3 6 マイネセレネ
4 7 グラスコロン
4 8 オーゾラヲマウトキ
5 9 エングレイス
5 10 ホワイトホーネット
6 11 カンシャノキモチ
6 12 サキノローゼン
7 13 バンダムパルフェ
7 14 カリオンレディ
8 15 ジェットリリー
8 16 ヴィーヴァドマーニ
5R
1 1 サマーディザイア
1 2 アントルシャカトル
2 3 メジロフューチャー
2 4 アコガレノダンス
3 5 マイネプリンセス
3 6 ジョウノリリー
4 7 シャルフミニョンヌ
4 8 スイートライラ
5 9 ユキノクイーン
5 10 ケージーカンザクラ
6 11 リキサンキャロル
6 12 チューベローズ
7 13 ハートオブウインク
7 14 トゥールザンレール
8 15 コイクレナイ
8 16 アトムスパンカー
6R メイクデビュー中山
1 1 クラウザーサン
1 2 ウインクドン
2 3 オリエンタルスカイ
2 4 リンガスアクター
3 5 マイネルブリオッソ
3 6 ヒシポジション
4 7 トウショウザウルス
4 8 ベルファンタジア
5 9 タイキシムーン
5 10 ネオアレキサンダー
6 11 ブルーデザフィーオ
6 12 ガートモンテス
7 13 ファブリックパネル
7 14 トーアショウリュウ
8 15 レイクメリット
8 16 オードフォーレ
7R
1 1 ヴィルヌーヴダスク
1 2 ナイスプロテクター
2 3 エイシンアマデウス
2 4 アポロビッグバン
3 5 グラスキング
3 6 ジョウキゲン
4 7 ブラウンバンガー
4 8 ラブラブガッキー
5 9 カネスラディカール
5 10 メジャーヴィーナス
6 11 サルバドールアスカ
6 12 ゴールドエンデバー
7 13 パックノオトウト
7 14 セイウンリファイン
8 15 モアザンスマート
8 16 ニュートン
8R
1 1 フェイクスパ
2 2 スガノゴールド
3 3 イマジンノココロヲ
4 4 マイネルスカイハイ
5 5 マチカネフクノカミ
6 6 テキサスイーグル
7 7 マダムビジュー
8 8 チャイコフスキー
8 9 ウインク
9R 初茜賞
1 1 エイワジョリー
1 2 グッドチョイス
2 3 ベルモントヤマユリ
2 4 ヒロアンジェロ
3 5 レティセントガール
3 6 シュウザンアイ
4 7 フレンチノワール
4 8 ヤサシイキモチ
5 9 フレンチムスメ
5 10 チャイニーズフレア
6 11 パープルカフェ
6 12 オースミマコ
7 13 ハイカックウ
7 14 ブルーポラリス
8 15 ファッシオドンナ
8 16 ツルマイクィーン
10R 初咲賞
1 1 ミヤビベガ
2 2 ベストオーカン
2 3 ショウナンラヴァー
3 4 ドレックセル
3 5 ムーンレスナイト
4 6 スノークラッシャー
4 7 ラヴォランテ
5 8 マイネルファーマ
5 9 コンベンション
6 10 サムデイシュアー
6 11 ベルモントルパン
7 12 シルクマンハッタン
7 13 マスラタケヲ
8 14 コーリンヴァリウス
8 15 トーセンジョーカー
11R ジャニュアリーS
1 1 サイボーグ
1 2 ヤマノルドルフ
2 3 ルミナスポイント
2 4 ヒシカツリーダー
3 5 ワーキングボーイ
3 6 スリーアベニュー
4 7 レキシントンシチー
4 8 ハギノトリオンフォ
5 9 ビッグジェム
5 10 ワールドハンター
6 11 タータンフィールズ
6 12 チョウカイシャトル
7 13 エーシンフォワード
7 14 ペプチドルビー
8 15 ガブリン
8 16 ウォーターオーレ
12R
1 1 サクラバレット
1 2 サラトガティプトン
2 3 テラモミリオネ
2 4 ステラーホープ
3 5 ジェヴォーナ
3 6 クインズプレイヤー
4 7 プリンセスドルチェ
4 8 カヴァリーノ
5 9 ドナヴィラージュ
5 10 ミラクルハニー
6 11 マイネウインク
6 12 カツヨカムトゥルー
7 13 シェリルピンク
7 14 ミラクルロンド
8 15 マルノサプライズ
8 16 アナナス
これができれば、後はイミディエイト ウィンドウに吐くのもテーブルに書き込むのも基本は一緒です。
煮るなり焼くなりお好きにどうぞ。
HTML DOM
HTML はもともと(あるていど)構造化された文書形式なわけですから、正規表現のようなある意味 力任せの文字列処理よりも、HTML DOM (Document Object Model) を使ってスマートに要素を取得したい、と思う方もいるでしょう。
正規表現同様、HTML DOM Parser にもさまざまな実装がありますが、Microsoft 社からであれば HTML DOM Parser として MSHTML ライブラリが Office 製品に同梱されています。したがって、Office から使うのであれば、まず MSHTML ライブラリが候補の筆頭に上がるはずです。
HTML DOM に詳しくない方は、オブジェクトブラウザで MSHTML ライブラリを眺めて探求するか、あるいは下記 msdn を参照してください。
- Microsoft Internet Explorer 5 およびドキュメント オブジェクト モデル
- DHTML のオブジェクトおよびコレクションを使用する
- DHTMLリファレンス
上記はかなり古い資料ばかりですが、一応 日本語でオンラインで読めます。
あと、自分の記憶では Office の CD-ROM のどこかにも英語版のヘルプがあった気がします。英語でも大丈夫な方は、HTMLREF.CHM で検索してみてください。CHM があれば、ローカルで閲覧できます。
現在では、(MS 独自仕様ではない) W3C DOM の解説であればいたるところで見つかるので、MDC 等で DOM を調べて、MSHTML との互換については使う部分だけ実地に確認していく方向が妥当かもしれません。
さて、MSHTML ライブラリを使って DOM を操作する場合は、まず IE ありきな使い方が一般的ですが、IE を表示してしまったらパフォーマンスがガタ落ちです。ここはぜひ、IE 抜きでパースしていただきたい。というか、IE 抜きでパースしたいんです。わたしが。
IE 抜きで手軽に MSHTML ライブラリを利用する方法としては、createDocumentFromUrl が有名ですが、このメソッドは URL の指定しかできません。GET ならそれでじゅうぶんでしょうが、今回のように POST が必須な場合はどうしたらよいのでしょうか。残念ながら、わたしには createDocumentFromUrl メソッドで POST する方法が分かりませんので、断念しました。
空の HTMLDocument に .ResponseBody (または .ResponseText) を write なり writeln なりする手もありますが、せっかく HTTP でストリーム (.ResponseStream) が取れるのに、文字列の方を受け渡すのは、どことなく隔靴掻痒な感じがします。たとえていうなら、蛇口からコップに水を入れて飲むような。それふつう? いやいや、体育の授業のあとは、やっぱ蛇口に直接口つけて飲みたいわけですよ。
というわけで、代替手段を調査した結果、次のようなことが分かりました。
HTMLDocument オブジェクトにストリームから HTML を Load させるには、IPersistStreamInit インターフェイスを使えばよい (ファイルからなら IPersistFile インターフェイス)
IPersistFile や IPersistStreamInit は、MSHTML ライブラリに定義されていないので、タイプライブラリを自作するか、自作した方のタイプライブラリを DL して使う必要がある
問題はですね、VB6 ならタイプライブラリが必要なのは開発時のみですからいいんですが、VBA の場合は実行環境にもタイプライブラリが必要になります。MDB ファイルと一緒に TLB ファイルも配布して、場合によっては参照設定もやり直して……っていうのは、できれば避けたい。ぜひとも避けたい。何としても避けたい。
タイプライブラリ不要な方法は無いだろうか…と探してみたところ、ありました。
「WebBrowser」でソースコードを取得するには?
全部 低レベル API で実装しています。クラシック VB || VBA は意外にも“やればできる子”だったというか、逆説的に改めてタイプライブラリのありがたみを再認識させられるというか、魔界の仮面弁士さんは経験値いくつなんだとか、まあいろいろな思いが錯綜するわけですが。
リンク先は IPersistFile インターフェイスを使う方で、自分が今回使いたいのは IPersistStreamInit の方ですからズバリではないんですが、IPersistFile から IPersistStreamInit への変更は OCIdl.h を見れば 5 秒で済みますから、もうほとんど完成物があるようなものです。魔界の仮面弁士さん、ありがとう。
というか、魔界の仮面弁士さんのご投稿以外に類似のコードがまったく見当たらないのですが、この 5 年間に IPersistStreamInit を必要とした VBA 屋さんは世界中にひとりもいなかったのでしょうか。何か、ニードレスの極北を目指しているようなヤな感じですが、気のせいでしょうきっと。皆さん DOM 使いたいですよね。使いたいはずです。使おうよ。ううっ…(だから泣くなよ)。
気を取り直して、検証用コードです。
短っ!
まあ検証用ですから短いんですけれど。
今回は HTMLDocument のローディングが完了するのを待機するため、 WithEvents でイベントシンクしています。したがって、アーリーバインディングかつ MSHTML ライブラリへの参照設定必須です。
また WithEvents を使用する関係上、フォームのクラスモジュールに書いています(フォームでなくとも、クラスモジュールであればよいです)。
ただしもちろん、イベントシンクを使わない(DoEvents でループしながら complete 待ちをするとか)のであれば、レイトバインディングで標準モジュールに書いてもかまいません。今回は気分でイベントシンクを使っただけです。
今回の主役です。前述の投稿を改変して PersistStreamInit というクラスを作成しました。クラスモジュール自体は掲載しませんが、サンプルに含まれていますので、落として任意のプロジェクトにインポートしてください。なおプロシージャの属性を拡張しているので、PersistStreamInit クラスを VBE のコードウィンドウ上でコピペしても動きません。必ずインポートしてください。
15 行目で PersistStreamInit オブジェクトに HTMLDocument を代入してキャストしています。
ここで = 演算子を使いたいがために、プロシージャの属性を拡張しました。Set にできなかったのが残念ですが、.NET 的と思えば許せるはず(え゛?)。
そして 16 行目で ResponseStream をロードしています。これで MSHTML ライブラリが HTML ソースをパースしてくれるわけです。ただし、すぐに DOM を参照しても解析が完了している保証はありません(というか、たいてい完了していません)から、ローディングが済んだらこのプロシージャはいったん抜けます。
ここで HTMLDocument の状態変化を検知して、完了("complete")だったら DOM にアクセスします。
今回は検証用ですから、タイトルを取り出して表示するだけですが、実際はここで、DOM を使ってあんなことやこんなことをするわけです。
実行結果はこんな感じです。
画像で見ると実になんとも脱力するキャプチャですが……、とりあえずちゃんと動いていそうです。
先述したように、実際は GET で事足りるなら IPersistStreamInit インターフェイスを使う必要はありませんが、POST の場合も IPersistStreamInit インターフェイス経由で ResponseStream をロードさせてやれば、上の検証デモとまったく同じように HTMLDocument にアクセスできるわけです。
言い換えれば、MS Access が得意とする Excel 連携などと同じく、オートメーションが使えるということですから、これこそ VBA 向きかもしれません。
単純な検証デモが無事動いたところで、出馬表を取得してパースするデモに移りしょう。
今回は、JRA のサイトから HTML を取得する部分を cJRA というクラスモジュールにラッピングしました。内容は前回と重複が多いため、割愛します。気になる方はサンプルを落として確認してみてください。
※ 当初のサンプルに収録漏れていたため、2009/4/10 にサンプルを更新しました。それ以前に落とした方は、お手数ですが再度ダウンロードしてください。
※ 2010/01/17 に、JRACard3.zip に Excel サンプルも追加しました。
このクラスは、「出走馬一覧」画面の HTML ソースを取得するたび(開催単位)に OnLoad イベントを発行し(つまり今回であれば6回)、全開催分を取得終了した時点で OnUnload イベントを(こちらは最後に1回だけ)発行するよう設計してあります。
では、フォームのクラスモジュール全コードです。
解説は割愛します。どのみち、あるていどクラスの設計を説明しないとこれ単独では完結していませんから、限界があります。
コードをすぐに理解しなくても、正規表現との違いをなんとなくニュアンスで感じていただければそれでじゅうぶんです。一読すれば、正規表現が文字列を切り刻んでいただけなのに対して、DOM はオブジェクトなので、プロパティやメソッド、コレクション等を駆使してデータを取得しているのがお分かりいただけるかと思います。
ただ、いかんせん出馬表のページには id も name もほとんど振られていなかったので、要素の特定がやっかいでした。
のあたりなんか、かなり無茶なテーブルのたぐり方になっています。たぶんこの辺は工夫次第でもっとうまく処理できるのかもしれません。また、もし XHTML のサイトであれば、XML Parser の方を使うという手もあるかもしれません。そうすれば要素や属性の取得に XPath が使えるはずですが、脱線するので深追いしません。
実行結果は、6開催分の出力を掲載すると長くなりすぎるので、こちらも割愛しますが、正規表現のサンプルと同一結果になりました。
最後に注意点をいくつか。
筆者環境(IE6 SP3 + mshtml.dll 6.0.2900.5726)で確認した限りでは、生成直後の HTMLDocument インスタンスのロケーションは「about:blank」になります。この文書に対してソースが流し込まれるので、相対参照アドレスで位置が指定されている外部ファイル (CSS や JS、画像等) は、軒並み参照に失敗します。目的しだいですが、今回のように HTML ソースそのものに必要な情報が含まれている場合であれば、さほど影響は無いでしょう。絶対参照アドレスであれば、外部ファイルもローディングされます。外部ファイルのアクティブコンテンツ (JavaScript や Flash 等) は、そのドメインに応じたセキュリティゾーンの扱いで、実行されたり実行されなかったりするものと思われます(ちゃんと検証はしていないので、推測です。間違っているかもしれません)。
一方、外部ファイルではなく HTML にインラインで記述されたスクリプトの場合、本来であればそのドキュメントの取得元ドメインに応じたセキュリティゾーンが適用されればよいのですが、先述のとおり「about:blank」の文書に対して Stream を流し込んでいるため、ローカルでスクリプトが実行されるものとして、以下のような警告が上がる場合があります。
回避方法はこちらに載っていますので、必要な方はご参照ください。
独自解析
一番大変な方法ですが、同時にもっとも高速な方法です。
正規表現にしても DOM にしても、COM のインスタンスを作成して、専用のエンジンが文書を解析します。これが非常にハイコストで、お世辞にも高速とはいえません。その代わり、開発者側は簡単な指定で目的の要素を取得できます。言い換えるなら、パフォーマンスを犠牲にして開発効率を上げているわけです。
裏を返せば、開発者側にあるていどのマンパワーをつぎこむ余裕と覚悟さえあれば、正規表現や DOM よりも高速な解析ロジックは簡単に組めます。あまりそういう事例を聞かないのは、ロジックをチューニングするよりも、PC のスペック向上で処理速度が短縮されるのを待つ方が、費用対効果的に正解だからでしょう。また、いままで5分かかっていた処理が5秒になるなら工数を割くかもしれませんが、10秒が5秒になるていどなら(それでも仮に処理速度が 2 倍になるならすごいことだと思いますが)、10秒くらい待っておけ、という感覚が一般的ではないかと思います。
しかし時には、とにかくできるだけ早く処理させたい、という状況があるかもしれません。
紹介済みの2手法で速度的に満足できない場合は、独自解析してください。
サンプルは示しません。単純に、InStr()、Mid$()、Left$()、Right$() 等の文字列操作系関数で地道に処理していくだけです。難しくはありませんが、面倒なので筆者はパスします。
ただし注意点をいくつか挙げておきます。
まず、& 演算子による文字列連結は途方もなくコストがかかるので、必要最小限にしてください。基本的には Mid ステートメントを使うか、あるいは RtlMoveMemory API をラッピングした StringBuilder クラス(のようなもの)を使ってもよいでしょう。
また $ 付きの関数が用意されているものは、積極的にそちらを使ってください。
他にも一般的なパフォーマンス関連の注意事項は、すべて守るようにします。
Microsoft 社の関連技術情報は、以下を参照してください。
あと、当然のことながら、ボトルネックは事前に確認しておいてください。
HTTP 通信待ちがボトルネックなのに解析ロジックをいじっても期待した効果は得られません。あくまで解析待ちがボトルネックになっている場合のみ、独自解析を検討してください。
さて、これで一通りデータの取得と解析ができるようになりました。しかし、ここまでは基本中の基本にすぎません。最寄の駅から目的の駅まで、何線に乗ってどこで乗り換えればよいか、ということが分かったレベルです。もちろん、その情報だけでも目的地へ行くことはできるでしょう。しかし、実用レベルのスクレイピングを行うには、他に知っておくべきノウハウがたくさんあります。もし毎日 電車通勤するなら、何両目が空いているから座れるぞとか、あそこへ行くなら何番出口から出ればすぐだとか、1両目は女性専用車両だから発車間際に駆け込んだら取り返しが付かないなどといった、ロケーションに即したサバイバル テクニックが存在するはずです。そして、それを知っているかどうかが、あなたの通勤生活の質を大きく左右するのです。
…え、なんですか。テーマが違う?
失礼しました。それを知っているかどうかが、あなたのスクレイピングの質を大きく左右するのです。
というわけで、次回「最適化編」(公開未定) へ続く。
ダウンロード
サンプル MDB のダウンロードは下記からどうぞ。
ZIP 形式で圧縮されています。Windows XP 以降の OS 上であれば、特に外部ソフトを使わず解凍できます。
Windows XP 未満の OS で解凍用のソフトが無い場合は、Vector 等のソフトウェアライブラリから入手する必要があります。
なおコードを見てお分かりのとおり、アーリーバインディングでコーディングしています。
WinHTTP ライブラリが存在しない環境(Windows ME/98/95)で実行する場合は、WinHTTP ライブラリの参照設定を外し、モジュール頭部の
を
に書き換えてから実行してください。