ブラウザもSeleniumも使わずExcelVBAでスクレイピング

以前こういう記事を書きました。

このとき、SeleniumBasicをインストールしないといけないのとWebdriverをダウンロードしてこないといけないのがめんどうでした。

イメージとしてはこんな感じです↓

SeleniumBasicを介してHTTP通信するときのイメージ

さらに以前こういう記事を書きました。

このときはSeleniumは使わず(使えず)、Webdriverを使ってHTTP通信することになりました。これも結構めんどうでした。

Webdriverを介してHTTP通信するときのイメージ

そこで今回は、こういうイメージのことをやりたいと思います。

SeleniumBasicもWebdriverも使わずにHTTP通信するイメージ

ただし、Webdriverを使わないのでClickとか動的な情報は難しいです。

できるのかもしれませんが、私にはやり方を見つけることはできませんでした。

静的情報をとってくるだけなら、今回紹介する方法でなんとかなりますという話です。

コード

今回こちらのサイトを参考にしました。

【ExcelVBA】HTTP/HTTPS通信でWebページを取得する

https://qiita.com/nkojima/items/c11f6369f4c32b06c90b

それから、上記のサイトでも言及がありますが、こちら↓のサイトも参考にしました。

エクセルVBAでHTTPリクエストをする最も簡単なプログラム

https://tonari-it.com/excel-vba-http-request/

Option Explicit

'HTTP通信用クラス

Private client As Object
Private Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal ms As Long)

'コンストラクタ


Private Sub Class_Initialize()
    Set client = CreateObject("MSXML2.ServerXMLHTTP")
End Sub

'デストラクタ

Private Sub Class_Terminate()
    Set client = Nothing
End Sub

'引数のURLをGETで取得
'
'url:URL文字列
'return:取得したページ
'
Public Function GetPage(url As String) As String

    client.Open "GET", url
    
    Sleep 2000 'スクレイピングのマナー(1000以上を設定のこと)
    client.Send
    
    'readyStateの値が4で読み込みが完了
    Do While client.readyState < 4
        DoEvents
    Loop
    
    
    Dim statusCode As Integer
    statusCode = client.Status
    
    Dim responseText As String
    Dim lNum1 As Long, lNum2 As Long, stMoji As String
    
    'HTTPのステータスコードが200(OK)以外であれば、ステータスコードなどを返す
    If (statusCode = 200) Then
    
        '取得した文字列を変数へ
        responseText = client.responseText
        
        '文字コード把握
        lNum1 = InStr(1, responseText, "charSet")
        lNum2 = InStr(lNum1, responseText, ">")
        stMoji = Mid(responseText, lNum1, lNum2 - lNum1)
        stMoji = Replace(stMoji, "charSet", "")
        stMoji = Replace(stMoji, "=", "")
        stMoji = Replace(stMoji, "/", "")
        stMoji = Replace(stMoji, " ", "")
        stMoji = Replace(stMoji, """", "")
        Select Case LCase(stMoji)
            Case "utf-8"
                GetPage = client.responseText
            Case Else
                GetPage = StrConv(client.responseText, vbUnicode)
        End Select
    Else
        GetPage "HTTP StatusCode:" & statusCode & "," & vbCrLf & "HTTP StatusText:" & client.statusText
    End If
End Function

上記はクラスモジュールに記述します。クラスモジュールの名称は「Http」にします。

クラス名はHttpとする

このクラスを使うサブは下記のようなコードになります。

Sub HTTPリクエスト()

    Dim client As Http 
    Set client = New Http
    
    Dim response As String
    response = client.GetPage("https://www.yahoo.co.jp/")
    
'    Dim localHtml As HTMLDocument    '←これだとコンパイルエラーになる
    Dim localHtml As Object
    Set localHtml = New HTMLDocument 'CreateObject("htmlfile")

    localHtml.write response    'HTTP通信でとってきたテキストデータをlocalHtml変数に書き込む

    Debug.Print localHtml.innerText

    Set localHtml = Nothing
    Set client = Nothing
End Sub

localHtmlというローカルの変数にHTML文書を書き込んでしまいます(localHtml.write responseがそれです)ので、ネットワーク負荷がそれほどかからず、相手先サーバに迷惑をかけなくて済むのではないかと思いました。

参照設定

基本的に私は参照設定は最小限にしたい派なんですが、今回ばかりはMicrosoft HTML Object Libraryにチェックを入れました。

参照設定でMicrosoft HTML Object Libraryにチェックを入れる

使えるプロパティとメソッドが分からないので参照設定しておいた方が入力補助してくれて便利です。

解説と問題点

せっかく参照設定したのに、どういう訳かDim localHtml As HTMLDocumentとした状態でlocalHtml.write responseと書くと、コンパイルのときエラーとなりました。

writeメソッドを使おうとするとコンパイルエラーとなる

理由は不明です。

これ以外にも、htmlfileには不思議な現象がたくさんありました。

?Typename(localHtml)とイミディエイトペインで入力すると、HTMLDocumentとでるくせに、CreatObjectで”HTMLDocument”とするとだめで、”htmlfile”としないといけません(全部小文字)。

× CreateObject(“HTMLDocument”)

○ CreateObject(“htmlfile”)

これも理由は不明。マイクロソフトがそういう風に作った(そういう風にしか作れなかった)としか説明できません。

それから、As HTMLDocumentとしてコーディングしているときに、入力補助で選択肢にgetElementByIdとかgetElementsByClassNameとか出てきますが、動作確認ができたのはgetElementsByTagNameだけでした。

getElement系がいくつか選択肢にあるけど、実際に使えたのはgetElementsByTagNameだけ

これも理由は不明です。

器だけ作ったけど、中身は実装しなかった・・・とか?

私の使い方がうまくないのかもしれません。階層の深いHTML文書では、Childの要素を順番にたどっていかないといけないのかもしれません・・・。これを確認する時間がありませんでした。

要素をとってくるコード

上記のコードではただHTMLドキュメント全体をとってくるだけですが、それではあまりにも役に立たないので、もう少し内容を細かく取得するようにしたのが次のコードです。

Option Explicit
Sub HTTPリクエスト()

    Dim client As Http
    Set client = New Http
    
    Dim response As String
    response = client.GetPage("https://www.yahoo.co.jp/")
    
'    Dim localHtml As HTMLDocument
    Dim localHtml As Object
    Set localHtml = New HTMLDocument 'CreateObject("htmlfile")
    
    localHtml.write response

    Dim nodes As Object
    Set nodes = localHtml.getElementsByTagName("div")
    
    Dim node As HTMLUListElement
    Const cstClassName As String = "TRuzXRRZHRqbqgLUCCco9"  'tabTopics1の記事一覧のクラス名
    For Each node In nodes
        If node.className = cstClassName Then
            Debug.Print node.innerText
        End If
    Next node
    
    Set nodes = Nothing
    Set localHtml = Nothing
    Set client = Nothing
End Sub

タグdivで要素を検索し、クラス名が”TRuzXRRZHRqbqgLUCCco9″であるならinnerTextを吐き出すようにしてあります。

“TRuzXRRZHRqbqgLUCCco9″というのは、下図のようにブラウザのF12で確認したところ8件だけしかヒットしませんでした。ちょうどtabtopics1~8のところだけで使われているようでしたので、これを使ってみました。

上記のコードを実行するとこんな感じです。

Newの文字まで取得してしまうのは仕様です。

以上、SeleniumBasicもWebdriverも使わず、ExcelVBAでスクレイピングをしてみました。

意外とできるものですね。ただ、XPathが使えないので開発には時間がかかります。