VBAによるWebコンテンツの取得


はじめに

同僚から、「ExcelからWebサイトをスクレイピングするVBAスクリプトがうまく動かなくなったので直して欲しい」という依頼を受けた際に調べたことをまとめました。

VBAでWebのコンテンツを取得するには、以下の4つの方法が代表的なようです。

  • InternetExplorer(以下、「IE」)をVBAから操作する。
  • HTMLDocumentのcreateDocumentFromUrlメソッドを使う。
  • XMLHTTP60を使い文字列としてコンテンツを取得しHTMLとしてパースする。
  • Selenium Webdriver を使う。

現時点での僕の方針を先に書くと、

  1. 自由にソフトウェアがインストールできる環境なら、VBA以外の言語を検討
  2. 静的ページが対象で、ページ内のリンクを辿る必要があるならHTMLDocument
  3. 静的ページが対象で、ページ内のリンクを辿る必要が無いならXMLHTTP
  4. スクレイピング対象コンテンツがJavascriptで読み込まれるならIE

と、なります。

以下では、それぞれの方法のメリット・デメリットをまとめました。
Seleniumを使う方法以外については、使用例のコードを書きました。

それぞれの手法のメリット・デメリット

Internet ExplorerをVBAから操作する

ブラウザをプログラムから操作するので、およそ人間の操作であれば、ほぼそれをトレースして、プログラムに行わせることができます。
対象であるWebサイトがJavascriptでコンテンツを読み込んでいる場合には、この方法がもっとも有力に思えます。
一方プログラムを職場なんかの縛られた環境で利用する場合は、その環境でいつまでIEが利用できるのか注意を払っておく必要があります。

メリット

  • Javascriptで読み込まれるコンテンツへの操作にも対応できる。
  • プログラムがブラウザの操作と結びついているので、非エンジニアにも何が起こっているのか分かりやすい。

デメリット

  • 取得したいコンテンツとは関係ない画像なども読み込むため、場合によっては遅い。
  • 「IEを使い続けることは技術的負債」 のように言われている中で、新規にIEによるスクレイピングのコードを書くことは不安(職場の端末でIEがサポートされなくなる将来に、一度に改修を行う必要がでるかもしれない)。

HTMLDocumentのcreateDocumentFromUrlメソッドを使う

1つのライブラリにしか依存しておらず、コードもシンプルに書けます。
静的なウェブサイトのリンクを巡回してコンテンツをダウンロードするような用途であれば、この方法がオススメです。

メリット

  • IEに依存しない。
  • 余計なコンテンツを取得しないので多分速い。

デメリット

  • Javascriptで読み込まれるコンテンツには対応できない。

XMLHTTP60で文字列としてコンテンツを取得しHTMLとしてパースする

HTTP GET メソッドを送って、それに対するレスポンス文字列をHTMLとしてパースする方法です。
欲しいコンテンツが相対URLで書かれていない場合や、HTMLとしてパースする必要のない、WebAPIを叩いてXMLやJSONを取得する場合には、この方法がオススメです。

メリット

  • 多分最も高速。
  • 一度文字列を介するので、オブジェクトの管理が楽。

デメリット

  • パースされたHTMLDocumentは元のURLの情報を持たないので、相対URLで書かれたリンクを扱うのに苦労する。
  • Javascriptで読み込まれるコンテンツには対応できない。

Selenium Webdriver を使う(未検証)

VBAからSelenium Webdriverを扱うライブラリである SeleniumBasic に関する、Web上のいくつかの解説を読んだだけで、実際にはコードを書いていません。

僕の印象では、VBAでスクレイピングをせにゃならん状況と言うのは、やむなくVBAを使ってることが多いです。
Selenium Basicを入れられるくらい自由に環境をいじれるなら、XlwingsでPythonとExcelを連携するとか、メインの処理はもっと書きやすい言語でやって最後にCSVを吐いてExcelから読ませるとかした方が良いようにも思います。

メリット

  • Javascriptで読み込まれるコンテンツにIE以外のブラウザで対応できる
  • 別の言語でSelenium Webdriverに慣れてる人には書きやすい

デメリット

  • 環境を整えるのが大変
  • 複数のツールに依存するので問題の切り分けやデバッグが大変そう

それぞれの方法でHTMLDocument を取得する関数

引数としてURLを与えて、HTMLDocumentオブジェクト(への参照)を返す関数として実装してみました。
返されるHTMLDocumentオブジェクトの生存期間が、他のオブジェクトの影響を受ける場合は、そのオブジェクトをグローバル変数とはせず、関数の引数として一緒に渡すことにしました。
こうしておく方が、変数名の変更なんかが自由にできると思います。

以下全ての例で、HTMLDocumentを使うため、VBEのメニューから
[ツール] → [参照設定]→ "Microsoft HTML Object Library" にチェック
をしておく必要があります。

IE

  • [ツール] → [参照設定]→ "Microsoft Internet Controls" にチェックしておく
  • 呼び出し元のIEオブジェクト(以下では objIE)が閉じられると、この関数が返したHTMLDocumentも参照先が無くなって以降の操作ができなくなるので、IEの生存期間には注意すること
Private Function getHTMLDoc_withIE(ByVal url As String, ByRef objIE As InternetExplorer) As HTMLDocument
  ' IE で対象ページを開く
  Call objIE.Navigate(url)

  ' 完全にページが表示されるまで待機する
  Do While objIE.Busy = True Or objIE.readyState <> READYSTATE_COMPLETE
    DoEvents
  Loop
  Do objIE.Document.readyState <> "complete"
    DoEvents
  Loop

  ' 関数の返り値に設定
  Set getHTMLDoc_withIE = objIE.Document
End Function

HTMLDocumentのcreateDocumentFromUrlメソッド

  • 呼び出し元となってるHTMLDocumentオブジェクト(以下では htmldoc)が失われると、この関数が返したHTMLDocumentも参照先が無くなって以降の操作ができなくなるので、オブジェクトの生存期間には注意すること
Private Function getHTMLDoc_withHTMLDoc(ByVal url As String, ByRef htmldoc As HTMLDocument) As HTMLDocument
  ' HTMLDoc オブジェクトで対象ページを開き関数の返り値に設定
  Set getHTMLDoc_withHTMLDoc = htmldoc.createDocumentFromUrl(url, vbNullString)

  ' 完全にページが表示されるまで待機する
  Do getHTMLDoc_withHTMLDoc.readyState <> "complete"
    DoEvents
  Loop
End Function

XMLHTTP60

  • [ツール] → [参照設定]→ "Microsoft XML v6.0" にチェックしておく
Private Function getHTMLDoc(ByVal url As String) As IHTMLDocument
  Dim http As XMLHTTP60
  Set http = New XMLHTTP60

  ' GETメソッドで対象ページを取得
  Call http.Open("GET", url, False)
  Call http.Send()

  ' 待つ
  Do While http.readyState <> READYSTATE_COMPLETE
    DoEvents
  Loop

  ' 取得した文字列をHTMLとしてパースし関数の返り値に設定
  Set getHTMLDoc = New HTMLDocument
  Call getHTMLDoc.Write(http.responseText)
End Function

参考にしたWebサイト

VBAでWebページを取得する方法 @ so-zou.jp
InternetExplorerオブジェクトのdocument.readyStateプロパティ @ VBAのIE制御入門
HTMLDocument Class @ Microsoft .NET
VBAのスクレイピングを簡単楽にしてくれるSelenium @ エクセルの神髄
Excel VBAコーディング ガイドライン案 @ Qiita