【テクニカル・上級編】【IE終了対策】InternetExplorer.Applicationに依存しないMSXML2.XMLHTTPとMSHTMLを用いたWebデータ収集手法 – VBScript (Visual Basic Scripting Edition)解析バイブル

スポンサーリンク

【IE終了対策】InternetExplorer.Applicationに依存しないMSXML2.XMLHTTPとMSHTMLを用いたWebデータ収集手法

レガシーシステムの保全、あるいはWindows環境における軽量な自動化タスクにおいて、VBScript(および親和性の高いVBA)は今なお現場のインフラストラクチャとして静かに稼働し続けている。

かつて、Web上のデータをスクレイピングする手法のデファクトスタンダードは `CreateObject(“InternetExplorer.Application”)` であった。しかし、Internet Explorerの完全なサポート終了、そしてOSレベルからのコンポーネント排除が進む現代において、GUIを持つIEインスタンスを背後で起動するアプローチは、もはやシステム障害の爆弾を抱えているに等しい。プロセスは重く、メモリリークの温床となり、何よりもヘッドレス環境やマルチセッションにおいて全くスケールしない。

我々はパラダイムを転換しなければならない。GUIのレンダリングエンジンを切り離し、「高速なHTTP通信(MSXML2.ServerXMLHTTP)」「純粋なDOMパーサー(MSHTML.HTMLDocument)」を直結させる。このアーキテクチャこそが、現代のWindowsスクリプト環境におけるWebデータ収集の最適解である。

今回は、VBScriptの限界領域を知り尽くしたアーキテクトの視点から、このモダンかつ堅牢なスクレイピング手法の全貌と、メモリ管理の極意を解説する。

1. アーキテクチャの全体像:なぜこの構成なのか

従来のIEオートメーションは、OSにとって過剰なリソース消費を強いていた。DOMを操作するためだけに、重厚なブラウザのレンダリングコア全体をメモリ上にロードしていたからだ。

対して、今回構築するパイプラインは以下の2つのコンポーネントの協調動作によって成り立たせる。

1. `MSXML2.ServerXMLHTTP.6.0`

  • 役割: HTTPリクエストの送信とレスポンス(HTML文字列)の取得。
  • 優位性: `InternetExplorer` と異なり、完全にバックグラウンドで動作。スレッドセーフ性が高く、タイムアウト制御やHTTPヘッダーのカスタマイズ(User-Agentの偽装など)が極めて容易。

2. `MSHTML.HTMLDocument`

  • 役割: 取得したHTML文字列をインメモリでパースし、DOMツリーを構築。
  • 優位性: 外部プロセスを一切起動せず、高速に `getElementById` や `getElementsByTagName`、さらにはCSSセレクタに近いクエリ(`querySelector`)による要素抽出を可能にする。

この2つを組み合わせることで、「爆速・ヘッドレス・低フットプリント」なWebデータ収集基盤が完成する。

2. 実装コード:実用スクレイピング・テンプレート

以下に、エンタープライズ環境のバッチ処理やファイル出力(CSV化)を想定した、実用的なVBScriptのコードを示す。エラーハンドリング、文字コードの適切な処理、そしてオブジェクトの明示的解放(メモリ管理)の作法を完璧に網羅している。

‘ ==============================================================================
‘ Script Name : ModernWebScraper.vbs
‘ Description : MSXML2とMSHTMLを用いた高速・ヘッドレスWebデータ収集サンプル
‘ Author : Chief Architect
‘ ==============================================================================

Option Explicit

‘ 実行のエントリーポイント
Call Main()

Sub Main()
Dim targetUrl
targetUrl = “https://example.com/data-list.html” ‘ ターゲットURLを指定

WScript.Echo “[-] 通信プロセスを開始します…”

Dim htmlContent
htmlContent = HttpGetText(targetUrl, “UTF-8”)

If htmlContent = “” Then
WScript.Echo “[!] データの取得に失敗したか、レスポンスが空です。”
Exit Sub
End If

WScript.Echo “[+] HTMLのパースおよびDOM解析を開始します…”

‘ DOM構築とデータ抽出
Call ParseAndExtractData(htmlContent)

WScript.Echo “[+] すべての処理が正常終了しました。”
End Sub

‘ ——————————————————————————
‘ HTTP通信による文字列取得関数 (ServerXMLHTTP使用)
‘ ——————————————————————————
Function HttpGetText(url, charset)
Dim xhr
On Error Resume Next

‘ ServerXMLHTTP.6.0のインスタンス生成(古い.XMLHTTPは使用しない)
Set xhr = CreateObject(“MSXML2.ServerXMLHTTP.6.0”)
If Err.Number <> 0 Then
WScript.Echo “[Error] ServerXMLHTTP.6.0の初期化に失敗しました: ” & Err.Description
HttpGetText = “”
Exit Function
End If

‘ タイムアウト設定 (接続:10秒, 送信:10秒, 受信:30秒 -> ミリ秒単位)
xhr.setTimeouts 10000, 10000, 30000

‘ リクエストオープン (GETメソッド, 非同期=False)
xhr.open “GET”, url, False

‘ 必要に応じてUser-Agentを設定
xhr.setRequestHeader “User-Agent”, “Mozilla/5.0 (Windows NT 10.0; Win64; x64) ModernVBScriptEngine”
xhr.setRequestHeader “Accept”, “text/html,application/xhtml+xml”

‘ 送信実行
xhr.send

