Edited at

[VBA]30分あればできるVBAスクレイピング


0 前置き

昨今、様々な言語でスクレイピングが出来ますが、VBAなら開発環境構築が不要でサイトから引っ張ってきた値をCsvに出す必要もなく直接セルに設定できるのは中々魅力的だと個人的に思います。


1 事前準備


1.1 必要な環境

Excel

Internet Explore

※バージョンについては知らない

これだけでスクレイピングができる環境が整います。他の言語に比べるとかなりお手軽だと思いません?


1.2 ライブラリの追加

標準のライブラリだとDOM操作、およびスクレイピングはできないのでライブラリを追加します。

①Excelを開いたらVBEを開く(Alt+F11)

②メニューバー→「ツール」→「参照設定」

③ずらずらとライブラリが並んでいるが、

その中から以下の二つを探してチェックを入れる。

Microsoft HTML Object Library

Microsoft Internet Controls

チェックを入れたら「OK」ボタンを押す。

これで追加完了。

この2つを入れることではIEの操作、HTMLの操作ができるようになります。


2 実際にスクレイピング


2.1スクレイピング処理の流れ

①IEオブジェクトを作成

②IEオブジェクト.navigateでサイトにアクセス

③読み込み待ちをする(navigateする度に必要)

④HTMLからタグ名や、クラス、id名を検索条件として欲しい値の場所を特定する


2.2 IEでサイトにアクセスする例

①IEオブジェクトを作成

②IEオブジェクト.navigateでサイトにアクセス

③読み込み待ちをする(navigateする度に必要)

今回はサンプルとして「ゴールドポイントカード」のサイトにアクセスします。


Sub testIE()

Dim objIE As InternetExplorer 'IEオブジェクトを準備
Set objIE = CreateObject("Internetexplorer.Application") '新しいIEオブジェクトを作成してセット

objIE.Visible = True 'IEを表示

objIE.navigate "https://secure.goldpoint.co.jp/gpm/authentication/index.html" 'IEでURLを開く

Call WaitResponse(objIE) '読み込み待ち

End Sub

下記のSubはnavigateする度に使うので書いて書いおくと楽チンです。


WaitResponse()


Sub WaitResponse(objIE As Object)
Do While objIE.Busy = True Or objIE.readyState < READYSTATE_COMPLETE '読み込み待ち
DoEvents
Loop
End Sub


結果


2.3 データの特定

④HTMLからタグ名や、クラス、id名を検索条件として欲しい値の場所を特定する

まず欲しいデータがHTMLのどこにあるのか特定しなければなりません。

場所を特定する際の情報としては以下が挙げられます。

特定の情報
使用するメソッド

タグ名
<span ,<form,<p

クラス名
各タグに記載されるclass="class_sample"のこと

id名
各タグに記載されるid="id_sample"のこと

他にもありますが一旦ここまで。

上記を踏まえて、今回取得したい値のタグを見てみましょう。

ここではChromeのデベロッパーツールを使います。

F12を押せば開きます。

今回はフッターに記載されているライセンスの記述文字を取得します。


Dim htmlDoc As HTMLDocument 'HTMLドキュメントオブジェクトを準備
Set htmlDoc = objIE.document 'objIEで読み込まれているHTMLドキュメントをセット

'---------------------------------
' footerの文字を取得
' 今回はクラスを指定(getElementsByClassNameで)してタグを特定します
' (0)は「class="license"」がついてるタグの一つ目を指します
'---------------------------------
MsgBox htmlDoc.getElementsByClassName("license")(0).innerHTML

結果

今回は指定した「class="license"」は一つのタグでしか使われていませんでしたが、

もし複数あってHTMLの上から数えて2つ目のタグの値が欲しい場合は、


MsgBox htmlDoc.getElementsByClassName("license")(1).innerHTML

全てのタグを取得したい場合は以下のようにします


'---------------------------------
'添え字を指定せず存在するだけループさせれば全て取得できます
'---------------------------------
For Each str In htmlDoc.getElementsByClassName ("license")
Debug.Print "出力:" & str
Next str


3 その他サンプル

これは随時更新していく予定です。


3.1 idの場合


'idの場合は必ず一つしかないため添え字は不要です
htmlDoc.getElementById ("id").innerHTML


3.2 他getElemen


htmlDoc.getElementsByName 'コレクションを返す
htmlDoc.getElementsByClassName 'コレクションを返す
htmlDoc.getElementsByTagName 'コレクションを返す
htmlDoc.getElementsByTagNameNS 'コレクションを返す


3.3 メソッド


htmlDoc.Links 'ドキュメント内のリンクすべて取得
htmlDoc.getElementById("").Click
htmlDoc.getElementById("").className
htmlDoc.getElementById("").innerHTML
htmlDoc.getElementById("").innerText
htmlDoc.getElementById("").outerHTML
htmlDoc.getElementById("").outerText
htmlDoc.getElementById("").contains


3.4 formのテキストエリアに値を設定


objIE.document.forms("form").Item("ここにid").Value = "huga"
objIE.document.forms("form").Item("ここにid").Value = "foo"


3.5 javascript実行


objIE.navigate "JavaScript:methodName"


4.データ操作

これらを必ず使え!というわけではなく、使ったら便利なのと良く忘れるので書いておきます。


4.1 Dictionary

ハッシュ(連想配列)として使う。

Collectionでも同じことができるが処理速度と便利なメソッドがある点においてDictionaryの方がよい


test


Sub test()

'宣言と初期化
Dim obj As Object
Set obj = CreateObject("Scripting.Dictionary")

' キーと値の設定
obj.Add "foo", 100
obj.Add "bar", 200
obj.Add "hoge", 300

' 値の取得
Dim key As Variant
For Each key In obj
MsgBox obj(key)
Next key

'または
hash("foo") '->100

Set obj = Nothing

End Sub


メソッド名
説明

Add(key,item)
キーと値のセットを追加

Exists(key)
指定されたキー(key)の存在

Item(key)
キー(key)に紐づく値


4.2 Collection

基本的にDictionaryと同じ感覚

ただ動作が顕著に遅いことがある。

沢山オブジェクトを作るとそうなる。


'----------------
'宣言と初期化
'----------------
Dim collec As Collection
Set collec = New Collection

'----------------
'値追加
'----------------
''keyと値のセット
collec.Add "キー","値"

''値のみ
collec.Add "値"

'※値の型にバラツキがあっても問題ない
'→String,配列,integerが混ざっててもおけ

'----------------
'値の取り出し
'----------------
''keyを指定して取り出し
cllec("キー") '→値

''拡張forで取り出し
For each value In collec
Debug.print value
Next value