SeleniumBasicでダウンロード利用その2 usage_download

リファレンス

 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設定(ダイアログボックスの非表示)
STEP
「ダウンロード」をクリック
STEP
「ダウンロード前にファイルの保存場所を確認する」を非アクティブに設定

 4行目の内容を手動で設定する方法を紹介します。Chromeの設定画面から「クリックでPDFダウンロードをする」を設定する流れになります。(以下タイトルをクリックすると確認できます。)

Chrome設定(クリックでPDFダウンロード)
STEP
「プライバシーとセキュリティ」をクリック
STEP
「サイトの設定」をクリック
STEP
「その他コンテンツの設定」をクリック
STEP
「PDF ドキュメント」をクリック
STEP
「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ではこの様な表現も使われていた様です。参考になるページも紹介しておきます。

 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

この方法は、以下ページでも紹介しています。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

VBAを中心とした自動化、効率化の手法を紹介しています。現在は、SeleniumBasicのexamplesを紹介しています。その内、SeleniumBasic以外の手法も掲載したいと思っております。

目次