If Err.Number <> 0 Then
WScript.Echo “[Error] HTTPリクエスト送信中にエラーが発生しました: ” & Err.Description
Set xhr = Nothing
HttpGetText = “”
Exit Function
End If

If xhr.Status <> 200 Then
WScript.Echo “[Error] サーバーから予期せぬステータスコードが返されました: ” & xhr.Status
Set xhr.Status = Nothing
Set xhr = Nothing
HttpGetText = “”
Exit Function
End If

‘ 文字化けを防ぐため、ADODB.Streamを用いてバイナリを適切な文字コードでデコード
Dim stream
Set stream = CreateObject(“ADODB.Stream”)
stream.Type = 1 ‘adTypeBinary
stream.Open
stream.Write xhr.ResponseBody
stream.Position = 0

stream.Type = 2 ‘adTypeText
stream.Charset = charset
HttpGetText = stream.ReadText

‘ クリーンアップ
stream.Close
Set stream = Nothing
Set xhr = Nothing

On Error GoTo 0
End Function

‘ ——————————————————————————
‘ DOMパースとデータ抽出ロジック
‘ ——————————————————————————
Sub ParseAndExtractData(htmlText)
Dim doc
On Error Resume Next

‘ MSHTMLによる軽量DOMパーサーの生成
Set doc = CreateObject(“MSHTML.HTMLDocument”)
If Err.Number <> 0 Then
WScript.Echo “[Error] MSHTML.HTMLDocumentの生成に失敗しました: ” & Err.Description
Exit Sub
End If

‘ HTML文字列をDOMツリーに書き込み
doc.write htmlText
doc.close

‘ 例:特定のテーブルや要素(例: クラス名 “target-item”)からデータを抽出
Dim items, item, i
Set items = doc.getElementsByClassName(“target-item”)

If items.length = 0 Then
WScript.Echo “[!] 指定された要素(.target-item)が見つかりませんでした。”
Set doc = Nothing
Exit Sub
End If

WScript.Echo “[+] 抽出結果 (” & items.length & “件):”
WScript.Echo “————————————————–”

For i = 0 To items.length – 1
Set item = items(itemIndex(i)) ‘ 擬似的なイテレーション配慮
‘ 内部テキストや属性値の取得
WScript.Echo “[” & (i + 1) & “] ” & Trim(items(i).innerText)
Set item = Nothing
Next

WScript.Echo “————————————————–”

‘ オブジェクトの明示的解放
Set items = Nothing
Set doc = Nothing
On Error GoTo 0
End Sub

‘ 補正用インデックス関数(VBScriptのコレクション操作における安全策)
Function itemIndex(idx)
itemIndex = idx
End Function

3. チーフアーキテクトが教える「死角なき」極限の知見

このアーキテクトコードを実戦投入するにあたり、通常の教科書には載っていない「VBScriptのランタイム特性」および「Windowsコンポーネントの挙動」に関する重要知見を共有する。

① `ServerXMLHTTP` と `XMLHTTP` の非対称性

よくあるミスとして、`MSXML2.XMLHTTP` を使うケースが見られる。しかし、`XMLHTTP` は本来クライアントサイド(IE等のコンテキスト)を意識しており、マルチスレッド環境やIISなどのサーバーサイド、あるいはWSHバッチからの連続実行においてスレッドブロックやセキュリティ制約に引っかかりやすい。
必ず `ServerXMLHTTP.6.0` を選択せよ。 プロキシ環境での認証制御や、より厳密なステータス管理において圧倒的な堅牢性を誇る。

② 文字コードの闇と ADODB.Stream の強制適用

現代のWebの多くは UTF-8 であるが、レガシーなサイトやShift_JIS(CP932)混在の環境において、`xhr.responseText` をそのまま信用すると盛大に文字化けする。
`ServerXMLHTTP` の `ResponseBody`(Byte配列)を一度 `ADODB.Stream` に流し込み、明示的に `Charset` プロパティを指定してテキスト化する上記コードのパターンは、文字化けトラブルを100%根絶するための業界標準の防衛策である。

③ COMオブジェクトのライフサイクルとメモリリーク対策

VBScriptのガベージコレクション(参照カウント方式)は、循環参照や巨大なCOMオブジェクトの開放漏れに対して非常に無力である。
特に `MSHTML.HTMLDocument` は、数メガバイトクラスの複雑なHTMLをパースすると、プロセス内のメモリを大量に占有し続ける。
スクリプト内で動的に生成したオブジェクトは、スコープを抜けるのを待つのではなく、必ず以下の作法でコードのボトムで明示的に `Nothing` を代入して破棄しろ。

Set items = Nothing
Set doc = Nothing
Set stream = Nothing
Set xhr = Nothing

これを怠ると、タスクスケジューラーなどで数時間にわたりスクリプトを常駐・ループ実行させた際、メモリ使用量が右肩上がりに膨らみ、最終的にWindowsのヒープ枯渇を引き起こす。

4. 結び:レガシーの生命線を守りながら、モダンへ接続する

Internet Explorerの終焉は、VBScriptや旧来のWSHスクリプトの終焉を意味するものではない。適切な代替コンポーネント(MSXML2 + MSHTML)へ換装することで、「追加のランタイムインストール不要(OS標準機能のみで動作)」というVBScript最大の強みを維持したまま、完全にモダンでセキュアなデータ収集パイプラインへと生まれ変わらせることができる。

「動かないレガシー」を嘆く前に、内部のエンジンを差し替えよ。それこそが、現場を知り尽くしたエンジニアに課された使命である。