自動化厨のプログラミングメモブログ │ CODE:LIFE

Python/ExcelVBA/JavaScript/Raspberry Piなどで色んなことを自動化

【ExcelVBAでスクレイピング入門】Googleで検索順位を自動取得してみる④ ~2ページ目以降も取得する~

第三回では複数キーワードを順番に検索してExcelシートにキーワード、検索順位、ページタイトルを入力するところまでできました。

code-life.hatenablog.com

今回は検索結果一番下にある「次へ」を押していく機能です。 これが実装できれば1キーワード100位までとか取得できるようになりますね。

一回目と二回目

code-life.hatenablog.com

code-life.hatenablog.com

前回までのおさらい

こちらが前回までで作ってきたプログラム。

このコードに書き加えていく形で「1キーワードにつき上位100位までの検索結果を取得する」機能を実装していきます。

Public Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
Sub web_scraping()
    
    Dim oIE As InternetExplorer 'oIEという変数はInternet Explorerが入ると宣言
    Set oIE = New InternetExplorer 'oIEに新しいIEを起動して定義
    
    oIE.Visible = True 'IEを表示させる
    
    Dim keyword As String
    
    Dim i As Integer
    For i = 2 To Sheets("キーワード").UsedRange.Rows.Count
    
        keyword = Sheets("キーワード").Range("A" & i)
        
        oIE.Navigate2 "https://www.google.com/search?q=" & keyword 'URLにアクセス
        Call browser_wait(oIE)
        Call data_input(oIE, keyword)
        
        Sleep 3000 '3秒待機
        
    Next
    
    Sleep 3000 '3秒待機
    
    oIE.Quit 'IEを閉じる
    
End Sub
Sub data_input(oIE, keyword)
    
    'search_result に検索結果部分をページごとにリストとして格納
    Dim search_result_list
    Set search_result_list = oIE.Document.querySelectorAll(".srg .rc")
    
    'データシートの入力開始行番号を取得
    Dim writing_start_row As Long
        writing_start_row = Sheets("データ").UsedRange.Rows.Count + 1
    
    'search_result の個数分繰り返し処理
    Dim i As Integer
    For i = 0 To search_result_list.Length - 1
    
        '検索結果一つひとつを順番に参照してデータシートに格納
        With Sheets("データ")
            
            .Range("A" & i + writing_start_row) = keyword 'キーワード
            .Range("B" & i + writing_start_row) = i + 1 '検索順位
            .Range("C" & i + writing_start_row) = search_result_list.Item(i).querySelector("h3").innerText 'タイトル
            .Range("D" & i + writing_start_row) = search_result_list.Item(i).querySelector("a").href 'URL
            .Range("E" & i + writing_start_row) = search_result_list.Item(i).querySelector(".st").innerText 'ディスクリプション
            
        End With
    
    Next
    
End Sub
Sub browser_wait(oIE)
    
    '読み込みが完了するまで待つ
    While oIE.ReadyState <> READYSTATE_COMPLETE Or oIE.Busy = True
    
        DoEvents
        Sleep 100
        
    Wend
    
    '読み込み完了後の安定化待ち
    Sleep 200
    
End Sub

 

2ページ目以降の取得に対応する

まず基準となる取得件数を変数に用意

get_limit という変数に 100 を格納。

取得件数が100になるまで繰り返し処理するイメージ。

    Dim get_limit As Integer
        get_limit = 100

 

data_inputプロシージャで取得した件数をカウントする

取得件数上限に達するまで繰り返す処理を実装するのに必要な情報は「取得件数の設定」と「現在の取得件数」。

現在の取得件数が取得件数上限に達しない限り繰り返し処理をすることになります。

「繰り返し」と聞いてFor~Next文やWhile文を想像するかもしれませんが、今回は「自分自身をCallする」ことで繰り返し処理を実装してみましょう。

これ最近こういうやり方があることに気づいたんですが、メインプロシージャに値を返して処理させるよりシンプルに書けて気に入ってます。

 

受け側として引数を追加

取得件数上限としてget_limitを受け取る。

現在の取得件数としてget_countを受け取る。こちらはOptional引数(あってもなくても良い引数)として設定しつつ、As Integer = 0 と記述することで初期化を行います。

Sub data_input(oIE, keyword, get_limit, Optional get_count As Integer = 0)

For文の中にカウンターを記述

get_count = get_count + 1

 

End Subの直前に条件文を記述

get_count(現在の取得件数)がget_limit(取得件数上限)に満たない場合は自分自身を呼び出します。

その際に現在の取得件数を次回に引き継ぐためにOptional引数のget_countを忘れず指定。

    If get_count < get_limit Then
        Call data_input(oIE, keyword, get_limit, get_count)
    End If
    
End Sub

 

次へ をクリックさせる

次のページの取得を開始する前にページを移動させる必要がありますね。

まずはセレクターをチェック。

f:id:maru0014:20190602230353p:plain
デベロッパーツールでリンクのセレクタを確認

IDpnnextが設定されていたのでこれを使います。クリック後にはページ遷移が発生するのでブラウザーのビジー状態を待機するようにbrowser_waitを呼び出します。

