第三回では複数キーワードを順番に検索してExcelシートにキーワード、検索順位、ページタイトルを入力するところまでできました。
今回は検索結果一番下にある「次へ」を押していく機能です。 これが実装できれば1キーワード100位までとか取得できるようになりますね。
一回目と二回目
前回までのおさらい
こちらが前回までで作ってきたプログラム。
このコードに書き加えていく形で「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
次へ をクリックさせる
次のページの取得を開始する前にページを移動させる必要がありますね。
まずはセレクターをチェック。
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_input
にget_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
ここまでで実行してみると・・・
For文のイテレータi
を検索順位のカウントに用いていたため呼び出すたびにリセットされちゃっていますね。
.Range("B" & i + writing_start_row) = i + 1 '検索順位
これをget_count
に変更すれば2ページ目以降の順位も正常に記録できます。
.Range("B" & i + writing_start_row) = get_count + 1 '検索順位
これでいい感じに取得できた!
ディスクリプションが無い場合に対応する
と思いきやエラー発生。
そんなやつに出くわすとは予想外でした。
まぁ、無いなら取得しないように条件文を加えるだけですね。
要素の有無を判別する
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
この状態で先程のエラー箇所を実行してみると取得するコードはスキップされることが確認できました。
まとめ
- 次のページへ移動できた
- 2ページ目以降も順位をカウントできた
- 取得対象要素が無いパターンに対応できた
- For文を使わずに繰り返し処理できた
いかがでしたでしょうか。
Optional引数やリンククリックでのページ繊維、要素の存在チェックなどスクレイピングでよく使うやつはでてきましたね。
検索エンジン以外でもこういったツールを使うとなるとログイン処理が必要になったりでフォームへのIDパスワード入力がでてきます。また今度紹介できたらいいですねー。
あとはPythonを使うパターンやAPI経由での取得とかも。そのうちね。