[ExcelVBA]ExcelVBAでスクレイピング！(VBA超初級者編)

#目次
+ はじめに
+ 概要
+ サンプルコード
+ サンプル画像と使用例
+ 終わりに
+ 参考にしたサイト
+ 追記(2020/4/08)

#はじめに
会社でも家庭でも何気に使うExcelファイル。一般的に設計書や家計簿を作る際に使うことが多いであろうが、こんな使い方もあるということを、忘備録も兼ねて投稿する。
#概要
さて、突然だが、ポケモンバトル用ポケモンデータ収集ツールを作ってみた。
(🌟完全に個人の趣味です🌟)
これは、スクレイピングという方法を用いて作成する。
ちなみに、スクレイピングとは、端的に言うと取得した情報を加工することである。(詳しくは[ココ](https://ja.wikipedia.org/wiki/%E3%82%A6%E3%82%A7%E3%83%96%E3%82%B9%E3%82%AF%E3%83%AC%E3%82%A4%E3%83%94%E3%83%B3%E3%82%B0)をクリック！)
なお、ポケモンのタマゴ技のデータの抽出がごく一部うまくいかないが、そこは見逃してほしい。

#サンプルコード
今回は、以下の4つのプロシージャを活用する。

```
Option Explicit
'ご利用の際は、ここから
'#If VBA7 Then
'Private Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal ms As LongPtr)
'#Else
'Private Declare Sub Sleep Lib "kernel32" (ByVal ms As Long)
'#End If
'ここまでをコメントアウトして下さい

Private Sub btnTeamMake_click()
On Error GoTo Err
    
    '新しいメンバーポケモンのシートを作成する
    Dim meinSheet As Worksheet
    Dim templateSheet As Worksheet
    
    Set meinSheet = ThisWorkbook.Worksheets("メインシート")
    Set templateSheet = ThisWorkbook.Worksheets("テンプレート")
    If meinSheet.Range("C4").Value = "" Then
        MsgBox "ポケモンの名前を入力してください。"
        Exit Sub
    Else
        Dim pokemonName As String
        pokemonName = meinSheet.Range("C4").Value
    End If
    
    '新しいメンバーのシートがある場合、シート作成処理を飛ばす。ない場合、シート作成処理発火
    Dim ws As Worksheet
    Dim flg As Boolean
    
    For Each ws In Worksheets
        If ws.Name = pokemonName Then flg = True
    Next ws
    
    If flg = True Then
        Dim newMemberSheet As Worksheet
        Set newMemberSheet = ThisWorkbook.Worksheets(pokemonName)
        newMemberSheet.Activate
    Else
        templateSheet.Copy After:=meinSheet
        ThisWorkbook.ActiveSheet.Name = pokemonName
        Set newMemberSheet = ThisWorkbook.Worksheets(pokemonName)
        newMemberSheet.Range("C5").Value = pokemonName
    End If
    
    'ポケモン徹底攻略サイトをIE(InternetExplorer)で起動
    Dim ieObject  As InternetExplorer
    
    Call ieView(ieObject, "https://yakkun.com/swsh/zukan/" & pokemonName)
    'aタグのリンクをクリックして同じウィンドウで表示
    Dim ieImageObject As Object
    
    For Each ieImageObject In ieObject.document.getElementsByTagName("a")
        If InStr(ieImageObject.innerText, pokemonName) > 0 Then
            ieImageObject.Click
            Call ieCheck(ieObject)
            Exit For
        End If
    Next
    
    Call createNewMemberSheet(ieObject, newMemberSheet, pokemonName)
    ieObject.Quit
Err:
    ieObject.Quit
End Sub
```
```
Private Sub ieView(objIE As InternetExplorer, _
           urlName As String, _
           Optional flgView As Boolean = True, _
           Optional ieTop As Integer = 0, _
           Optional ieLeft As Integer = 0, _
           Optional ieWidth As Integer = 600, _
           Optional ieHeight As Integer = 800)
    
    'IE(InternetExplorer)のオブジェクトを作成する
    Set objIE = CreateObject("InternetExplorer.Application")
    
    With objIE
        'IE(InternetExplorer)を表示・非表示
        .Visible = flgView
        .Top = ieTop  'Y位置
        .Left = ieLeft  'X位置
        .Width = ieWidth  '幅
        .Height = ieHeight  '高さ
        '指定したURLのページを表示する
        .navigate urlName
    End With
    'IE(InternetExplorer)が完全表示されるまで待機
    Call ieCheck(objIE)
    
End Sub
```
```
Private Sub ieCheck(objIE As InternetExplorer)

    Dim timeOut As Date
    
    '完全にページが表示されるまで待機する
    timeOut = Now + TimeSerial(0, 0, 20)
    Do While objIE.Busy = True Or objIE.readyState <> 4
        DoEvents
        Sleep 1
        If Now > timeOut Then
            objIE.Refresh
            timeOut = Now + TimeSerial(0, 0, 20)
        End If
    Loop
    
    timeOut = Now + TimeSerial(0, 0, 20)
    Do While objIE.document.readyState <> "complete"
        DoEvents
        Sleep 1
        If Now > timeOut Then
            objIE.Refresh
            timeOut = Now + TimeSerial(0, 0, 20)
        End If
     Loop
End Sub
```
```
Private Sub createNewMemberSheet(ByVal ieObject As Object, ByVal newMemberSheet As Worksheet, ByVal pokemonName As String)

    'ポケモンの種族値(class属性がleft、liの要素)を抽出
    Dim i As Integer
    
    For i = 1 To ieObject.document.getElementsByClassName("left").Length
        With newMemberSheet
            Select Case i
                Case 1
                    .Range("C11").Value = ieObject.document.getElementsByClassName("left")(i).innerText
                    .Range("C7").Value = ieObject.document.getElementsByTagName("li")(i).innerText
                Case 2
                    .Range("C12").Value = ieObject.document.getElementsByClassName("left")(i).innerText
                    .Range("C8").Value = ieObject.document.getElementsByTagName("li")(i).innerText
                Case 3
                    .Range("C13").Value = ieObject.document.getElementsByClassName("left")(i).innerText
                Case 4
                    .Range("C14").Value = ieObject.document.getElementsByClassName("left")(i).innerText
                Case 5
                    .Range("C15").Value = ieObject.document.getElementsByClassName("left")(i).innerText
                Case 6
                    .Range("C16").Value = ieObject.document.getElementsByClassName("left")(i).innerText
                Case Else
                
            End Select
        End With
        
        '新しいメンバーのポケモンの画像を取得(新しいメンバーの名前が含まれているimgタグ)、シートに貼り付け
        If ieObject.document.images(i).alt = pokemonName Then
            With newMemberSheet.Shapes.AddPicture(ieObject.document.images(i).src, True, False, Selection.Left, Selection.Top, 0, 0)
                .ScaleHeight 1, msoTrue
                .ScaleWidth 1, msoTrue
            End With
        End If
    Next i
    
    '新しいメンバーのポケモンの育成論のURL("の育成論を見る"の文字列が含まれているaタグ))を抽出
    Dim objLink As Object
    
    For Each objLink In ieObject.document.Links
        If InStr(objLink.outerHTML, pokemonName & "の育成論を見る") > 0 Then
            newMemberSheet.Range("B2").Value = objLink
            Exit For
        End If
    Next
        
    '新しいメンバーのポケモンの覚える技、タマゴ技の文字列、遺伝経路のURL("遺伝経路"の文字列が含まれているaタグ)を抽出
    Dim j As Integer
    j = 1
    Dim k As Integer
    k = 3
    Dim l As Integer
    l = 3
    Dim objLink2 As Object
    
    With newMemberSheet
        For Each objLink2 In ieObject.document.Links
            If InStr(objLink2, "move=") > 0 Then
                 .Range("E" & k).Value = ieObject.document.Links(j).outerText
                 k = k + 1
                If InStr(ieObject.document.Links(j).outerHTML, "遺伝経路") > 0 Then
                    .Range("H" & l).Value = objLink2
                    .Range("G" & l).Value = ieObject.document.Links(j + 1).outerText
                    l = l + 1
                End If
            End If
            j = j + 1
        Next
    End With
End Sub
```
#サンプル画像と使用例
以下の手順で作成してほしい。
1.Excelブックを新規作成する。
2.Excelブックのシート名を「メインシート」に変更する。
3.以下画像の通り、ボタンを配置する。A1:G2をセル結合し、タイトルを入力。また、C4:D4をセル結合し、B4に「ポケモン名:」と入力。こうしないと、マクロが機能しない。なお、登録するマクロ名は『btnTeamMake_click()』とし、コードは上記のコードをコピペするものとする。
<img width="460" alt="コメント 2020-03-29 174157.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/341829/27b7a826-c225-02cf-28b2-a9efeaca028f.png">
4.以下画像の通り、テンプレートシートを作成する。(個人的に)重要なのは、B5:C16、E1:E2、G1:H2だ。そのため、それらの箇所は、画像の通り入力してほしい。
なお、シート名は「テンプレート」とする。

・テンプレートシート
<img width="580" alt="コメント 2020-03-29 174217.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/341829/7d965a20-ebc5-afca-baa3-f28ee84f8f6c.png">
5.こうしてボタンを押すと、以下画像の通り、ポケモンのデータが出力されたシートが出来上がる。
・実行結果の一例
<img width="781" alt="コメント 2020-03-29 174246.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/341829/0516dc5f-fe04-4447-76b4-49a5a1b594b3.png">


#終わりに
VBA超初心者の方が事前準備なしに実際に作っていくとなると、かなり苦労するだろう。(具体的に、作成には5時間ほどかかると思われる)
なので、拙作のコードを以って、うまく応用していただきたい。
業務でもスクレイピングを使うこともあるだろうし、趣味のソシャゲのデータをスクレイピングしたいときもあるだろう。
その時にこの記事を思い出してくれると、嬉しいことこの上ない。
#参考にしたサイト
・[VBA コーディングガイドライン](http://www.thom.jp/vbainfo/codingguideline.html)
・[VBAのIE制御入門](https://www.vba-ie.net/)

#追記(2020/4/08)
この記事をご覧になった方は、VBAでも手軽にスクレイピングをできるじゃない！と思われただろう。
確かに、VBAでスクレイピングを行うのは比較的容易だ。しかし、思わぬ落とし穴も存在する。
それは、『汎用性が低い』ことだと、筆者は考えている。
上記の点は、VBAに限らず、pythonやjavascriptといったメジャーな言語でも同じことが言えるが、VBAの場合は、InternetExplorerプロパティの仕様のためだと考えている。
例えは、以下のコードをご覧いただきたい。

```
For i = 1 To ieObject.document.getElementsByClassName("left").Length
        With newMemberSheet
            Select Case i
                Case 1
                    .Range("C11").Value = ieObject.document.getElementsByClassName("left")(i).innerText
                    .Range("C7").Value = ieObject.document.getElementsByTagName("li")(i).innerText
                Case 2
                    .Range("C12").Value = ieObject.document.getElementsByClassName("left")(i).innerText
                    .Range("C8").Value = ieObject.document.getElementsByTagName("li")(i).innerText
                Case 3
                    .Range("C13").Value = ieObject.document.getElementsByClassName("left")(i).innerText
                Case 4
                    .Range("C14").Value = ieObject.document.getElementsByClassName("left")(i).innerText
                Case 5
                    .Range("C15").Value = ieObject.document.getElementsByClassName("left")(i).innerText
                Case 6
                    .Range("C16").Value = ieObject.document.getElementsByClassName("left")(i).innerText
                Case Else

            End Select
        End With

        '新しいメンバーのポケモンの画像を取得(新しいメンバーの名前が含まれているimgタグ)、シートに貼り付け
        If ieObject.document.images(i).alt = pokemonName Then
            With newMemberSheet.Shapes.AddPicture(ieObject.document.images(i).src, True, False, Selection.Left, Selection.Top, 0, 0)
                .ScaleHeight 1, msoTrue
                .ScaleWidth 1, msoTrue
            End With
        End If
    Next i
```
上記のコードで、
```
ieObject.document.getElementsByClassName("left")(i).innerText
```
とあるが、これはi番目の、クラス名がliftであるタグのテキストを出力するというコードである。このように記述するには、該当のサイトでF12キーを押下しHTMLを読み解くしかない。そこが面倒といえば面倒で、落とし穴になる。場合によっては、5番目のクラス名がtestのh5タグのテキストを抽出するといったように、直に場所を指定しなければならないこともままある。
長々と駄文を書き綴ったが、要は「スクレイピングには該当のサイトでF12キーを押下しHTMLを読み解くことが不可欠」ということを伝えたかっただけだ。
そのやり方は、本記事の趣旨に外れるので記載は控える。興味がある方は、まずは自力で調査することをお勧めする。