また、連続アクセスによる負荷を軽減するため3秒待機も入れておきましょう。

    If get_count < get_limit Then
        oIE.Document.querySelectorAll("#pnnext").Click '次へ をクリック
        Call browser_wait(oIE)
        Call data_input(oIE, keyword, get_limit, get_count)
    End If

ここまででdata_inputプロシージャは以下のようになります。

Sub data_input(oIE, keyword, get_limit, Optional get_count As Integer = 0)
    
    'search_result に検索結果部分をページごとにリストとして格納
    Dim search_result_list
    Set search_result_list = oIE.Document.querySelectorAll(".srg .rc")
    
    'データシートの入力開始行番号を取得
    Dim writing_start_row As Long
        writing_start_row = Sheets("データ").UsedRange.Rows.Count + 1
    
    'search_result の個数分繰り返し処理
    Dim i As Integer
    For i = 0 To search_result_list.Length - 1
    
        '検索結果一つひとつを順番に参照してデータシートに格納
        With Sheets("データ")
            
            .Range("A" & i + writing_start_row) = keyword 'キーワード
            .Range("B" & i + writing_start_row) = i + 1 '検索順位
            .Range("C" & i + writing_start_row) = search_result_list.Item(i).querySelector("h3").innerText 'タイトル
            .Range("D" & i + writing_start_row) = search_result_list.Item(i).querySelector("a").href 'URL
            .Range("E" & i + writing_start_row) = search_result_list.Item(i).querySelector(".st").innerText 'ディスクリプション
            
            get_count = get_count + 1
            
        End With
    
    Next
    
    '取得件数上限に満たない場合は自身をCall
    If get_count < get_limit Then
        oIE.Document.querySelector("#pnnext").Click '次へ をクリック
        Call browser_wait(oIE)
        Sleep 3000 '3秒待機
        Call data_input(oIE, keyword, get_limit, get_count)
    End If
    
End Sub

 

Call data_inputの引数に取得件数上限を追加

20行目Call data_inputget_runkを追加。

Call data_input(oIE, keyword, get_limit)
Sub web_scraping()
    
    Dim get_limit As Integer
        get_limit = 100
    
    Dim oIE As InternetExplorer 'oIEという変数はInternet Explorerが入ると宣言
    Set oIE = New InternetExplorer 'oIEに新しいIEを起動して定義
    
    oIE.Visible = True 'IEを表示させる
    
    Dim keyword As String
    
    Dim i As Integer
    For i = 2 To Sheets("キーワード").UsedRange.Rows.Count
    
        keyword = Sheets("キーワード").Range("A" & i)
        
        oIE.Navigate2 "https://www.google.com/search?q=" & keyword 'URLにアクセス
        Call browser_wait(oIE)
        Call data_input(oIE, keyword, get_limit)
        
        Sleep 3000 '3秒待機
        
    Next
    
    Sleep 3000 '3秒待機
    
    oIE.Quit 'IEを閉じる
    
End Sub

ここまでで実行してみると・・・

f:id:maru0014:20190602231138p:plain
検索順位のカウントが正常にできてない!

For文のイテレータiを検索順位のカウントに用いていたため呼び出すたびにリセットされちゃっていますね。

.Range("B" & i + writing_start_row) = i + 1 '検索順位

これをget_countに変更すれば2ページ目以降の順位も正常に記録できます。

.Range("B" & i + writing_start_row) = get_count + 1 '検索順位

これでいい感じに取得できた!

f:id:maru0014:20190602231703p:plain
11位~もちゃんとカウントされている

 

ディスクリプションが無い場合に対応する

と思いきやエラー発生。

f:id:maru0014:20190602231813p:plain
ディスクリプションが無いパターン・・・

そんなやつに出くわすとは予想外でした。

まぁ、無いなら取得しないように条件文を加えるだけですね。

 

要素の有無を判別する

querySelectorAll(".st").Lengthでディスクリプション要素の数をカウントし、0じゃないときだけ取得するようにIF文を書き加えます。

ここで注意してほしいのはquerySelectorでなくquerySelectorAllにするということ。

要素をリストとして取得するquerySelectorAllでなければlengthメソッドが機能しません。

            If search_result_list.Item(i).querySelectorAll(".st").Length <> 0 Then
                .Range("E" & i + writing_start_row) = search_result_list.Item(i).querySelector(".st").innerText 'ディスクリプション
            End If

この状態で先程のエラー箇所を実行してみると取得するコードはスキップされることが確認できました。

f:id:maru0014:20190602232345p:plain
ディスクリプション要素が0なのでスキップされた

 

まとめ

  • 次のページへ移動できた
  • 2ページ目以降も順位をカウントできた
  • 取得対象要素が無いパターンに対応できた
  • For文を使わずに繰り返し処理できた

いかがでしたでしょうか。

Optional引数やリンククリックでのページ繊維、要素の存在チェックなどスクレイピングでよく使うやつはでてきましたね。

検索エンジン以外でもこういったツールを使うとなるとログイン処理が必要になったりでフォームへのIDパスワード入力がでてきます。また今度紹介できたらいいですねー。

あとはPythonを使うパターンやAPI経由での取得とかも。そのうちね。