SeleniumBasicでダウンロード作業をする際に役立つコードの紹介です。
私は、SeleniumBasicで各ホームページにアップされているPDFデータやJPEGデータ等の画像データ(バイナリデータ)をダウンロードすることがよくあります。
SeleniumBasicでファイル容量が重いファイルを複数ダウンロードする場合、ダウンロードが完了する前にコードが終了してしまい、ファイルダウンロードが中断してしまう事があります。また、複数ダウンロードを同時並行してしまうと、Webサーバの負荷が増えてしまいます。
そこで、今回紹介するコードは、1件毎にデータダウンロードが完了してから次のデータダウンロードに移るため、上記問題を解決する事ができます。
紹介したいコードが大きく分けて二つあり、2回に分けて説明します。
2回目は、ブラウザ操作中のキーボード操作でダウンロードする方法を紹介します。
- 利用目的は業務効率化です。(テスト自動化ではありません)
- ブラウザはGoogleChromeが対象です。(EdgeやFirefoxは対象外)
- 原文のVBAでは現在エラー発生する場合が多いため、修正を加えてます。
Examples.xlsmの保存場所(ここをクリック)
Examples.xlsmが保存されている場所は、SeleniumBasicがインストールされているサブフォルダ内になります。SeleniumBasicのインストール完了時に保存フォルダを確認することができます。ただ、大体以下フォルダのどちらかにファイル保存されています。
- C:\Program Files\Seleniumbasic\Examples\Excel
- shell:Local AppData\SeleniumBasic\Examples\Excel
ファイルをスクレイピングでダウンロードする方法を紹介します。今回紹介するのは、ホームページ上のPDFデータリンクを取得する方法です。
構文全文
ダウンロード先は、日本空手協会ホームページを利用させていただきました。実行データ全文は、以下のとおりです。
Option Explicit
Private Sub Download_File_Chrome()
Dim Driver As New ChromeDriver, file As String
Dim elm As Selenium.WebElement
'Chromeの環境設定
Driver.SetPreference "download.default_directory", ThisWorkbook.Path
Driver.SetPreference "download.directory_upgrade", True
Driver.SetPreference "download.prompt_for_download", False
Driver.SetPreference "plugins.always_open_pdf_externally", True
'ダウンロード作業前の準備
WaitNewFile ThisWorkbook.Path & "\*.pdf"
'ダウンロード作業
Driver.Get "https://www.jka.or.jp/jka-news-pdf/jka-no34-pdf10mb/"
Dim Keys As New Selenium.Keys
Set elm = Driver.FindElementByPartialLinkText("JKAニュース No34")
Driver.Actions.MoveToElement(elm).Perform
Driver.Wait 2000
elm.Click Keys.Alt
'ダウンロード完了の待機
file = WaitNewFile()
Driver.Quit
End Sub
Public Function WaitNewFile(Optional target As String) As String
Static files As Collection, filter As String
Dim file As String, file_path As String, i As Long
If Len(target) <> 0 Then
filter = target
Set files = New Collection
file = Dir(filter, vbNormal)
Do While Len(file)
files.Add Empty, file
file = Dir()
Loop
Exit Function
End If
On Error GoTo WaitReady
Do
file = Dir(filter, vbNormal)
Do While Len(file)
If file Like "*crdownload" Or file Like "*tmp" Then Exit Do
files.Item (file)
file = Dir()
Loop
For i = 0 To 3000: DoEvents: Next
Loop
WaitReady:
file_path = Left(filter, InStrRev(filter, "\")) & file
Do
If FileLen(file_path) Then
On Error Resume Next
Name file_path As file_path
If Err = 0 Then Exit Do
End If
For i = 0 To 3000: DoEvents: Next
Loop
files.Add Empty, file
WaitNewFile = file_path
End Function
ポイントは、ダウンロード作業(Alt+Click)の前後に以下構文を挿入することです。
WaitNewFile ThisWorkbook.Path & “*.pdf”
~ダウンロード作業~
file = WaitNewFile()
以下、各構文を分解して説明します。
構文分解説明
Chromeの環境設定
Chromeブラウザを起動する前に設定するChromeの環境設定です。Driver.Get後にこの設定をしても設定反映されません。ですので、ダウンロードファイルの保存先を変えたい場合は、一度、Driverオブジェクトを破棄し、Driver.Get前に再度環境設定をしなければなりません。
Driver.SetPreference "download.default_directory", ThisWorkbook.Path
Driver.SetPreference "download.directory_upgrade", True
Driver.SetPreference "download.prompt_for_download", False
Driver.SetPreference "plugins.always_open_pdf_externally", True
- 1行目
-
ファイル保存先の指定。今回は、マクロファイルと同ディレクトリ
- 2行目
-
(おそらく)1行目の保存ディレクトリを更新。
- 3行目
-
ダウンロード時、「名前を付けて保存」のフォルダ出現させない(名前指定せずにファイル保存)
- 4行目
-
PDFをダウンロードする設定(これでAlt+Clickが不要になる)
3行目の内容を手動で設定する方法を紹介します。(タイトルをクリックすると確認できます。)
Chrome設定(ダイアログボックスの非表示)
4行目の内容を手動で設定する方法を紹介します。Chromeの設定画面から「クリックでPDFダウンロードをする」を設定する流れになります。(以下タイトルをクリックすると確認できます。)
Chrome設定(クリックでPDFダウンロード)
ダウンロード準備作業(WaitNewFile)
ダウンロード作業前の準備作業になります。WaitNewFile関数を引数付で呼び出します。
WaitNewFile ThisWorkbook.Path & "\*.pdf"
パス名、ファイル名、拡張子は以下の様に設定します。
パス名:Chrome環境設定で指定したパス(ThisWorkbook.Path)
ファイル名:全てのファイル(”*”:正規表現)
拡張子:ダウンロードしたいファイルの拡張子(”.pdf”)
ダウンロードしたい拡張子が予め分かっている場合は、上記の様に拡張子を指定します。一応、拡張子を指定しなくてもダウンロードできる様に工夫を施しましたが、拡張子を指定した方が動作は安定します。
次に、WaitNewFileの準備作業について説明します。WaitNewFile関数は関係する箇所を抜粋しています。
簡単に説明すると、ダウンロードしたいフォルダ内のPDFファイルを全て検索し、fileコレクションに代入します。
Public Function WaitNewFile(Optional target As String) As String
Static files As Collection, filter As String
Dim file As String, file_path As String, i As Long
If Len(target) <> 0 Then
filter = target
Set files = New Collection
file = Dir(filter, vbNormal)
Do While Len(file)
files.Add Empty, file
file = Dir()
Loop
Exit Function
End If
'~中略~
End Function
・5行目
targetの文字列有無を確認。
今回は引数の指定があるため、条件分岐はTrueで通過します。
・6行目
filterにtargetを代入。静的に宣言したfilter変数は後でも利用。
・8~12行目
Do While でThisWorkbook.Path内のpdfファイルを検索。
検索ヒットしPDFファイルをfilesコレクションのkeyに代入。(itemはempty)
・13行目
準備作業は終了したため、関数終了。
Do WhileのDirファイルループ検索は、VBAのDir全件検索方法です。
詳しくは、以下の様なページを参照いただければと思います。
原文では、変数宣言が"Dim file$, file_path$, i&"と記載されていました。
見慣れない変数宣言だったのですが、$=As String、&=As Longと読み替えていただければ問題ありません。昔のVBAではこの様な表現も使われていた様です。参考になるページも紹介しておきます。
ダウンロード作業(Selenium)
SeleniumBasicで空手協会ホームページを開き、PDFリンクの要素を"Alt+Click"でダウンロード実行します。
"Alt+Click"は、Chromeのショートカットキーです。Clickのみの場合、デフォルト設定ではブラウザでpdfファイルを開いてしまいますが、"Alt+Click"で、直接ダウンロード実行しています。Driver.SetPreference "plugins.always_open_pdf_externally", Trueを設定することで、クリックのみでファイルダウンロードが可能になります。
Driver.Get "https://www.jka.or.jp/jka-news-pdf/jka-no34-pdf10mb/"
Dim Keys As New Selenium.Keys
Set elm = Driver.FindElementByPartialLinkText("JKAニュース No34")
Driver.Actions.MoveToElement(elm).Perform
Driver.Wait 2000
elm.Click Keys.Alt
4行目:リンクテキストを部分一致検索("JKAニュース No34")で要素取得
5行目:elm要素(リンクテキスト)までスクロール移動。
7行目:"Alt+Click"でダウンロード実行
7行目の"Alt+Click"は、他の文法でも表現することができます。(下記)
Driver.Actions.MoveToElement(elm).KeyDown(Keys.Alt).Click.Release.Perform
Actionsでまとめて実行したいキーボード操作を設定できます。
"Alt"と"Click"を設定後、Performで操作を確定させます。
もし、elm.Click Keys.Altで上手く操作ができない場合、試してみてください。
ダウンロード完了待機(WaitNewFile)
後半のWaitNewFileを呼び出し、ダウンロード完了を待機します。
後半のWaitNewFileは引数の指定はしません。
file = WaitNewFile()
後半のWaitNewFile構文で利用する箇所を以下に抜粋しました。
Public Function WaitNewFile(Optional target As String) As String
~中略~
On Error GoTo WaitReady
Do
file = Dir(filter, vbNormal)
Do While Len(file)
If file Like "*crdownload" Or file Like "*tmp" Then Exit Do
files.Item(file)
file = Dir()
Loop
For i = 0 To 3000: DoEvents: Next
Loop
WaitReady:
file_path = Left(filter, InStrRev(filter, "\")) & file
Do
If FileLen(file_path) Then
On Error Resume Next
Name file_path As file_path
If Err = 0 Then Exit Do
End If
For i = 0 To 3000: DoEvents: Next
Loop
files.Add Empty, file
WaitNewFile = file_path
End Function
3~12行目(概要)
ダウンロード指定先のフォルダ内のPDFファイルを全て検索し、検索ヒットしたPDFファイル名を前半のWaitNewFileでセットしたfilesコレクションのkeyに代入します。
ダウンロードフォルダに対象のPDFファイル名が合われるまでループします。ダウンロードが完了し、ダウンロードフォルダにPDFファイルが現れると、8行目のfilesコレクションのkeyに登録が無い新しいPDFファイルがloopの間に現れますのでエラーが発生し、WaitReady:に飛ぶ、という仕組みです。
・3行目
エラー発生したら、WaitReady:に飛ぶ様にセット
・7行目
Chromeダウンロード途中の一時ファイルを見つけ、一回Loopを抜けます。
この処理をしていれば、一応拡張子を指定しなくてもファイルダウンロードができます。
・8,9行目
ダウンロードしたPDFファイルがダウンロードフォルダ内に保存されると、Dir()ループでファイル名として浮上してきます。ダウンロードしたてのファイル名は、filesのkeyに登録が無いため、エラーが発生し、WaitReady:に飛びます。
・11行目
ループ先頭に戻るまで時間を稼ぐためforループで3000をカウントしています。
ここで注意点です。ダウンロード途中で上手く作業ができなかった場合、ここのループを延々と繰り返す事があります。DoEventsで制御をOSに戻しているため、一見エラー発生していない様に見えますが、実行中のVBAを一時停止させると、この箇所で止まっている事が多くあります。
13~25行目(概要)
ファイルサイズが0より大きくなるまでループします。ファイルサイズが0より大きくなったらファイル名をリネームして処理を終了します。終了時にファイルフルパス名を返します。
・14行目
ファイル名を"*.pdf"から"JKA_NO34.pdf"に変更しています。
原文では、Left関数で"$"が付いていました。昔のVBAの文法ですので、現在は気にしなくても良いです。気になる方は、ここのページを参照ください。
・17行目
19行目でエラー判定をしていますので、8行目で発生したエラーの回避処理をしています。
ですので、"On Error GoTo 0"でも意味は通るかと思われます。
・18行目
この命令文を通過する時にダウンロードしたてのファイルを開いているとエラーが発生します。
または、ファイル名のリネームがここで出来ます。
この命令文の目的がエラー出しにあるのか、リネームにあるのか、はたまた他に目的があるのかまだ検証が必要かもしれません。エラー出しに目的があるとすれば、エラー発生で構文がストップするので構文の安定運用のためには省いた方が良いかもしれません。
・19行目
エラーが無ければ、ここでループが終了します。
・23行目
最後にダウンロードしたファイルのファイル名をfilesコレクションに登録します。
・24行目
ファイルフルパス名を返します。
WaiterクラスのWaitForFileメソッド利用
WaiterクラスのWaitForFileメソッドを利用したダウンロード待機の方法も紹介しておきます。
Private Sub Download_File_Chrome()
Dim Driver As New ChromeDriver
Dim elm As Selenium.WebElement
Dim Waiter As New Selenium.Waiter
'Chromeの環境設定
Driver.SetPreference "download.default_directory", ThisWorkbook.Path
Driver.SetPreference "download.directory_upgrade", True
Driver.SetPreference "download.prompt_for_download", False
Driver.SetPreference "plugins.always_open_pdf_externally", True
'ダウンロード作業前の準備
Driver.Get "https://www.jka.or.jp/jka-news-pdf/jka-no34-pdf10mb/"
Dim Keys As New Selenium.Keys
Set elm = Driver.FindElementByPartialLinkText("JKAニュース No34")
Dim buf, FileName As String
buf = Split(elm.Attribute("href"), "/")
FileName = buf(UBound(buf))
Dim FullPath As String
FullPath = ThisWorkbook.Path & "\" & FileName
If FileName Like "*.pdf" And Dir(FullPath) = "" Then
Driver.Actions.MoveToElement(elm).Perform
Waiter.Wait 2000
elm.Click Keys.Alt
ErrResume:
On Error GoTo ErrHandle
Waiter.WaitForFile FullPath, 50
End If
Driver.Quit
Exit Sub
ErrHandle:
Resume ErrResume
End Sub
この方法は、以下ページでも紹介しています。