VBAプログラム開発、スクレイピング・データ要素を充実させる【6】コレクションを利用して、複数のデータを扱う
書籍一覧情報から詳細ページURLを収集し、詳細ページ内の情報をExcelワークシートへ出力という一連の処理ができました。
しかし、詳細ページURLは一度ワークシート上に保存していました。あくまで一時的に保存して処理させていたのですが、最終的に不要であればワークシートには表示させずに処理できるようにしたいです。
これを最後に実施して、データ要素を充実させる項目としては終わりにしたいところです。
今回の目的
URL情報をVBA上の処理だけで扱う
なぜやるか
ワークシートに保存する情報は書籍情報のみであり、参照元として収集するURL情報は保存しないため、メモリ上で処理だけが実施できるようにするため
やりたいこと
- 複数のURLデータを格納できる変数を用意する
- 格納した情報をから繰り返し詳細ページにアクセスできるようにする
やったこと
- 現状のURL情報の扱い方を確認する
- 複数データを扱える方法を確認する
- コレクションを利用する
- コレクションを利用したコードに変更する
- 取得URLがなかった場合を考慮する
実施内容
詳細ページURL収集方法を確認する
詳細ページへアクセスするURLの取得方法を改めて確認します。
# 詳細ページURL取得プロシージャ
Sub getBookList(DWSheet As Worksheet, htmlDoc As HTMLDocument, i As Integer)
'詳細ページURLを取得
Dim Bookdata As HTMLDivElement 'レコード単位データ
Dim detailField As HTMLDivElement '詳細フィールドデータ
Dim BookdataURL As String '詳細ページURL
For Each Bookdata In htmlDoc.getElementsByClassName("book-table__list")
'--detail情報からデータ取得
'--detailを取得
Set detailField = Bookdata.getElementsByClassName("book-table__list--detail")(0)
'詳細ページURL
BookdataURL = detailField.getElementsByTagName("a")(0) 'URL取得
DWSheet.Cells(i, 1).Value = BookdataURL '取得URL反映
'--detail情報からデータ取得ここまで
'列番号処理
i = i + 1
Next Bookdata
End Sub
今までは取得したURLは専用のワークシートへと保存する手順としていました、これをどうにかしてワークシート保存ではなく、変数に一時的に格納しておき、そこから詳細ページの情報に順番にアクセスできるようにします、URL自体は保存はしません。
複数の情報を扱う変数を用意する
配列を使う方法を検討する
VBAに限らず、プログラミングで使う手法のひとつに配列というものがあります。
簡単に言うと変数に複数の情報が格納できると言ったところでしょうか。
通常の変数では
A = 1
cells(1,1).value = A
とすると、セルA1に1と表示されます。
配列使用例としては
B(2) = array(0,1,2)
cells(1,1).value = B(0)
とすると、配列Bに入っている3つの要素の0番目を取り出せるので、0が表示されます。
これを利用して、URL情報を格納して順番に取り出そう…そう考えていましたが、VBAの配列では、配列宣言に対して要素数の指定がいるということがわかりました。※動的配列もありますが、データを追加する時に要素数が必要になってきます。
他のプログラミング言語では特に要素数の指定なく動的に追加できたりするものもあります。
と、ここで配列を含め、複数要素を扱う方法を調べていく中で、もっと簡単にできることが分かったので、今回は配列は使用せずに次の方法を使うことにしました。
コレクションを使う
その方法は、コレクションと呼ばれるオブジェクトを利用します。
こちらを参考にさせて頂きました、ありがとうございます。
今回は詳細ページURLを配列の様に集約しておき、順番に参照できればいいので、コレクションで充分と考えました。
コレクションで変数宣言しておき、必要な要素を追加すれば、上書ではなく、追加要素として変数へ格納できます。要素数の宣言も不要なので、先程の配列よりも簡単に扱えそうです。
コレクションを利用して複数要素をコントロールする
コレクションを利用してデータを格納する
というわけで、早速コレクションを利用してデータを扱ってみます。
# メインプロシージャ[追加コード抜粋]
Sub getBookdatasDatail()
'URLコレクション
Dim URLCol As Collection
Set URLCol = New Collection
Do Until OpenPage = ""
'詳細ページURL取得
Call getBookList(DWSheet, htmlDoc, i, URLCol)
Loop
End Sub
メインプロシージャでコレクション変数を宣言しておき、詳細ページURLプロシージャへと引き渡せるようにしました。メインで宣言しておくことで、後から詳細ページ内の情報取得時に利用するためです。
詳細ページURL取得のプロシージャにURLColを引数指定したので、参照先プロシージャも対応させます。
# 詳細ページURL取得プロシージャ[コレクションテスト]
Sub getBookList(DWSheet As Worksheet, htmlDoc As HTMLDocument, _
i As Integer, URLCol As Collection)
'詳細ページURLを取得
Dim Bookdata As HTMLDivElement
Dim detailField As HTMLDivElement
Dim BookdataURL As String
For Each Bookdata In htmlDoc.getElementsByClassName("book-table__list")
'--detail情報からデータ取得
Set detailField = Bookdata.getElementsByClassName("book-table__list--detail")(0)
BookdataURL = detailField.getElementsByTagName("a")(0) 'URL取得
' DWSheet.Cells(i, 1).Value = BookdataURL '取得URL反映
URLCol.Add BookdataURL
'--detail情報からデータ取得ここまで
i = i + 1
Next Bookdata
End Sub
コレクション宣言したURLColにURLを追加する手順に変更しました。
コレクション変数.Addでコレクションにデータが追加されます。
DWSheet.Cells(i, 1).Value = BookdataURLとワークシート出力から、URLCol.Add BookdataURLと変わっていることが分かります。
コレクション変数へのデータ追加時にキーを引数として指定しておくことで、コレクションからデータを取り出す際にキー指定で出力することもできるようです。
コレクション変数にデータが格納されていることを確認する
ブレークポイントを設定しておき、処理中の変数の値を確認しました。これは1つめのURLを取得した時点で一時停止させています。特に確認したい項目は次の項目です。
コレクションとして宣言したURLColはこの部分です。Item1として~/book/8のURLが格納されています。
続けて2つめのURLまで取得します。
先程の続きで取得できました。
このまま継続させることで、最初の書籍一覧ページから20件までを取得しました。この後、ページネーションを確認して、最終ページまでデータを続けて取得します。
これで、全URLをコレクション変数であるURLColに格納できました。
コレクション変数から値を取り出す
後は、このコレクションから要素をとりだして各URLへアクセスできれば、ワークシートに書き出す処理が不要になります。
早速、ワークシートへ書き出していた場所を変更しました。
# 詳細ページデータ取得プロシージャ[変更点抜粋]
Do Until OpenPage = ""
'次ページURL取得
' OpenPage = DWSheet.Cells(URLi, 1).Value
OpenPage = URLCol(URLi)
objIE.navigate OpenPage 'IEでURLを開く
URLColにURLを全て格納しましたので、ここから要素をとりだしてOpenPageへ格納できるように変更しました。
URLCol()として要素を番号指定すれば対応したデータが取得できます。
1件目のURL取得時の変数を確認しました。
番号指定はURLiを使用しています。上記の変数参照では一番下にあり、1が格納されています。
OpenPage = URLCol(1)という指定をすることとなり、OpenPageにURLが格納されています。
URLさえ取得できれば、残りの処理は今までと同様です。
配列では要素の開始が0からとなっていますが、コレクションでは1が初期値になってます。
動作確認する
コレクション変数設定後に全て正常に完了することを確認する
URLCol()からコレクション要素を取得することができました。
先程は1件のデータで実験したのみだったので、全データを最後まで取得できることを確認します。
しかし、全データが出力された後にエラーとなりました。
デバッグすると、OpenPageにURLを格納する場所で停止していました。
OpenPageへURLColコレクションから値を取得する箇所で停止していました。
この時の変数の状態を確認します。
URLiの値を確認してみると、47になっていました。
URLColのコレクションデータも確認します。
URLColコレクションに格納されているURLの最終部分です。URLは46件です。
しかし、47番目を取得しようとして、値がないためにエラーとなっていました。
どうやら繰り返し処理が正常に終了できていないようです。
URLColは46件なので、46件目が終われば終了・47件目がなければ終了といった条件が設定できていないようです。
詳細ページ情報取得ループ設定を確認する
ループ処理が正常に終了できていませんでした、改めて現状のループ設定を確認します。まずは、コレクション変数を使用する前です。
# 詳細ページデータ取得プロシージャ[URLワークシート参照時]
'最初のページ
OpenPage = DWSheet.Cells(URLi, 1).Value
'詳細ページを開いて中のデータを取得
Do Until OpenPage = ""
objIE.navigate OpenPage 'IEでURLを開く
~ 中略 ~
i = i + 1
URLi = URLi + 1
'次ページURL取得
OpenPage = DWSheet.Cells(URLi, 1).Value
Loop
URLを専用ワークシートを参照してOpenPageへ格納し、繰り返していました。繰り返しの最後で次のURLを参照しています。
URL情報保存用ワークシートに一度書き出されたデータを参照してループ処理していました。
この時46行目まではURLがあるので、次の繰り返し開始時にOpenPageには値があるので繰り返し処理が行われていました。
47行目は値がありません。この時、繰り返し処理の開始コードはDo Until OpenPage = ""となっており、OpenPage=""が成立するので繰り返しから抜けていました。
この時は、この条件は正常であり問題なく動作していました。
現在のコードでループ動作を確認する
ワークシートに書き出していたURLをコレクションに変更しました。
# 詳細ページデータ取得プロシージャ[URLコレクション使用]
'最初のページ
OpenPage = URLCol(URLi)
'詳細ページを開いて中のデータを取得
Do Until OpenPage = ""
objIE.navigate OpenPage 'IEでURLを開く
~ 中略 ~
i = i + 1
URLi = URLi + 1
'次ページURL取得
OpenPage = URLCol(URLi)
Loop
OpenPageのデータ取得がURLColに変わっています。
この場合の最後のデータ取得を改めて確認します。
先程確認したURLColの要素です。47件目はありません。
URLi = URLi + 1
'次ページURL取得
OpenPage = URLCol(URLi)
Loop
このコードの最終部分はURLi + 1をして直後に次のデータを読み込もうとしていますが、47件目がないためにここでエラーとなっていました。
いままではワークシート上の47件目のデータ…つまり空欄を取得していました。その後、OpenPage = ""と条件が合っていました。
しかし、今回は存在しないデータを読み込む設定となっているので、ループを抜ける条件を変えてやる必要があります。
ループ終了の条件を修正する
今までのループ終了の条件は「URL情報が空になれば終了する」でした。しかし、この条件ではエラーとなることが分かっています。
そこで、URLの件数を予め確認しておき「指定の回数処理すれば終了する」とします。
こうすることで、今までの条件時のエラーとなっていた「存在しないデータを参照する」ということを回避できます。
# 詳細ページデータ取得プロシージャ[修正点抜粋]
Dim URLi As Long '詳細URL読み込み行番号処理
URLi = 1
'URL要素数
Dim fornumber As Long
fornumber = URLCol.Count
'詳細ページを開いて中のデータを取得
Do
'次ページURL取得
OpenPage = URLCol(URLi)
~ 中略 ~
i = i + 1
URLi = URLi + 1
Loop Until URLi > fornumber
URLColの要素数を予め先に取得しておくコードを追加しました。
URLCol.Countで数を取得します。今回のデータでは46件のデータがありましたので、fornumber = 46となります。
この後の処理の流れとしては
- URLiを宣言しておき、+1を繰り返し要素を順番に取得できるようにしておく
- URLCol(URLi)でURLを取得
- 詳細ページからデータ取得
- URLi + 1 をする
- 2〜4を繰り返す
- URLiがコレクション要素を上回ったらループから抜ける
となります。
繰り返し条件も見直しました。
繰り返し開始時には終了条件をいれていません。
繰り返し処理終了時に条件を設定しました。
繰り返し処理終了時に判定することによる効果としては、
- URLi + 1直後に要素上限を確認しているので、要素のないデータを取得するということが回避できる。
- ループを開始してからURL取得する記述としたので、事前に1つめのデータを個別に取得するコードがなくなる。
といった結果になりました。
これで、改めて動作確認し、全件取得後に正常完了することを確認しました。
繰り返し条件変更に伴う考慮を行う
とここまで記載して気づいたのですが、仮に書籍情報が1件もない場合について、対処できていないことに気づきました。
URLCol()にデータがない場合の処理が必要
今まではURLを書き出しておき、空となれば終了としていたので、URLが取れていない=最初から空で終了となっていました。
しかし、ループの終了条件が1件目のデータ処理後に変更したので、仮に0件であったとしても1件目の処理が開始されてしまい、そこでエラーとなります。
データが1件もない場合や、そもそも最初に指定しているURLが異なる場合は正常にスクレイピングが実施できないので、URL情報0件に対しての対処をいれておく必要が有ります。
URLCol()の件数による処理分岐を行う
詳細ページの情報を取得するには、詳細ページURLを収集してから詳細ページを順番に確認していますので、URL収集件数を確認すれば判定できそうです。
# メインプロシージャ[修正点抜粋]
Sub getBookdatasDatail()
'Dim宣言
'詳細URL取得ループ
Do Until OpenPage = ""
Loop 'OpenPageループエンド
'処理完了時メッセージ格納変数
Dim ExitMsg As String
'詳細ページURLの件数で処理分岐
If URLCol.Count > 0 Then
Call getDetailBookdata(SWSheet, objIE, URLCol)
ExitMsg = "データ取得が完了しました。"
Else
ExitMsg = "取得データがありません"
End If
'VBA終了処理
objIE.Quit 'objIEを終了させる
MsgBox ExitMsg
End Sub
詳細ページURLを取得完了後に、URLColの件数によってURLが格納されているか確認できるようにしました。
1件でもあれば詳細ページ取得プロシージャを実行、データがなければ処理せずに終了させます。
詳細情報取得プロシージャを分けていた為に、簡単に追加できました。
あわせて、条件によって終了時にユーザーに返すメッセージを変更出来るようにしました。 IF文で処理分岐しているので、そこで処理に合致するメッセージをいれています。
以上でコレクションを利用したURL取得対応と、URLが0件であった場合の考慮まで完了しました。
書籍情報の一覧情報から詳細ページを確認して、詳細ページの全情報がExcelワークシートに保存できるようになります。
基本処理としては完了ですが、いくつか拡張をしてみたいと思っているので、次にそれらを実施していきます。