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

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

作成したマクロは[ダウンロード](https://note.com/bkg/n/n284f7c49d05f "")できるのでよかったらどうぞ

#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する度に必要)

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

```vb:

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する度に使うので書いて書いおくと楽チンです。

```vb:WaitResponse()

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

```

結果
![image.png](https://qiita-image-store.s3.amazonaws.com/0/208615/45b8abb5-b055-fc86-ecf8-38ab2207f9a5.png)




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

まず欲しいデータがHTMLのどこにあるのか特定しなければなりません。
場所を特定する際の情報としては以下が挙げられます。

| 特定の情報 | 使用するメソッド |
|:--|:--|
| タグ名 | <span ,<form,<p | 
| クラス名 | 各タグに記載されるclass="class_sample"のこと |
| id名 | 各タグに記載されるid="id_sample"のこと |

他にもありますが一旦ここまで。
上記を踏まえて、今回取得したい値のタグを見てみましょう。

ここではChromeの`デベロッパーツール`を使います。
`F12`を押せば開きます。

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

![image.png](https://qiita-image-store.s3.amazonaws.com/0/208615/5412ae0a-28be-9fda-4e8b-5ac7a413af85.png)

```vb

    Dim htmlDoc As HTMLDocument 'HTMLドキュメントオブジェクトを準備
    Set htmlDoc = objIE.document 'objIEで読み込まれているHTMLドキュメントをセット
    
    '---------------------------------
    '  footerの文字を取得
    '  今回はクラスを指定(getElementsByClassNameで)してタグを特定します
    '  (0)は「class="license"」がついてるタグの一つ目を指します
    '---------------------------------
    MsgBox htmlDoc.getElementsByClassName("license")(0).innerHTML

```

結果
![image.png](https://qiita-image-store.s3.amazonaws.com/0/208615/e56884d7-0b98-ea1e-5c22-10bf380a835d.png)

今回は指定した「class="license"」は一つのタグでしか使われていませんでしたが、
もし複数あってHTMLの上から数えて２つ目のタグの値が欲しい場合は、

```vb

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

```

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

```vb

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

```


#3 その他サンプル
これは随時更新していく予定です。

##3.1 idの場合

```vb

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

```

##3.2 他getElemen

```vb

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

```

##3.3 メソッド

```vb

    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のテキストエリアに値を設定

```vb

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

```

##3.5 javascript実行
```vb

    objIE.navigate "JavaScript:methodName"

```


#4.データ操作
これらを必ず使え！というわけではなく、使ったら便利なのと良く忘れるので書いておきます。

##4.1 Dictionary
ハッシュ(連想配列)として使う。
Collectionでも同じことができるが処理速度と便利なメソッドがある点においてDictionaryの方がよい

```vb: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と同じ感覚
ただ動作が顕著に遅いことがある。
沢山オブジェクトを作るとそうなる。

```vb:

'----------------
'宣言と初期化
'----------------
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

```