2025/02/09

テキストファイルの読み取り(Webからダウンロード編)




VBAを使い「テキストファイルを読み書き」する手法について、以下のようなシリーズで紹介しています。
 ①テキストの文字コードについて
 ②Openステートメント法による読み書き
 ③ADODB.Stream法による読み書き
 ④TextStream法による読み書き
 ⑤XMLHTTP法によるWebデータの読み取り
 ⑥Web上からダウンロードしての読み取り   ←今回
今回は、下図のように「Web上のファイルを自分のPC内にダウンロード」する手法を紹介します。自分のPC内にファイルを持ってきた後に上記②~④の手法を使えば、テキストファイルを読み取ることが可能となります。
なお、ダウンロード先は自PC以外にもファイルサーバー等のLAN上も可能です。またダウンロードの対象はテキスト以外の画像等でも手法によっては可能です(もちろんダウンロード後の処理は、ファイル種に合わせた方法にする必要があります)。
Web上のファイルを自PC等にダウンロードしてから読み込む
図01

Web上からファイルをダウンロードする手法としては、Win32APIの「URLDownloadToFile関数」が良く紹介されていますが、以下のように複数の手法が存在します(これが全てではありません)。
手法備考
Windowsコマンドbitsadmin.exeWindowsのBITS機能を使用
curl.exeHTTPリクエストを行うためのツール
APIURLDownloadToFileURL先からファイルをダウンロード
InternetReadFile等URLハンドルから一定バイト単位でデータ読み取り
アプリを中継ExcelWeb上のテキストファイルをシート上に開き、テキストファイルで保存
InternetExplorerIE上にテキストデータを表示し、テキストファイルで保存
図02

ここではテキストファイルを扱いますが、ダウンロードされたファイルの文字コードは、基本的にはWeb上のテキストファイルと同じ文字コードとなります。そのため、テキストの処理も文字コードに合わせた以下のような手法が必要となります。
読み書き手法対応文字コード
Shift-JISUTF-8UTF-16
Openステートメント
ADODB.Stream
TextStream(FileSystemObject)
図03

なお、以下の説明内で使っているサンプルコードでは、Web上に存在するテキストファイルをダウンロードし、その上でファイルを読み取って表示するという内容になっています。実際にサンプルファイルが動く為には、その「Web上のファイル」が実在する必要があり、ここでは本サイト上に以下の内容のファイルを置いています。
特に意味のあるファイルではありませんが、文字コードが違うとどうなるか等を試すには必要かと思い、内容は漢字・数字・アルファベット・半角カナ・ひらがなを含めたものにしています。
URL:https://atsumitm.iobb.net/its/excel/ [下表のファイル名]
ファイル名文字コードBOM内容
Test_SJIS.txtShift-JIS-"Aアあ123"
"Web上のファイル"
"文字コード〇〇(BOM有無)"
Test_UTF8N.txtUTF-8無し
Test_UTF8B.txtUTF-8付き
Test_UTF16LEN.txtUTF-16LE無し
Test_UTF16LEB.txtUTF-16LE付き
Test_UTF16BEN.txtUTF-16BE無し
Test_UTF16BEB.txtUTF-16BE付き
Test_HTML.phpUTF-8無し1~2行目:GET(g1=, g2=)で送った文字列
3~4行目:POST(p1=, p2=)で送った文字列
PHPファイル内容はXMLHTTP編を参照下さい
図04

1.Windowsコマンドを使用 

Web上のファイルをダウンロードするコマンド(外部プログラム)には、「bitsadmin.exe」と「curl.exe」があります。
なお他にもPowerShellのwgetを使用する方法などがありそうですが、PowerShellを実行しようとするとMcAfee等が異常を検知してしまうため、VBAで使うには難しそうなので今回は割愛します。

1-1.bitsadmin.exe 

「bitsadmin.exe」は、ジョブの作成・ダウンロード・アップロード、およびそれらの進行状況の監視をするコマンドで、WindowsのBITS(Background Intelligent Transfer Service)機能を使って作業をします。bitsadmin.exeには多くのスイッチがありますが、ファイルをダウンロードするには「/TRANSFER」スイッチを使用します。
構文は以下です。
bitsadmin /TRANSFER name [type] [/PRIORITY job_priority] [/ACLflags flags] [/DYNAMIC] remotefilename localfilename
スイッチ・値内容
name必須ジョブの名前
type省略可/DOWNLOAD ・・・ダウンロード時(既定値)
/UPLOAD ・・・アップロード時
/PRIORITY job_priority省略可FOREGROUND ・・・ 優先度高
HIGH
NORMAL (既定)
LOW ・・・ 優先度低
/ACLflags flags省略可o ・・・ファイルと共に所有者情報をCopy
g ・・・ファイルと共にグループ情報をCopy
d ・・・ファイルと共に随意アクセス制御リスト(DACL)をCopy
s ・・・ファイルと共にシステムアクセス制御リスト(SACL)をCopy
/DYNAMIC省略可サーバーのHTTP要件が緩和され、署名エラーを回避
remotefilename必須ダウンロードしたいWeb上ファイルのURL
localfilename必須保存先ファイル名(フルパス)
図05

name には、ジョブ名を指定します。この名前は、何でも良いようです。
なお試してみるとジョブ名を指定しなくても一応正しく動きます。これは「""(長さゼロの文字列)」がジョブ名として指定されたと認識しているのかもしれません。しかし異常時に対応が出来なくなる気がするので、少なくとも「文字として見えるジョブ名」を指定するのが良いと思います。
type には、ダウンロード(/DOWNLOAD を指定)をするのか、アップロード(/UPLOAD を指定)をするのかを指定します。既定はダウンロードです。
/PRIORITY job_priority には、優先度を指定します。既定はNORMALで4段階中の下から2番目です。
/ACLflags flags は、ファイルと同時にどのような情報をコピーするかの指定です。指定しない(既定)と、どの情報もCopyされないのですが、タイムスタンプ(更新日時)はCopyされるようです。
/DYNAMIC を指定すると、サーバー側のHTTP要件が緩和され、署名によるエラーが回避できる場合があります。
寄り道(/DYNAMIC の機能)
BITSはダウンロードを実行する前に「HTTP HEADリクエスト」を送信し、ダウンロードしようとしているファイルのサイズ等を確認します。サーバー側としては、このHTTP HEADをサポートしている事がBITSを使ったダウンロードのHTTP要件となっている様です。
しかし、事前に送るHEADリクエストとダウンロード本体のGETリクエストとでは異なる署名が必要なサーバーも存在するようで、この場合はエラー(403:閲覧禁止)となってしまいます。
(署名は /setclientcertificatebyname スイッチで設定するみたいですが、GETリクエスト用のようです。)
この対策の1つとして、BITSジョブに「BITS_JOB_PROPERTY_DYNAMIC_CONTENTフラグ」を立てる事で「HTTP HEADリクエストを行わずに、すぐにHTTP GETリクエストを実行」する事が可能になります。このフラグを立てる方法が「/DYNAMIC」スイッチをつけることで実現できます。
つまり「/DYNAMIC」スイッチをつける事で、サーバー側の要件が緩和(HTTP HEADリクエストのサポート不要)される事になり、HEADとGETで別々の署名が必要なサイトのファイルであっても、エラーが回避できます。
(参考にしたサイトの頁:「forums.powershell.org」、「Microsoft」)

remotefilenameは、ダウンロードしたいファイルのURLを指定します。
localfilenameは、ローカル(自PCやLAN)上に保存したいファイル名をフルパスで指定します。bitsadminを実行する時点で存在していないファイルでもOKですし、もし存在していたら「上書き」となります。

1ー1-1.bitsadminのコード例 

bitsadmin.exeを使ったダウンロードのコード例が以下になります。ダウンロード後、MsgBoxでテキスト内容を表示させます。
  1. '========== ⇩(1) bitsadminでのダウンロード ============
  2. Sub Test001()
  3.  Const FN As String = "https://atsumitm.iobb.net/its/excel/" & "Test_UTF8B.txt"   '←ダウンロードするファイル
  4.  Dim Temp As String    '←保存先ファイルのフルパス
  5.  Dim DT As Date     '←ダウンロード開始日時
  6.  Temp = Environ("TEMP") & "¥" & "its-053.txt"
  7.  If Not Dir(Temp) = "" Then
  8.   Kill Temp    '←既存の保存先ファイルを削除
  9.  End If
  10.  Shell "bitsadmin /TRANSFER " & " myDL " & "/DYNAMIC " & FN & " " & Temp
  11.  DT = Now()
  12.  Do While Dir(Temp) = "" And DT + TimeValue("0:00:10") > Now()
  13.   DoEvents: DoEvents
  14.  Loop
  15.  With CreateObject("ADODB.Stream")
  16.   .type = 2
  17.   .Charset = "UTF-8"
  18.   .Open
  19.    On Error Resume Next
  20.     .LoadFromFile Temp
  21.    On Error GoTo 0
  22.    MsgBox .readtext
  23.   .Close
  24.  End With
  25. End Sub
図06

02行目「Const FN As String = "https://atsumitm.iobb.net/its/excel/" & "Test_UTF8B.txt"」は、ダウンロードしたいWeb上ファイルのURLを定数設定しています。
06行目「Temp = Environ("TEMP") & "¥" & "its-053.txt"」では、保存先ファイルのフルパスを作成しています。今回は「Environ("TEMP") & "¥"」とし、自PCのTempフォルダーに設定しています。
通常ですとTempフォルダーは「C:¥Users¥<ユーザー名>¥AppData¥Local¥Temp¥」になるようです。
08~10行目のIf文では、保存先ファイルが既に存在するか否かを調べ、存在時には09行目「Kill Temp」で削除をしています。これは、より新しいファイルを読み込むための作業です。
今回、存在有無の判断には「Dir関数」を使いました。Dir関数の引数に保存先ファイルのフルパスを指定することで、存在しなければ「""(長さゼロの文字列)」を戻し、存在すればファイル名(この場合だと、"its-053.txt")を戻してきます。
なお09行目のKillステートメントの代わりに、下記の様に「FileSystemObjectオブジェクトのdeletefileメソッド」を使っても既保存ファイルを削除出来ます。
  • '========== ⇩(2) 既保存ファイルの削除 ============
  •  If Not Dir(Temp) = "" Then
  •   With CreateObject("Scripting.FileSystemObject")
  •    .deletefile Temp
  •   End With
  •  End If
図07

12行目「Shell "bitsadmin /TRANSFER " & " myDL " & "/DYNAMIC " & FN & " " & Temp」が、Web上のファイルを保存先ファイルとしてダウンロードしている部分です。
先頭の "Shell" は「外部の実行可能プログラム(ここでは bitsadmin.exe)を実行」するVBA関数です。Shell関数には引数を2つ指定でき、第1引数が「"bitsadmin /TRANSFER " & " myDL " & "/DYNAMIC " & FN & " " & Temp」というコマンド一式です。
第2引数は実行プログラムのウィンドウ形式の設定で、省略可能です。今回のように省略した場合は「フォーカスを持つ最小化ウィンドウ(vbMinimizedFocus 値=2)」ですので、煩わしいマンドプロンプト画面が一瞬表示される事はありません。
なお「フォーカスを持つ非表示ウィンドウ(vbHide 値=0)」や「フォーカスを持たない最小化ウィンドウ(vbMinimizedNoFocus 値=6)」を指定しても、コマンドプロンプト画面は表示されません。
寄り道(VBAでのコマンドの実行方法)
図06では、外部プログラム「bitsadmin.exe」をVBAのShell関数で実行させています。このShell関数を使う以外にも、外部プログラムを実行させる方法はあります。
その1つが、CreateObject("WScript.Shell")などを使いWSHshellオブジェクトを生成し、そのメソッドである「execメソッド」または「runメソッド」を使う方法です。
どちらも引数に「実行するコマンド」を指定するのは同じですが、以下のような違いがあります。
 ・「exec」は、出力結果を戻り値として取得できるが、コマンドプロンプト画面が表示されてしまう。
 ・「run」は、コマンドプロンプト画面は表示されないが、出力結果を取得できない。
しかし「bitsadmin.exe」をexecやrunで実行すると、どちらも下記のような「実行結果」が一瞬表示されてしまいます。
bitsadminの実行結果
図08

そのため、今回はVBAのShell関数を使用して「bitsadmin.exe」を実行させています。

また外部実行プログラム「bitsadmin.exe」の ".exe" は、付けなくてもOKです。ジョブ名は今回 "myDL" としましたが、何でもOKです。また "/DYNAMIC "スイッチを指定し署名エラー回避をしていますが、今回のサーバーでは特に必要はありません。
ダウンロードするファイル名は02行目で宣言した定数FN、保存先ファイル名は06行目で計算させた変数Tempとなります。
12行目のbitsadmin.exeを実行すれば、基本的にはファイルがダウンロードされるのですが、注意しておく事があります。
1つ目は、ダウンロードするファイルのURLが間違っていたりしていて「ダウンロードされない」場合がある事です。2つ目は、正常にダウンロードされている途中段階はファイルとして認識されないため、その状態の時に読み取り等でアクセスしようとするとエラーとなる事です。
そこで14~17行目では、ダウンロードが完了するまでプログラムの進行を待機させています。
まず14行目「DT = Now()」では、現時点での日時データを取得します。
15行目のDo~Loopの条件式は「Dir(Temp) = ""」と「DT + TimeValue("0:00:10") > Now()」の2つをANDで結び、Do Whileとしていますので、2つの式が両方とも成立している間はDo~Loop内(= どちらか一方が不成立になったらDo~Loopを抜ける)という意味です。
「Dir(Temp) = ""」の条件では、ダウンロードが成功しファイルが完成する(=テキスト内容が読み込める状態になった)事を表し、「DT + TimeValue("0:00:10") > Now()」は10秒間待つという内容です。
気持ちとしては「正常にダウンロード出来たら10秒以内に保存先ファイルは完成するだろう」+「10秒待っても保存先ファイルが出来ないのは、エラーが出ている為と判断」という事になります。
寄り道(bitsadminが成功したか否かのチェック法)
今回 bitsadmin が成功したか否かのチェック法として、ダウンロードしたテキストが空文字列か否かを使用しています。但しこの判断には、ダウンロード時間大という原因も含まれてしまう事になります。
一方、よりみちで紹介したWSHshellオブジェクトの「execメソッド」を使えば戻り値が取得でき、その戻り値(WshExecオブジェクト)の中のExitCodeプロパティを確認する方法が考えられます。
しかし試したところ、以下のような結果となりました。
 ・execメソッド実行直後は、ExitCodeプロパティ値は常にゼロ
 ・URLのフォルダー名やファイル名が違っていた時は、ExitCodeプロパティ値がゼロ以外に変わる
 ・URLのドメイン部分(今回で言えば "https://atsumitm.iobb.net")が違っていた時は、ゼロのまま
つまりexecメソッドの戻り値ではエラーが確実に仕訳けられない為、15行目のように「取得テキストが空文字列か否かで成功・失敗を判断する」事としました。なお今回は「10秒待つ」としていますが、PCの能力や通信環境によっては「もっと長い待ち時間が必要」となりそうな印象を持ちました。

保存先ファイルが出来た・出来ないに関わらず、長くても10秒経てばDo~Loopを抜け出し、19~30行目でADODB.Streamを使って保存したファイルの処理(テキスト内容の表示)を行います。
19行目「With CreateObject("ADODB.Stream")」ではStreamオブジェクトを生成します。Withで囲っている範囲内では、ドットから始まっているプロパティ・メソッドはStreamオブジェクトのものという事になります。
20行目「.type = 2」でStreamをテキスト型に設定し、21行目「.Charset = "UTF-8"」でStreamの文字コードをUTF-8に設定しています。このUTF-8に設定しているのは、02行目で定数宣言している「ダウンロードするファイルの文字コードがUTF-8」に合わせている為です。
23行目「.Open」でStreamを開き、25行目「.LoadFromFile Temp」で「保存先ファイル(変数Temp)からテキストを読み込み」ます。しかし保存先ファイルが出来ていない(=読み込むファイルが無い)場合にはエラーとなってしまうため、24行目「On Error Resume Next」でエラー回避をしています。
Stream内は、保存先ファイルがあるのであればテキスト内容が入り、ファイルが無いのであれば初期値の「""(長さゼロの文字列)」となっています。その文字列を28行目「MsgBox .readtext」で取り出し、表示させます。
以下が出力例です。
bitsadminでの出力例
図09

なお図06では、対象ファイルとして「Test_UTF8B.txt(02行目で定数設定)」を使用しました。このファイルは図04でも説明したように文字コード「UTF-8(BOM付)」のファイルですが、その他の文字コードファイルでもOKです。
但しテキストを読み取る際には文字コードを意識する必要があり、21行目のCharsetプロパティ値も併せて変更(「ADODB.Stream編」参照)する必要があります。以下の手法でも同様です。

1-2.curl.exe 

「curl.exe」は、HTTPリクエストを行うためのツールで、幅広い機能を持っているコマンドです。構文は以下です。
curl [Options] URL
後半の引数 URL は指定必須で、ダウンロードするファイルのURLを指定します。
またOptionsは任意指定です。Optionsは260個超が存在するようですが、ここではテキストファイルのダウンロードに関係しそうなものを紹介します。
Option内容
-O
--remote-name
出力をカレントディレクトリ書き込む
ダウンロードされるファイル名はWeb上のファイル名と同一
-o file
--output file
出力をfileに書き込む
--create-dirs必要なローカルディレクトリ階層を作成
-I
--head
レスポンスヘッダ情報のみを出力
-i
(--include)
レスポンスヘッダ+テキストデータを出力
-X method
--request method
使用するリクエスト方法を指定
(methodにはGET,POST等を指定)
-G
--get
URLに投稿データを入れてGETを使用
(未指定でもGETで送信される)
-d data
--data data
HTTP POSTデータ
--data-urlencode dataHTTP POSTデータをエンコードする
(dataに指定したワード("&"印を含む)を全てEncode)
-H header
--header header
リクエストヘッダの追加
-L
--location
リダイレクトに従う
-u user:password
--user user:password
サーバーユーザー名とパスワード
-U user:password
--proxy-user user:password
プロキシユーザーとパスワード
図10

なお、Optionsを1つも指定しない(curl URL)場合はエラーは出ないのですが、どこに何がダウンロードされたのか(=既定は何なのか)が把握できませんでした。またOptionsURLを逆に記述(つまり、curl URL [Options])しても、問題無く動きそうです。
また上から5行目の「レスポンスヘッダ+テキストデータ出力」で、カッコ書きにしてある「--include」は、Windows10には存在しますがWindows11からは消えているようです。Windows11では「--show-headers」がその代わりとなっているようです。
但しWindows11でも「--include」は今のところ動きます。しかし将来的には分かりませんので、可能ならばハイフンが1つの「-i」を使った方が良いように思います。

1-2-1.カレントディレクトリにダウンロード 

オプションの「-O,--remote-name」は、指定したWeb上のファイルを「カレントディレクトリにダウンロード」するものです。ダウンロードしたファイル名はWeb上の指定ファイル名と同一です。
なお「カレントディレクトリ」は、VBAでは「CurDir関数」を実行することで取得できますし、「ChDirステートメント」を使うことで変更できます。コード例は以下です。
  1. '========== ⇩(3) curlでのダウンロード(カレントディレクトリ) ============
  2. Sub Test002()
  3.  Const Fname = "Test_UTF8N.txt"   '←ダウンロードするファイル名
  4.  Const FN As String = "https://atsumitm.iobb.net/its/excel/" & Fname   '←ダウンロード元URL
  5.  Dim Temp As String   '←ダウンロードされる先
  6.  Dim DT As Date     '←ダウンロード待ち時間
  7.  Temp = CurDir & "¥" & Fname   '←ダウンロード先のパス+ファイル名
  8.  If Not Dir(Temp) = "" Then
  9.   Kill Temp
  10.  End If
  11.  Shell "curl.exe --remote-name " & FN
  12.  DT = Now()
  13.  Do While Dir(Temp) = "" And DT + TimeValue("0:00:10") > Now()
  14.   DoEvents: DoEvents
  15.  Loop
  16.  Application.Wait (Now() + TimeValue("0:00:01"))
  17.  With CreateObject("ADODB.Stream")
  18.   .type = 2
  19.   .Charset = "UTF-8"
  20.   .Open
  21.    On Error Resume Next
  22.     .LoadFromFile Temp
  23.    On Error GoTo 0
  24.    MsgBox .readtext
  25.   .Close
  26.  End With
  27. End Sub
図11

57行目「Temp = CurDir & "¥" & Fname」では、ダウンロード先のパス+ファイル名を作成しています。
「-O,--remote-name」を使用すると「カレントディレクトリにWeb上のファイル名でダウンロード」されるので、CurDir関数を使用してフォルダー先を特定し、既保存ファイルの削除(59~61行目)・保存ファイルの実在確認(66行目)・ADODB.Streamへの取り込み(76行目)の引数(パス+ファイル名)としています。
なお、Dir関数の引数・.LoadFromFileメソッドの引数に「ファイル名のみ」を指定した場合は、「カレントディレクトリに存在しているファイル」と認識してくれますので、「Dir(Fname)」「.LoadFromFile Fname」と、ファイル名のみを指定することも可能です。
59~61行目のIf文は、ダウンロード先に既に同名のファイルが存在した時にファイルを削除しています。図06の08~10行目と同じです。
なお、削除処理をせず、ダウンロード先に同名ファイルが存在する場合は、エラー等は出ずに上書きされます。
63行目「Shell "curl.exe --remote-name " & FN」では、curlを使ってダウンロード元(定数FN)からカレントディレクトリにファイルをダウンロードしています。
なお「--remote-name」の代わりに「-O(ハイフンは1つ、大文字のオー)」でも良いですし、オプションとURLの順番を逆にして「Shell "curl.exe " & FN & " -O"」としても大丈夫です。また「curl.exe」の.exeを省略した「curl」もOKです。
以上の操作でWeb上のファイルはダウンロードされるのですが、ダウンロード直後にそのファイルを開こうとする時にはいくつか注意が必要です。
「URLの間違い」と「ダウンロード中はファイルが認識されない」のは図06の時と同じですので、65~68行目で「10秒待つ」または「ファイルが存在する」までDo~Loopを回しています。
しかしcurlの場合はこれだけではダメで、「ファイルが存在しても、ファイルの中身が書き込まれていない」状態が存在するようなのです。そのため69行目「Application.Wait (Now() + TimeValue("0:00:01"))」で、「1秒だけ待つ」ことを追加しています。
寄り道(ファイル使用中か否かの検査法)
69行目では「1秒待つ」としましたが、1秒が充分なのかは分かりません。大きなファイルを扱う場合や処理能力の低いPCなどの場合では、もう少し待ち時間が必要かもしれません。そのため、もう少し科学的に「ファイル内容を書き終えたか否か」を判断する必要がありそうです。
そこで「ファイル内容を書き込み中」という事は「そのファイルを他アプリで使用中」であるという事から、以下のOpenステートメントを実行しエラーが発生すれば「誰かが使用中」という事になると考えました。
 「Open ファイル名 For Binary Lock Read Write As #1」
しかし図11の中で試してみると「誰も使用していない」と判断されてしまい、上記「Open ・・・」は使えませんでした。そのため暫定的に「1秒待つ」仕様にしています。

ファイルの内容が取得できる状態になったら、71~80行目のADODB.Streamでファイルの文字コードを処理し、表示させています。内容は図06と同じです。
URLが間違っていてファイルが出来ていない事もある為、75行目「On Error Resume Next」で76行目「.LoadFromFile Temp」の処理をスキップさせ、空データの表示をさせています。

1-2-2.指定パス+ファイル名にダウンロード 

「-o,--output」のオプションを使うと、Web上のファイルを「指定したパス+ファイル名にダウンロード」します。
指定ファイルのパスを省略すると、カレントディレクトリのファイルと認識するようですが、ファイル名は省略不可(≒ダウンロードされない)です。コード例は以下です。
  1. '========== ⇩(4) curlでのダウンロード(指定ディレクトリ+ファイル名) ============
  2. Sub Test003()
  3.  Const FN As String = "https://atsumitm.iobb.net/its/excel/" & "Test_UTF8N.txt"   '←ダウンロード元URL
  4.  Dim Temp As String   '←ダウンロードされる先
  5.  Dim DT As Date     '←ダウンロード待ち時間
  6.  Temp = Environ("TEMP") & "¥" & "its-053.txt"   '←ダウンロード先のパス+ファイル名
  7.  If Not Dir(Temp) = "" Then
  8.   Kill Temp
  9.  End If
  10.  Shell "curl.exe --output " & Temp & " " & FN
  11.  DT = Now()
  12.  Do While Dir(Temp) = "" And DT + TimeValue("0:00:10") > Now()
  13.   DoEvents: DoEvents
  14.  Loop
  15.  Application.Wait (Now() + TimeValue("0:00:01"))
  16.  With CreateObject("ADODB.Stream")
  17.   .type = 2
  18.   .Charset = "UTF-8"
  19.   .Open
  20.    On Error Resume Next
  21.     .LoadFromFile Temp
  22.    On Error GoTo 0
  23.    MsgBox .readtext
  24.   .Close
  25.  End With
  26. End Sub
図12

96行目「Temp = Environ("TEMP") & "¥" & "its-053.txt"」は、ダウンロード先のパス+ファイル名を作成しています。ここでは一時フォルダー(Tempフォルダー)にダウンロードする為に、環境変数"TEMP"をEnviron関数に指定して、一時フォルダーのパスを取得しています。
なお、パスを省略して「Temp = "its-053.txt"」とすると、カレントディレクトリを指定する事になります。
98~100行目のIf文で既存のファイルを削除し、102行目「Shell "curl.exe --output " & Temp & " " & FN」でWeb上のファイルを指定先パス+ファイル名にダウンロードします。
なお「--output」の代わりに「-o(ハイフンは1つ、小文字のオー)」としてもOKです。
なお、--outputに指定しているパスが「実在しない」場合は、ダウンロードされません。しかしオプションの「--create-dirs」を付けることで、指定したパスが作成されダウンロードが可能となります。
ダウンロード直後にファイル内容を取得する際の注意点は図11と同じです。104~107行目でファイルが完成するまで待ち、108行目でテキスト内容が書き込まれるのを1秒待ちます。
110~119行目では、ADODB.Streamでのテキスト処理を行いメッセージ出力させています。

1-2-3.レスポンスヘッダ内容を出力 

「-I, --head」を付けると、ステータスライン+レスポンスヘッダ情報を出力します。ファイル内容は出力しません。
また「-i,--show-headers」を付けると、ステータスライン+レスポンスヘッダ情報+ファイル内容を出力します。コード例は以下です。
  1. '========== ⇩(5) curlでのダウンロード(レスポンスヘッダの出力) ============
  2. Sub Test004()
  3.  Const FN As String = "https://atsumitm.iobb.net/its/excel/" & "Test_UTF8N.txt"   '←ダウンロード元URL
  4.  Dim Temp As String   '←ダウンロードされる先
  5.  Dim DT As Date     '←ダウンロード待ち時間
  6.  Temp = Environ("TEMP") & "¥" & "its-053.txt"   '←ダウンロード先のパス+ファイル名
  7.  If Not Dir(Temp) = "" Then
  8.   Kill Temp
  9.  End If
  10.  Shell "curl.exe --head -o " & Temp & " " & FN
  11. ' Shell "curl.exe --include -o " & Temp & " " & FN
  12.  DT = Now()
  13.  Do While Dir(Temp) = "" And DT + TimeValue("0:00:10") > Now()
  14.   DoEvents: DoEvents
  15.  Loop
  16.  Application.Wait (Now() + TimeValue("0:00:01"))
  17.  With CreateObject("ADODB.Stream")
  18.   .type = 2
  19.   .Charset = "UTF-8"
  20.   .Open
  21.    On Error Resume Next
  22.     .LoadFromFile Temp
  23.    On Error GoTo 0
  24.    MsgBox .readtext
  25.   .Close
  26.  End With
  27. End Sub
図13

142行目「Shell "curl.exe --head -o " & Temp & " " & FN」では、「--head(または「-I」でも可)」オプションを指定していますので、レスポンス情報の内「ステータスライン」と「レスポンスヘッダ」の情報が出力されます。
また見え消しにしてある143行目「Shell "curl.exe --include -o " & Temp & " " & FN」の場合には、「--include(または「-i」でも可)」オプションを指定していますので、レスポンス情報の全情報が出力される事になります。
なお「--include」はWindows10までのオプションのようで、Windows11では「--show-headers」に変わったようです。但しWindows11でも、まだ「--include」が使えるようですが、安全のため「-i(1つのハイフン+小文字のアイ)」を使った方が良さそうです。
出力例は以下のようになります。
レスポンスヘッダ+ファイル内容の出力
図14

なお「-I, --head」の場合、HTTPレスポンス情報はAsciiのみの為か、153行目で指定しているCharsetプロパティ値をどの文字コードに設定しても正しくデータ取得できそうです。但し「-i, --include」ではファイル内容も一緒に出力されるため、Web上のファイルの文字コードに合わせた設定でデータを取得しないと文字化けが発生します。

1-2-4.サーバー側にクライアントデータを送信 

上記はWebサーバー上の固定ファイル(.txt や .html)の内容を取得するものでした。一方、クライアント側からサーバーにデータを送信し、そのデータをサーバー側が処理をしてからクライアントにデータを戻す場合もあります(XMLHTTP編参照)。
データ送信の手法にはGET法とPOST法があります。
1-2-4-1.GET法 
以下では「GET法」でデータをサーバーに送信し、その結果を得ています。
  1. '========== ⇩(6) curlでのダウンロード(GET法でデータ送信) ============
  2. Sub Test005()
  3.  Const FN As String = "https://atsumitm.iobb.net/its/excel/" & "Test_HTML.php" & "?g1=Aアあ&g2=Bイい"
  4.  Dim Temp As String   '←ダウンロードされる先
  5.  Dim DT As Date     '←ダウンロード待ち時間
  6.  Temp = Environ("TEMP") & "¥" & "its-053.txt"   '←ダウンロード先のパス+ファイル名
  7.  If Not Dir(Temp) = "" Then
  8.   Kill Temp
  9.  End If
  10.  Shell "curl.exe --request GET --get -o " & Temp & " " & FN
  11.  DT = Now()
  12.  Do While Dir(Temp) = "" And DT + TimeValue("0:00:10") > Now()
  13.   DoEvents: DoEvents
  14.  Loop
  15.  Application.Wait (Now() + TimeValue("0:00:01"))
  16.  With CreateObject("ADODB.Stream")
  17.   .type = 2
  18.   .Charset = "UTF-8"
  19.   .Open
  20.    On Error Resume Next
  21.     .LoadFromFile Temp
  22.    On Error GoTo 0
  23.    MsgBox .readtext
  24.   .Close
  25.  End With
  26. End Sub
図15

172行目の取得するWeb上のファイル名「Test_HTML.php」の後に追加した「?印より後方の文字列」がサーバーに送信するデータになります。今回は「g1=Aアあ&g2=Bイい」で、「キー = 値」という形にします。ここではg1とg2がキーで、"Aアあ"と"Bイい"が値という事になります。また複数データを渡しているので、データ間は「&」でつないでいます。
182行目「Shell "curl.exe --request GET --get -o " & Temp & " " & FN」では、「--request GET」でリクエスト方法としてGETを指定し、「--get」でURLに送信するデータを入れている事を明らかにしています。なお、それぞれ「-X GET」、「-G」でもOKですし、どちらか一方が無くても、また両方指定をしなくても、URLに追加したデータはサーバー側へ渡り処理後のデータがちゃんと戻ってきます。
1-2-4-2.POST法 
以下では「POST法」でデータをサーバーに送信し、その結果を得ています。
  1. '========== ⇩(7) curlでのダウンロード(POST法でデータ送信) ============
  2. Sub Test006()
  3.  Const FN As String = "https://atsumitm.iobb.net/its/excel/" & "Test_HTML.php"
  4.  Dim Temp As String   '←ダウンロードされる先
  5.  Dim DT As Date     '←ダウンロード待ち時間
  6.  Temp = Environ("TEMP") & "¥" & "its-053.txt"   '←ダウンロード先のパス+ファイル名
  7.  If Not Dir(Temp) = "" Then
  8.   Kill Temp
  9.  End If
  10.  Shell "curl.exe --request POST --data p1=Cウう&p2=Dエえ -o " & Temp & " " & FN
  11.  DT = Now()
  12.  Do While Dir(Temp) = "" And DT + TimeValue("0:00:10") > Now()
  13.   DoEvents: DoEvents
  14.  Loop
  15.  Application.Wait (Now() + TimeValue("0:00:01"))
  16.  With CreateObject("ADODB.Stream")
  17.   .type = 2
  18.   .Charset = "UTF-8"
  19.   .Open
  20.    On Error Resume Next
  21.     .LoadFromFile Temp
  22.    On Error GoTo 0
  23.    MsgBox .readtext
  24.   .Close
  25.  End With
  26. End Sub
図16

212行目のURLは、ファイル名「Test_HTML.php」で終了し、クライアントのデータは添付されていません。
222行目「Shell "curl.exe --request POST --data p1=Cウう&p2=Dエえ -o " & Temp & " " & FN」では、データ送信方法としてPOST法を選択した事を「--request POST」で明示し、そのデータは「--data p1=Cウう&p2=Dエえ」で指定しています。
データの形はGET法と同様で「キー = 値」という形にし、「&」で結んでいる事でp1とp2がキーで、"Cウう"と"Dエえ"が値という事になります。
なお「--request POST」は「-X POST」でもOKですし、省略しても大丈夫なようです。一方「--data data」は「-d data」としてもOKですが、データを渡すものなので省略は不可です。
また複数データを同時に渡す場合、222行目では「&」でつないでいますが、以下のように「--data data」を複数指定する方法でもOKです。
 「Shell "curl.exe --request POST --data p1=Cウう --data p2=Dエえ -o " & Temp & " " & FN」
また似たようなOptionに「--data-urlencode data」があります。これは「dataをエンコード」してサーバーに送信する機能です。
たとえばdataに「p1=Cウう」というデータのみを指定した場合、--dataで送信した時と--data-urlencodeで送信した時のパケットデータの違いは以下のようになります。
--dataと--data-urlencodeの違い
図17

上側の--dataを使用した場合は、POSTデータはパケット上では「UTF-8」の文字コードとして送信されるようです。
一方下側の--data-urlencodeを使用した場合は、ACSII文字以外はエンコードされ、半角カナの「ウ」は「%EF%BD%B3」、全角の「う」は「%E3%81%86」という形でサーバー側に送信されます。
なお、POSTデータをエンコードせず(UTF-8の状態で)に送信しても、エンコードして送信しても、サーバー側では正しく理解してくれるようです(サーバーによって異なるかもしれないので要注意)。
しかし、図16のように「p1=Cウう&p2=Dエえ」と複数データを「&」でつないでいる場合は「&印もエンコード」されるため、キーp1の値は「Cウう&p2=Dエえ」と誤認されてしまいます。
もちろん「&」を使わずに複数の --data-urlencode を指定すれば大丈夫ですが、通常のPOSTデータ送信には--data-urlencode を使う必要は無さそうで、恐らく「URLデータをPOSTで送信」する場合のOptionなのではないか?と勝手に推定しています。
ちなみに今回のサンプルデータで使用しているphpファイルのサーバー側処理は「Test_HTML.phpファイル内容」のようになっており、以下のように出力されます。
 GET法で渡すキーg1のデータは1行目、GET法で渡すキーg2のデータは2行目
 POST法で渡すキーp1のデータは3行目、POST法で渡すキーp2のデータは4行目
GET法・POST法でデータを渡し、その出力表示をした形は以下の様になります。
--dataと--data-urlencodeの違い
図18

左端が図15のコードでの出力結果、中央が図16のコードでの出力結果です。なお図16(POST法のコード)で、URLの後ろにデータを添付した場合(172行目)は、図18の右端のように「GET法・POST法の両方でデータが渡せる」ことになります。
但しメリットが2倍になるのか?と言うと、逆に「URLだけでは情報は全て伝わらない」+「セキュリティも中途半端」でデメリットが2倍になっている気がするので、お勧めは出来ません。

1-2-5.その他のOptions 

-H header, --header header」を使用することで、リクエストヘッダを追加する事が可能です。
上記のようなPOST法を「XMLHTTPオブジェクト」で使う場合は、送信するクライアントデータは キー = 値 という形式だということを示すため、ヘッダに「Content-type : application/x-www-form-urlencoded」という項目追加が必要になります。
しかし「-d, --data, --data-urlencode」でPOSTデータを指定した場合は、勝手にヘッダに「Content-type : application/x-www-form-urlencoded」が追加されますので、改めて -H, --header で追加する必要は無さそうです。
-L, --location」は、目的のファイルが指定したURLでは無く、リダイレクト先に存在する場合にはサーバーの指示に従う というOptionのようです。
但しこのOptionを付けておくと「サーバー側の意図で勝手にリダイレクト」される危険があるため、ちょっと慎重に検討した方が良いかもしれません。信頼のおけるサーバーである事を確認の上使うことをお勧めします。

2.Win32APIを使用 

2-1.URLDownloadToFileを使ったダウンロード 

APIのURLDownloadToFile関数を使うためには、まずモジュール先頭部(宣言部)で下記のようなDeclare宣言をしておく必要があります。
  1. '========== ⇩(8) URLDownloadToFile関数のDeclare宣言 ============
  2. #If Win64 Then   ' Office 64bitアプリケーション用
  3.  Declare PtrSafe Function URLDownloadToFile Lib "urlmon" Alias "URLDownloadToFileA" ( _
  4.   ByVal pCaller As LongPtr, ByVal szURL As String, ByVal szFileName As String, _
  5.   ByVal dwReserved As Long, ByVal lpfnCB As LongPtr) As Long
  6. #Else   ' Office 32bitアプリケーション用
  7.  Declare Function URLDownloadToFile Lib "urlmon" Alias "URLDownloadToFileA" ( _
  8.   ByVal pCaller As LongPtr, ByVal szURL As String, ByVal szFileName As String, _
  9.   ByVal dwReserved As Long, ByVal lpfnCB As LongPtr) As Long
  10. #End If
図19

使用するアプリ(ここではExcel)が64ビットか32ビットかで、Declare宣言文が異なるため、251行目「#If Win64 Then」のIf文で分けています。64ビット版の場合は252~254行目、32ビット版の場合は256~258行目となります。
違いは64ビット版の方は「Declare PtrSafe Function ・・・」と "PtrSafe" が入ります。
それ以外に64ビット版ではメモリー番地をLongLong型(8バイト)で表すため、32ビット版のLong型(4バイト)と区分するために「64ビットはLongPtr、32ビットはLongで記載」と説明しているサイトもあります。しかしLongPtrは「アプリが64ビットだったらLongLong型、32ビットだったらLong型」に自動で切り替えるというものなので、両ビットとも同じLongPtrを使用しても問題ありません。
また、API関数の引数や戻り値を各プロシージャ内で変数宣言する場合、変数型とDeclare宣言型とが異なっているとエラーとなります(変数宣言型は LongLong ≠ LongPtr ≠ Long)。ですので両ビットで同じLongPtrで宣言した方が、プログラムを書く上でも楽になります。

2-1-1.URLDownloadToFile 

URLDownloadToFile関数の引数・戻り値の内容は以下になります。
役割関数名宣言
引数引数の内容構造体戻り値
ファイルを
ダウンロード
URLDownloadToFileDeclare PtrSafe Function URLDownloadToFile Lib "urlmon" Alias "URLDownloadToFileA" ( _
  ByVal pCaller As LongPtr, ByVal szURL As String, ByVal szFileName As String, _
  ByVal dwReserved As Long, ByVal lpfnCB As LongPtr) As Long
pCaller
szURL
szFileName
dwReserved
lpfnCB
呼出し元(0を指定)
取得するURL
保存するファイル名
予約済み(0を指定)
コールバック関数へのポインタ(0を指定)
成功=0
失敗=ゼロ以外
(図21参照)
図20

URLDownloadToFile関数に指定する引数は全部で5個です。しかし、第2引数(szURL)に取得するURLを、第3引数(szFileName)に保存するファイル名を指定するだけで、残りは全てゼロを指定しておけば良いようです。
寄り道(URLDownloadToFileAとURLDownloadToFileWの違い)
ここでは、大元の関数名として「URLDownloadToFileA」を使っています。
一方似た名前で「URLDownloadToFileW」を大元の関数として使う場合は、URLを指定する引数szURL、保存ファイル名を指定する引数szFileNameは、両方ともLongPtr型で宣言をします。
つまりURLやファイル名の先頭番地を指定する事になるため、「StrPtr(URL)」や「StrPtr(ファイル名)」のように指定する必要があります。

URLDownloadToFile関数の戻り値は、以下のようになっているようです。
定数内容
S_OK0
成功
E_OUTOFMEMORY0x8007000E
(-2147024882)
バッファ長が無効,メモリ不足
INET_E_ERROR_FIRST
INET_E_INVALID_URL
0x800C0002
(-2146697214)
URLを解析できず
INET_E_NO_SESSION0x800C0003
(-2146697213)
セッションが確立されず
INET_E_CANNOT_CONNECT0x800C0004
(-2146697212)
接続に失敗
INET_E_RESOURCE_NOT_FOUND0x800C0005
(-2146697211)
サーバーまたはプロキシが見つからず
(ドメインのミス、ネット未接続 等)
INET_E_OBJECT_NOT_FOUND0x800C0006
(-2146697210)
オブジェクトが見つからず(ファイル名のミス 等)
INET_E_DATA_NOT_AVAILABLE0x800C0007
(-2146697209)
接続されたがデータを取得出来ない
INET_E_DOWNLOAD_FAILURE0x800C0008
(-2146697208)
ダウンロードに失敗し、接続が中断。(拡張子?のミス 等)
INET_E_AUTHENTICATION_REQUIRED0x800C0009
(-2146697207)
アクセスするには認証が必要
INET_E_NO_VALID_MEDIA0x800C000A
(-2146697206)
許容していないMIMEタイプ
INET_E_CONNECTION_TIMEOUT0x800C000B
(-2146697205)
接続がタイムアウト
INET_E_INVALID_REQUEST0x800C000C
(-2146697204)
リクエストは無効
INET_E_UNKNOWN_PROTOCOL0x800C000D
(-2146697203)
プロトコルが不明
INET_E_SECURITY_PROBLEM0x800C000E
(-2146697202)
セキュリティ問題が発生
INET_E_CANNOT_LOAD_DATA0x800C000F
(-2146697201)
オブジェクトを読み込めず
INET_E_CANNOT_INSTANTIATE_OBJECT0x800C0010
(-2146697200)
オブジェクトをインスタンス化できず
INET_E_DEFAULT_ACTION0x800C0011
(-2146697199)
デフォルトのセキュリティマネージャを使用する必要有り
INET_E_USE_DEFAULT_PROTOCOLHANDLER0x800C0011
(-2146697198)
デフォルトのプロトコルハンドラーを使用する
INET_E_USE_DEFAULT_SETTING0x800C0012
(-2146697197)
デフォルト設定を使用する
INET_E_QUERYOPTION_UNKNOWN0x800C0013
(-2146697196)
不明
INET_E_REDIRECT_FAILED
INET_E_REDIRECTING
0x800C0014
(-2146697195)
リダイレクトされた、またはリダイレクト不可
INET_E_REDIRECT_TO_DIR0x800C0015
(-2146697194)
ディレクトリにリダイレクトされた
INET_E_CANNOT_LOCK_REQUEST0x800C0016
(-2146697193)
リソースをロック出来ず
INET_E_USE_EXTEND_BINDING0x800C0017
(-2146697192)
拡張バインディングで再リクエストする
INET_E_TERMINATED_BIND0x800C0018
(-2146697191)
バインディングが終了
INET_E_INVALID_CERTIFICATE0x800C0019
(-2146697190)
SSL証明書が無効
INET_E_RESERVED_10x800C001A
(-2146697189)
(IE8の予約)
INET_E_BLOCKED_REDIRECT_XSECURITYID0x800C001B
(-2146697188)
SIDが一致せず、ブロックされた
INET_E_CODE_DOWNLOAD_DECLINED0x800C0100
(-2146696960)
ダウンロードがユーザーによって拒否された
INET_E_RESULT_DISPATCHED0x800C0200
(-2146696704)
中止呼び出しをキャンセルした
INET_E_CANNOT_REPLACE_SFP_FILE0x800C0300
(-2146696448)
SFPファイルは保護されている為、置換不可
INET_E_CODE_INSTALL_SUPPRESSED0x800C0400
(-2146696192)
ページでActiveXコントロールのインストールが制限されている
INET_E_CODE_INSTALL_BLOCKED_BY_HASH_POLICY0x800C0500
(-2146695936)
レジストリキーポリシーにより、ActiveXコントロールのインストールは不許可
INET_E_DOWNLOAD_BLOCKED_BY_INPRIVATE
INET_E_ERROR_LAST
0x800C0501
(-2146695935)
プライベートブラウジングセッションの為、ダウンロードは許可されず
図21

なおMicrosoftでは、URLDownloadToFile関数の戻り値は「S_OK(値=0)」「E_OUTOFMEMORY(値=-2147024882)」「INET_E_DOWNLOAD_FAILURE(値=-2146697208)」の3種と説明しているのですが、URLを少し細工してみただけでもそれ以外のエラー値が戻るため、関係ありそうなエラー(INET_E_・・・)は全て載せました。但しネット接続をOFFにして試してみるとINET_E_CANNOT_CONNECT では無くINET_E_RESOURCE_NOT_FOUNDが戻る事から、出現しないエラー項目もありそうです。

2-1-2.コード例 

URLDownloadToFile関数を使用したコード例が以下になります。
  1. '========== ⇩(9) URLDownloadToFileでのダウンロード ============
  2. Sub Test007()
  3.  Const FN As String = "https://atsumitm.iobb.net/its/excel/" & "Test_UTF8N.txt"
  4.  Dim Temp As String   '←ダウンロードされる先
  5.  Dim Res As Long    '←URLDownloadToFileの戻り値
  6.  Temp = Environ("TEMP") & "¥" & "its-053.txt"   '←ダウンロード先のパス+ファイル名
  7.  If Not Dir(Temp) = "" Then
  8.   Kill Temp
  9.  End If
  10. ' DeleteUrlCacheEntry (FN)   '←キャッシュ削除
  11.  Res = URLDownloadToFile(0, FN, Temp, 0, 0)
  12.  If Res = 0 Then
  13.   With CreateObject("ADODB.Stream")
  14.    .type = 2
  15.    .Charset = "UTF-8"
  16.    .Open
  17.     .LoadFromFile Temp
  18.     MsgBox .readtext
  19.    .Close
  20.   End With
  21.  End If
  22. End Sub
図22

284行目「Res = URLDownloadToFile(0, FN, Temp, 0, 0)」では、URLDownloadToFile関数を使い、指定のURL(第2引数の定数FN)からファイルをダウンロードし、指定のファイル(第3引数の変数Temp)に保存しています。ダウンロードが成功したか否かを調べるため、戻り値を変数Resに代入しています。
URLDownloadToFile関数の戻り値がゼロならば「ダウンロード成功」なので286行目「If Res = 0 Then 」で仕訳け、成功の時のみテキスト内容を表示(287~295行目)させています。
Windowsコマンド(bitsadmin.exeやcurl.exe)の時には、コマンド実行直後には「ファイルが認識されない」「ファイルの中身が無い」状態が発生するため、少し時間を置いてからファイル内容を読み込む処理をしていたのですが、URLDownloadToFileの場合は「実行直後にファイルは認識され、ファイルの中身も完成している」状態になるようです。
そのためURLDownloadToFile実行直後、すぐに処理を実行しています。

2-1-3.キャッシュ削除(DeleteUrlCacheEntry関数) 

なお「処理の高速化」や「ネットへの不要なアクセスを少なくする」という目的の為、一度アクセスしたファイル内容はプロキシやWebサーバー、また自分のPC内にキャッシュ(一時保存)されるという機能があります。キャッシュは定期的に更新されますが、せっかくアクセスした内容が「もしかしたら古いデータ(=キャッシュ)」だった という事になるかもしれません。
その「キャッシュを削除」し、最新のファイルにアクセスするのが DeleteUrlCacheEntry関数で、上記のURLDownloadToFile関数と同時に使用される事が多いようです。
DeleteUrlCacheEntry関数を使用するには、下記のようなDeclare宣言が必要になります。
  1. '========== ⇩(10) DeleteUrlCacheEntry関数の宣言 ============
  2. #If Win64 Then   ' Office 64bitアプリケーション用
  3.  Declare PtrSafe Function DeleteUrlCacheEntry Lib "wininet" Alias "DeleteUrlCacheEntryA" ( _
  4.   ByVal lpszUrlName As String) As Long
  5. #Else   ' Office 32bitアプリケーション用
  6.  Declare Function DeleteUrlCacheEntry Lib "wininet" Alias "DeleteUrlCacheEntryA" ( _
  7.   ByVal lpszUrlName As String) As Long
  8. #End If
図23

DeleteUrlCacheEntry関数には、キャッシュを削除したいURLを指定します。削除が成功したか否かは戻り値で判断できます。
役割関数名宣言
引数引数の内容構造体戻り値
キャッシュを削除DeleteUrlCacheEntryDeclare PtrSafe Function DeleteUrlCacheEntry Lib "wininet" Alias "DeleteUrlCacheEntryA" ( _
  ByVal lpszUrlName As String) As Long
lpszUrlNameURLTrue(値=1)=削除完了
False(値=0)=削除対象無し,削除失敗
図24

図22の見え消しにしている282行目「DeleteUrlCacheEntry (FN)」を実行する事で、自PC内のキャッシュは削除され、284行目「Res = URLDownloadToFile(0, FN, Temp, 0, 0)」では新しいファイルをダウンロードする事が出来ます。
但しDeleteUrlCacheEntry関数で削除されるキャッシュは「自分のPC内に保存してあるキャッシュのみ」です。プロキシやWebサーバー上のキャッシュは残ったままなので「DeleteUrlCacheEntry関数を実行したから、Web上の本当の最新ファイルを取得できた」とはならない事には注意が必要です。
なお図23で使用した元の関数はDeleteUrlCacheEntryAですが、DeleteUrlCacheEntryWを使う事も可能です。その場合はよりみちと同様に「引数をLongPtr型で宣言」するのと併せ「StrPtr(URL)」のようにメモリー番地で指定します。

2-2.InternetReadFile関数等を使ったダウンロード 

wininetライブラリのInternetReadFile関数等を使用する事で、Web上のファイルをダウンロード(「読み取る」と言った方が正確かもしれません)できます。 プログラムの流れとしては下記のようになります。
wininetでWebファイルをダウンロードする流れ
図25

事前処理として「InternetOpen」「InternetOpenUrl」関数でハンドルを取得し、その後「InternetReadFile」関数を使ってWeb上のファイルを一定バイト単位で取得します。その一定バイト単位のデータをOpenステートメント等を利用してPC上のファイルに復元させます。
ダウンロードが完了したら、取得したハンドルを「InternetCloseHandle」関数で閉じます。
その4つの関数のDeclare宣言は以下のようになります。
  1. '========== ⇩(11) wininetの各関数のDeclare宣言 ============
  2. #If Win64 Then
  3.  Declare PtrSafe Function InternetOpen Lib "wininet" Alias "InternetOpenA" ( _
  4.   ByVal sAgent As String, ByVal lAccessType As Long, ByVal sProxyName As String, _
  5.   ByVal sProxyBypass As String, ByVal lFlags As Long) As LongPtr
  6.  Declare PtrSafe Function InternetOpenUrl Lib "wininet" Alias "InternetOpenUrlA" ( _
  7.   ByVal hInternetSession As LongPtr, ByVal lpszUrl As String, ByVal lpszHeaders As String, _
  8.   ByVal dwHeadersLength As Long, ByVal dwFlags As Long, ByVal dwContext As LongPtr) As LongPtr
  9.  Declare PtrSafe Function InternetReadFile Lib "wininet" ( _
  10.   ByVal hFile As LongPtr, ByRef lpBuffer As Any, ByVal lNumBytesToRead As Long, _
  11.   ByRef lNumberOfBytesRead As Long) As Long
  12.  Declare PtrSafe Function InternetCloseHandle Lib "wininet" (ByVal HINet As LongPtr) As Integer
  13. #Else
  14.  Declare Function InternetOpen Lib "wininet" Alias "InternetOpenA" ( _
  15.   ByVal sAgent As String, ByVal lAccessType As Long, ByVal sProxyName As String, _
  16.   ByVal sProxyBypass As String, ByVal lFlags As Long) As LongPtr
  17.  Declare Function InternetOpenUrl Lib "wininet" Alias "InternetOpenUrlA" ( _
  18.   ByVal hInternetSession As LongPtr, ByVal lpszUrl As String, ByVal lpszHeaders As String, _
  19.   ByVal dwHeadersLength As Long, ByVal dwFlags As Long, ByVal dwContext As LongPtr) As LongPtr
  20.  Declare Function InternetReadFile Lib "wininet" ( _
  21.   ByVal hFile As LongPtr, ByRef lpBuffer As Any, ByVal lNumBytesToRead As Long, _
  22.   ByRef lNumberOfBytesRead As Long) As Long
  23.  Declare Function InternetCloseHandle Lib "wininet" (ByVal HINet As LongPtr) As Integer
  24. #End If
図26

2-2-1.InternetOpen関数 

InternetOpen関数は、WinINet関数のアプリケーションの使用を初期化する機能です。
役割関数名宣言
引数引数の内容構造体戻り値
初期化InternetOpenDeclare PtrSafe Function InternetOpen Lib "wininet" Alias "InternetOpenA" ( _
  ByVal sAgent As String, ByVal lAccessType As Long, ByVal sProxyName As String, _
  ByVal sProxyBypass As String, ByVal lFlags As Long) As LongPtr
sAgent
lAccessType
sProxyName
sProxyBypass
lFlags
アプリケーション名
アクセス種(図28)
プロキシサーバー名
バイパスさせるプロキシのリスト
オプション(図29)
成功=接続に使用されるハンドル
失敗=Null
図27

引数は5つあります。
第1引数(sAgent)は、WinINet関数を呼び出すアプリケーション名です。
省略さえしなければ、「""(長さゼロの文字列)」でも構わないようです。
第2引数(lAccessType)は、アクセスの種類を下記の中から指定します。
定数内容
INTERNET_OPEN_TYPE_PRECONFIG0レジストリ設定を使用
INTERNET_OPEN_TYPE_DIRECT1ネットに直接アクセス
INTERNET_OPEN_TYPE_PROXY3プロキシを経由
INTERNET_OPEN_TYPE_PRECONFIG_WITH_NO_AUTOPROXY4java/script/INSの使用を禁止
図28

第3引数(sProxyName)は、第2引数にINTERNET_OPEN_TYPE_PROXY(値=3)を指定した際に、プロキシサーバーの名前を指定します。それ以外を指定した場合はNull(VBAではvbNullString)を指定します。
第4引数(sProxyBypass)は、第2引数にINTERNET_OPEN_TYPE_PROXY(値=3)を指定した際に、バイパスさせるプロキシのホスト名またはIPアドレスのリストを指定します。それ以外を指定した場合はNull(VBAではvbNullString)を指定します。
第5引数(lFlags)は、以下の値を指定します。必要ない場合はゼロを指定します。
定数内容
INTERNET_FLAG_ASYNC0x10000000データをサーバーから取得する(非同期)
INTERNET_FLAG_FROM_CACHE
INTERNET_FLAG_OFFLINE
0x01000000データをキャッシュから取得。
キャッシュが無い場合はエラー
図29

キャッシュが無い場合に、第5引数に「0x01000000(VBAでは &H1000000 や WorksheetFunction.Hex2Dec(1000000) 等と記載)」を指定すると、エラーが出るとMicrosoftでは説明しています。しかしVBAで実行してみるとエラーとはなりませんが、結果的にファイルが取得できない(空のデータが返る)状態になるようです。

2-2-2.InternetOpenUrl関数 

InternetOpenUrlは、指定したURLのリソース(ファイル)を開きます。
役割関数名宣言
引数引数の内容構造体戻り値
リソースを開くInternetOpenUrlDeclare PtrSafe Function InternetOpenUrl Lib "wininet" Alias "InternetOpenUrlA" ( _
  ByVal hInternetSession As LongPtr, ByVal lpszUrl As String, ByVal lpszHeaders As String, _
  ByVal dwHeadersLength As Long, ByVal dwFlags As Long, ByVal dwContext As LongPtr)
  As LongPtr
hInternetSession
lpszUrl
lpszHeaders
dwHeadersLength
dwFlags
dwContext
セッションへのハンドル
URL
送信されるヘッダー
ヘッダのサイズ
種別(図31)
アプリケーション定義値
正常接続=URLへのハンドル
失敗=Null
図30

引数は6つです。
第1引数(hInternetSession)には、InternetOpen関数で取得したハンドル(戻り値)を指定します。
第2引数(lpszUrl)は、開くリソースのURLを指定します。
第3引数(lpszHeaders)は、サーバに送るヘッダです。通常は設定不要なのでNull(VBAではvbNullString)とします。
第4引数(dwHeadersLength)は、第3引数で指定したヘッダの文字数です。ヘッダを指定しない場合はゼロになります。
第5引数(dwFlags)は、種別を指定するビットマスク値で、下記のように多くの項目があります。不要の時はゼロを指定するようです。
定数内容
INTERNET_FLAG_NEED_FILE0x00000010ファイルがキャッシュできない場合に一時ファイルを作成
INTERNET_FLAG_PRAGMA_NOCACHE0x00000100キャッシュがプロキシに存在する場合でも、Webサーバーに強制的にリクエストする
INTERNET_FLAG_NO_UI0x00000200Cookieダイアログボックスを無効にする
INTERNET_FLAG_HYPERLINK0x00000400再読み込みすべきかどうかの判断時に有効期限・最終更新時刻がサーバから返されない場合強制的に再読み込み
INTERNET_FLAG_RESYNCHRONIZE0x00000800FTPリソースをサーバから再読み込み
INTERNET_FLAG_IGNORE_CERT_CN_INVALID0x00001000証明書のチェックを無効化
INTERNET_FLAG_IGNORE_CERT_DATE_INVALID0x00002000証明書の有効期間チェックを無効化
INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTPS0x00004000HTTP→HTTPSへのリダイレクトを許可
INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTP0x00008000HTTPS→HTTPへのリダイレクトを許可
INTERNET_FLAG_NO_AUTH0x00040000認証を自動的にしない
INTERNET_FLAG_NO_COOKIES0x00080000リクエストにCookieヘッダーを自動的に追加しない
INTERNET_FLAG_NO_AUTO_REDIRECT0x00200000自動的にリダイレクトしない
INTERNET_FLAG_KEEP_CONNECTION0x00400000接続可能なら、サポートするデータを使う
INTERNET_FLAG_SECURE0x00800000安全なトランザクションを使用。SSL/PCT使用に変換され、HTTP要求のみで有効
INTERNET_FLAG_NO_CACHE_WRITE0x04000000返されたエンティティをキャッシュに追加しない
INTERNET_FLAG_PASSIVE0x08000000パッシブモードで接続
INTERNET_FLAG_EXISTING_CONNECT0x20000000可能ならばサーバとの接続を再利用する
INTERNET_FLAG_RAW_DATA0x40000000FTPディレクトリ情報の取得時に、データをWIN32_FIND_DATA構造体として返す
INTERNET_FLAG_RELOAD0x80000000要求したファイルをキャッシュからで無く、強制的にサーバから再読み込み
図31

第6引数(dwContext)は、コールバック関数に渡されるアプリケーション定義値です。VBAではゼロを指定すれば良さそうです。

2-2-3.InternetReadFile関数 

InternetReadFileは、InternetOpenUrl関数によって開かれたハンドルからデータを読み取ります。
役割関数名宣言
引数引数の内容構造体戻り値
データを読み取るInternetReadFileDeclare PtrSafe Function InternetReadFile Lib "wininet" ( _
  ByVal hFile As LongPtr, ByRef lpBuffer As Any, ByVal lNumBytesToRead As Long, _
  ByRef lNumberOfBytesRead As Long) As Long
hFile
lpBuffer
lNumBytesToRead
lNumberOfBytesRead
URLハンドル
配列の先頭要素
読み取るバイト数
読み取ったバイト数
True(値=1)=削除完了
False(値=0)=削除対象無し,削除失敗
図32

引数は4つあります。
第1引数(hFile)は、InternetOpenUrl関数の戻り値(URLへのハンドル)を指定します。
第2引数(lpBuffer)は、データを受信するバッファーへのポインターです。VBAではデータはバイト単位の配列で受け取りますので、通常であれば事前に宣言したByte型配列の最初の要素を指定します(配列の途中を指定する事も可能)。
第3引数(lNumBytesToRead)は、読み取るバイト数です。第2引数の為に準備した配列の要素数よりも大きな値を指定してしまうと、配列以外のメモリ位置に書き込まれてしまうので、最悪Windowsがクラッシュするので注意が必要です。
第4引数(lNumberOfBytesRead)には読み取ったバイト数が戻されます。ですので、その値を変数で受け取り、値を確認する事でファイル終端(End of File)に達したか否かが判断できます。

2-2-4.InternetCloseHandle関数 

InternetCloseHandleは、インターネットハンドルを閉じます。上記でInternetOpen関数およびInternetOpenUrl関数の戻り値としてインターネットハンドルを取得し処理に使用しましたが、必要が無くなったらハンドルは速やかに閉じます。
引数は1つ(HINet)で、ハンドル値を指定します。
役割関数名宣言
引数引数の内容構造体戻り値
ハンドルを閉じるInternetCloseHandleDeclare PtrSafe Function InternetCloseHandle Lib "wininet" (ByVal HINet As LongPtr) As Integer
HINetハンドルTrue(値=1)=削除完了
False(値=0)=削除対象無し,削除失敗
図33

2-2-5.ファイルダウンロードのコード例 

InternetReadFile関数等を使用したファイルのダウンロードのコード例は以下になります。
  1. '========== ⇩(12) InternetReadFile関数等でのダウンロード ============
  2. Sub Test008()
  3.  Const FN As String = "https://atsumitm.iobb.net/its/excel/" & "Test_UTF8N.txt"
  4.  Dim Temp As String    'ダウンロードされる先
  5.  Dim iOpen As LongPtr   '接続に使用されるハンドル
  6.  Dim iURL As LongPtr    'URLへのハンドル
  7.  Dim buf() As Byte     '読み取ったデータを一時的に保存する動的配列
  8.  Dim dwSize As Long    '読み取ったバイトサイズ
  9.  Temp = Environ("TEMP") & "¥" & "its-053.txt"   '←ダウンロード先のパス+ファイル名
  10.  If Not Dir(Temp) = "" Then
  11.   Kill Temp
  12.  End If
  13.  Open Temp For Binary As #1
  14.   iOpen = InternetOpen("Excel VBA", 1, vbNullString, vbNullString, 0)
  15.   iURL = InternetOpenUrl(iOpen, FN, vbNullString, 0, &H80000000, 0)
  16.   Do
  17.    ReDim buf(1 To 1024)
  18.    InternetReadFile iURL, buf(1), 1024, dwSize
  19.    If dwSize = 0 Then
  20.     Exit Do
  21.    Else
  22.     ReDim Preserve buf(1 To dwSize)
  23.     Put #1, , buf
  24.    End If
  25.   Loop
  26.   InternetCloseHandle iURL
  27.   InternetCloseHandle iOpen
  28.  Close #1
  29.  With CreateObject("ADODB.Stream")
  30.   .type = 2
  31.   .Charset = "UTF-8"
  32.   .Open
  33.    .LoadFromFile Temp
  34.    MsgBox .readtext
  35.   .Close
  36.  End With
  37. End Sub
図34

385行目「Open Temp For Binary As #1」では、保存するファイルをバイナリー型で開いておきます。
387行目「iOpen = InternetOpen("Excel VBA", 1, vbNullString, vbNullString, 0)」ではInternetOpen関数を使い、388行目のInternetOpenUrl関数に使用する接続ハンドルを戻し、変数iOpenに代入しています。
なおInternetOpen関数の引数には、第1引数(アプリケーション名)として"Excel VBA"としましたが、文字列であれば何でもOKです。また第2引数は値=1(ネットに直接アクセス)を指定しましたが、プロキシサーバーを使っている等の場合は、その環境に合わせてください。
第3・第4引数はNullを指定するため「vbNullString(値ゼロの文字列)」を使用しましたが、「""(長さゼロの文字列)」でも正しく動くようです。
第5引数は、図29のように「0」「0x10000000」「0x01000000」のどれかを指定します。この中で最も良さそうなのは、データをWebサーバーから直接取得できそうな「0x10000000」ですが、注意が必要です。
InternetOpenUrl関数の第5引数(図31)にも強制的にサーバーから再読み込みできそうな「0x00000100」「0x00000400」「0x80000000」という種別が選べるので、その組み合わせで調べた結果が下記です。
InternetOpenUrlの第5引数
0  0x000001000x000004000x80000000
InternetOpen
の第5引数
0
0x10000000×××
0x01000000××
 〇:Webデータを読む △:キャッシュを読む ×:空データになる
図35

この結果から、キャッシュでは無くWeb上のファイルを直接取得するには、以下の値の組み合わせが良さそうです。
 ・InternetOpenの第5引数=「0」
 ・InternetOpenUrlの第5引数=「0x80000000」
この結果を反映し、387行目のInternetOpenの第5引数にはゼロを指定しています。
388行目「iURL = InternetOpenUrl(iOpen, FN, vbNullString, 0, &H80000000, 0)」は、ファイルを開き、そのURLハンドルを変数iURLに戻しています。
InternetOpenUrl関数の第1引数には、387行目で得た「接続に使用するハンドル iOpen 」を指定し、第2引数には接続するURLである定数FN(372行目で宣言済み)を指定します。
第3引数の追加ヘッダーはNullで良いため「vbNullString(値ゼロの文字列)」を指定しました。なお「""(長さゼロの文字列)」でも正しく動くようです。
第3引数をNull(ヘッダに追加をしない)とした為、第4引数(ヘッダのサイズ)はゼロ値指定となります。
第5引数の種別は上記図35で説明したように、Webサーバーの最新データを取得する為に「&H80000000」としました。
第6引数のコールバック関数に渡されるアプリケーション定義値は、ゼロで良さそうです。
390~400行目のDo~Loop内では、Web上のファイルから一定のバイト単位(ここでは1024バイト)でデータをダウンロードし、保存ファイル(変数Temp)を作り上げています。
プログラムの流れ自体は難しく無いのですが、ReDimが2箇所で出てくる等で分かり難いかと思います。ですので、1行1行コードを説明する代わりに、下記のような流れ図で説明をします。なお、図34では1024バイト単位でダウンロードしていましたが、流れ図を簡単にするため図36では5バイト単位とし、またWeb上のファイルのサイズも9バイトとしています。
WebデータをInternetReadFileで読み取り、保存ファイルを作成する流れ
図36

Do~Loopの1回目では、まずReDimで配列のサイズを決めます。Preserve無しなので、配列は初期化(391行目)されます。
次にInternetReadFile関数でWeb上のデータを読み取り(392行目)ますが、配列のサイズの分(図36では5バイト)だけ読み取り、データを配列bufに格納します。Web上のデータ(9バイト)の内、先頭から5バイト分を読み取りましたので、InternetReadFile関数の第4引数dwSizeは5(バイト)という値が戻ってきます。
dwSizeはゼロでは無いため、394~399行目のIf文ではElse側を実行する事になります。まず397行目「ReDim Preserve buf(1 To dwSize) 」で配列サイズを変更するのですが、dwSizeは391行目で初期化した時の配列サイズと同じですし、Preserveを付けていますので、結果として配列bufは変化無しとなります。
その配列bufを398行目「Put #1, , buf」で保存ファイルに書き込んでいます。
Do~Loopの2回目でも、まずReDimで配列のサイズを決め、且つPreserveが無い為、配列は初期化(391行目)されます。
次にInternetReadFile関数でWeb上のデータを読み取り(392行目)ますが、Do~Loopの1回目でWebデータの5バイト目までは読み取り済みなので、6バイト目から読み取り配列に格納していきます。しかしWeb上のファイルは9バイト目までしかデータがありませんので、4バイト(=dwSize)分までが配列bufに入ります。
dwSizeはゼロでは無いため、394~399行目のIf文ではElse側を実行する事になります。まず397行目「ReDim Preserve buf(1 To dwSize) 」で配列bufのサイズを変更しますが、dwSize = 4 なので、データが格納されなかった要素が切り捨てられる形になります。
その配列bufを398行目「Put #1, , buf」で保存ファイルに書き込みますが、Do~Loopの1回目で書き込みのポインターは5バイト目の後ろにあります(「Openステートメント編」参照)ので、後ろ側に追記される形になります。
Do~Loopの3回目でも、まずReDimで配列のサイズを決め、且つPreserveが無い為、配列は初期化(391行目)されます。
次にInternetReadFile関数でWeb上のデータを読み取り(392行目)ますが、Do~Loopの2回目まででWebデータを全て読み込み済みなので、新たに読み込む事はできず(dwSize = 0)、配列bufも空のままとなります。
dwSizeがゼロとなったため、394~399行目のIf文の395行目「Exit Do」を実行することになり、Do~Loopを抜け出します。402~403行目では、387行目・388行目で作成した各ハンドルを削除した後、404行目「Close #1」で閉じる事で、保存ファイルが完成します。
以上は、Web上ファイルのサイズ および 1回で読み取るサイズを小さくしての説明(図36)でしたが、図34では391~392行目でも分かる通り、1024バイト(=1kB)毎にInternetReadFileでダウンロードを繰り返し、保存ファイルを作成する処理をさせています。
なお、処理単位を小さくすればInternetReadFile関数を実行する回数が増えるため処理が遅くなりますし、処理単位を大きくすればPCのメモリー容量を消費しますので、適当な処理単位を選択する必要があります。
保存ファイルが完成した後は、406~414行目でファイルを読み取り表示させています。
その際、bitsadminコマンド(図06)・curlコマンド(図11)の時のような一定時間処理を待つ事は必要無さそうです。

3.アプリを経由してのダウンロード 

Web上のファイル内容をアプリ上に展開し、その展開データをPC内のファイルに保存するのが以下の手法です。ファイル種類をテキストに絞った上で、とりあえず思いつくのが「Excel」と「Internet Explorer」の2種類です。
但し、テキスト内容をアプリ上に正しく展開できる文字コードは限られてしまうため、ダウンロードとは言えない手法かもしれませんが、もしアプリ上でWebデータを使用するのが目的の1つであるのなら、少しはこの手法が役に立つかもしれません。
なおダウンロードしたいファイルが例えば画像ファイルなどの場合には、当然これらとは異なるアプリを経由する事になると思いますが、この手法でダウンロードが可能か否かは今回試していません。

3-1.Excelワークシートを使用 

Web上のテキストファイルをExcelのワークシート上に展開し、そのワークシート上のデータをテキストファイルとして保存する事で「ダウンロードしたように見せかける」方法が以下のコードになります。
なお、この方法で対応できるWeb上ファイルの文字コードは「Shift-JIS」のみです。
  1. '========== ⇩(13) Excelシートを経由してダウンロード ============
  2. Sub Test009()
  3.  Const FN As String = "https://atsumitm.iobb.net/its/excel/" & "Test_SJIS.txt"
  4.  Dim Temp As String
  5.  Temp = Environ("TEMP") & "¥" & "its-053.txt"
  6.  If Not Dir(Temp) = "" Then
  7.   Kill Temp
  8.  End If
  9.  With Workbooks.Open(Filename:=FN)
  10.   Application.DisplayAlerts = False
  11.    .SaveAs Filename:=Temp
  12.   Application.DisplayAlerts = True
  13.   .Close
  14.  End With
  15.  With CreateObject("ADODB.Stream")
  16.   .type = 2
  17.   .Charset = "Shift-JIS"
  18.   .Open
  19.    .LoadFromFile Temp
  20.    MsgBox .readtext
  21.   .Close
  22.  End With
  23. End Sub
図37

441行目「With Workbooks.Open(Filename:=FN) 」で、Web上のテキストファイルをExcelで開いています。
このOpenメソッドの引数Filenameには、通常は自PC内などのLAN上のファイルを指定するのですが、Web上のURLを指定することも可能なようです。なお引数Filename以外にもOpenメソッドには複数の引数が指定でき、その中には「Origin」という開くテキストファイルの文字コードを指定できる引数もあります。しかし、FilenameにURLを指定してしまうと「Origin引数が指定できない(エラーが出る)」ようです。
Origin引数が使えないため、この手法に対応できるWebファイルは「Excelのシートに貼り付けても文字化けしない文字コード」だけと言うことになり、試したところ「Shift-JIS」のみが対応するようです。そのため432行目「Const FN As String = "https://atsumitm.iobb.net/its/excel/" & "Test_SJIS.txt"」では、Shift-JISのテキストファイル(Test_SJIS.txt)を指定しています。
なお引数Filenameは第1引数なので、441行目は「With Workbooks.Open(FN)」としてもOKです。
またExcelには「Workbooks.OpenText」というメソッドもありますが、こちらはURLを指定するとエラーが発生するため使用できません。
テキストファイルを開いたExcelシートを、443行目「.SaveAs Filename:=Temp 」でテキストファイルとして保存しています。保存先は変数Temp(435行目で設定)です。
寄り道(SaveAsメソッドでの保存形式)
443行目のSaveAsメソッドには、今回 Filename値のみを指定しましたが、これ以外にも多くの引数が指定できます。
その中の1つに「FileFormat」があります。この引数は「保存する時のファイル形式」を指定するものですが、今回テキストデータをExcelシートに表示し、そのExcelをSaveAsで保存する場合には、以下のようなFileFormat値が設定可能なようです。
定数内容拡張子文字コード
xlCSV6CSVファイル*.csvShift-JIS
xlCSVUTF862UTF8 CSVファイル*.csvUTF-8(BOM付)
xlCurrentPlatformText-4158現プラットフォームのテキスト*.txtShift-JIS
xlUnicodeText42Unicodeテキスト*.txtUTF-16LE(BOM付)
xlTextPrinter36プリンターテキスト*.prnShift-JIS
xlHtml44HTML形式*.htm, *.htmlUTF-16BE(BOM付)
xlXMLSpreadsheet46XMLファイル*.xmlUTF-8
xlWorkbookNormal-4143旧Excelブック*.xls
xlOpenXMLStrictWorkbook61厳密なExcelブック*.xlsx
xlWorkbookDefault51新Excelブック*.xlsx
xlOpenXMLWorkbookMacroEnabled52マクロ有効Excelブック*.xlsm
図38

443行目では、SaveAsメソッドにFileFormat引数を指定していませんが、データは Shift-JISのテキストとして保存されます。ですので、FileFormat引数に「xlCurrentPlatformText(値=-4158)」を指定していたのと同じ事になります。
この「既定が xlCurrentPlatformText」となっているのは、保存するファイルの拡張子が「.txt」だからでは無く、441行目で開いたのがテキストファイル(拡張子 *.txt)だからのようです。
もし気になる場合は「.SaveAs Filename:=Temp, FileFormat:=xlCurrentPlatformText」と、保存形式を指定して下さい。
なお、定数xlCurrentPlatformTextと同じ値(-4158)を持つ「xlText」を使用してもOKのようです。
また、ブックのプロパティの1つに「.WebOptions.Encoding」と言うのがあります。これは手動での保存時に「ツール → Webオプション → エンコード」で文字コードのリストから選択できる値のようです。
そこでSaveAsの実行前に、このプロパティに他の文字コード値を設定してみたのですが、作成されるファイルの文字コードは変わってくれません(図37の場合は、Shift-JISになる)。
もしこの文字コード設定の方法が分かっても、Excel上に読み込める文字コードがShift-JISのみである事に変わり無いのですが、ちょっと悔しいです。
なお「FileFormat:=xlHtml」を指定するとHTMLデータが出力されるのですが、その文字コードはUTF-16BEとなります。サイトに載せるページをExcelで作る場合は、ちょっと気にしておいた方が良いかもしれません。
一方、手動で html形式で保存する場合は、前述の保存時ダイアログの「ツール → Webオプション → エンコード」で変更は可能なようです。

ひとつ戻って、442行目「Application.DisplayAlerts = False」ではアラートを一旦止めています。これはExcelの保存時に「上書きして良いか?」とか「テキスト保存すると情報が失われる」等のダイアログが出る場合がありますが、VBA実行時にそれらの既知のダイアログによりプログラムが中断してしまうのを防ぐ役目をしています。
但しアラート停止は必要最小限にするため、444行目「Application.DisplayAlerts = True」で元に戻しています。
プログラムの流れを図で表すと、以下のように「Web上のファイル」→「Excelシート上の文字列」→「LAN上のテキストファイル」と、文字列を移動している事になります。
WebテキストファイルをExcelシート経由でダウンロード
図39

なお、Web上のテキストファイルの文字数よりもLAN上のテキストファイルの方が「1文字増えている」のは、文末に改行マーク(vbCrLf)が入るためです。この改行マークについては、図37では削除処理はしていません。
ファイル保存の完了後は、ダウンロードファイルの処理(448行目以降)を行っています。

3-2.Internet Explorerを使用 

Web上のテキストファイルをIE(Internet Explorer)に表示をさせ、その文字データをテキストファイルとして保存する事で「ダウンロードしたように見せかける」方法が以下になります。
なお、この方法で対応できるWeb上ファイルの文字コードは「Shift-JIS」「UTF-8(BOM有/無)」の計3種です。
  1. '========== ⇩(14) Internet Explorerを経由してダウンロード ============
  2. Sub Test010()
  3. ' Const FN As String = "https://atsumitm.iobb.net/its/excel/" & "Test_SJIS.txt"
  4.  Const FN As String = "https://atsumitm.iobb.net/its/excel/" & "Test_UTF8N.txt"
  5. ' Const FN As String = "https://atsumitm.iobb.net/its/excel/" & "Test_UTF8B.txt"
  6.  Dim Temp As String
  7.  Dim IE As Object
  8.  Temp = Environ("TEMP") & "¥" & "its-053.txt"
  9.  If Not Dir(Temp) = "" Then
  10.   Kill Temp
  11.  End If
  12.  Set IE = CreateObject("InternetExplorer.Application")
  13.  With IE
  14.   .Visible = False   'Trueを指定するとIEが開いてしまう
  15.   .navigate FN
  16.   Do While .busy = True Or .readystate < 4
  17.    DoEvents: DoEvents
  18.   Loop
  19.   .Document.Charset = "utf-8"
  20.   .Refresh
  21.   Do While .busy = True Or .readystate < 4
  22.    DoEvents: DoEvents
  23.   Loop
  24.   Open Temp For Output As #1
  25.    Print #1, .document.getElementsByTagName("pre")(0).innerHTML
  26.   Close #1
  27.   .Quit
  28.  End With
  29.  Set IE = Nothing
  30.  With CreateObject("ADODB.Stream")
  31.   .type = 2
  32.   .Charset = "Shift-JIS"
  33.   .Open
  34.    .LoadFromFile Temp
  35.    MsgBox .readtext
  36.   .Close
  37.  End With
  38. End Sub
図40

484行目「Set IE = CreateObject("InternetExplorer.Application")」で、IEオブジェクトを生成します。
486行目「.Visible = False」でIEを起動します。ここでFalseを指定することで、IEを非表示にしています。逆のTrueを指定するとIEが表示された状態で作業する事になるので、邪魔な感じがします。
487行目「.navigate FN」で、Web上のURL(473行目)の内容を読み込みます。
しかしnavigateメソッドを実行した瞬間にIE上に読み込まれる訳ではありません。読み込みが完了するまで待機するのが489~491行目のDo~Loopです。
Do~Loopの条件式は「While .busy = True Or .readystate < 4」で、2つの条件式を並べています。
前半は「.busy = True」です。Busyプロパティは「IEが作業をしているか否か」で、Trueの場合は作業中であることを示ます。しかし、このプロパティはフレーム単位での状態を表すため、複数のフレームを使用しているページの場合は「Busy = Falseとなっても、全てのフレームの作業が完了したとは言えない」事になるようです。
後半は「.readystate < 4」で、ReadyStateプロパティは「Webページのデータのロード状態」を表わし、以下の値です。
ここで使用しているのは READYSTATE_COMPLETE(値=4)なので、データの読み込みが完了するまでDo~Loopを回すことになります。
定数内容
READYSTATE_UNINITIALIZED0未完了状態(既定)
READYSTATE_LOADING1IEオブジェクトのロード中状態
READYSTATE_LOADED2IEオブジェクトのロード完了状態。ただし操作不可能状態
READYSTATE_INTERACTIVE3IEオブジェクトの操作可能状態
READYSTATE_COMPLETE4IEオブジェクトの全データ読み込み完了状態
図41

489行目では「Busy」と「ReadyState」のどちらかの条件が成立している間は待つ事になります。しかし、2つを見比べてみるとReadyStateプロパティのみで条件としては用が足りている気がしますが、一般的には併用して使うようです。もしかしたら歴史的な経緯があるのかもしれません。
ここまでで得られるIE上の表示(Webファイルの文字コード別)は、以下のようになります。
文字コード別のIE上の表示状態
図42

図42の中央の「UTF-8(BOM無)」では、navigateメソッドを実行しただけでは文字化けしている事がわかります。これは、Webファイルの文字コード(UTF-8)とCharsetプロパティ値(shift_jis)が異なっているためです。BOM付(図42の右側)は正しく読み込むので、BOM値を見て文字コードを判別しているのかもしれません。
その為、Webファイルの文字コードがUTF-8(BOM無)の場合は、493行目「.Document.Charset = "utf-8"」でCharsetプロパティを変更し、494行目「.Refresh 」で再表示をさせることで、下図のように正しく表示されるようになります。
なお、Charsetプロパティ値の変更+再表示後も、読み込みを完了させるため496~498行目は必要となります。
UTF-8のBOM無しの場合は、Charset値を変更させ再表示させる
図43

なお、見え消しにしてある474行目「UTF-8(BOM付)」や472行目「Shift-JIS」については、図42の右側・左側のように「正しくCharsetプロパティ値がセットされ、文字化けせずに表示」されるため、493~498行目の処理は無くても問題ありません。
一方で UTF-16系の文字コードでは487行目のnavigateメソッドを実行する時に、ファイルを開くのか保存するのかを選択するIEの「ダウンロードの表示と追跡」のダイアログが出てしまい、テキストがうまく読み込めません。
読み込みさえうまくいけば、493行目で適切な文字コードをセットすれば表示できそうなのですが残念です。
IE上に読み込まれたら、500行目「Open Temp For Output As #1」で保存ファイルを開き、書き込む準備をします。
501行目「Print #1, .document.getElementsByTagName("pre")(0).innerHTML」では、表示されている文字列を保存ファイルに書き込んでいます。ここで「.document」オブジェクトはIE上のドキュメントを表しますが、単純なテキストファイルを読み込んだ場合には「表示されているテキストは <pre>タグ(整形済みテキスト要素)で囲まれている」ようです。
そのため、その <pre>タグで囲まれているテキスト(≒ブラウザ上に表示されているテキスト)を取得するには「.getElementsByTagName("pre")」を使用します。但しタグ名から要素を取得する場合は配列形式で取得されるため、1つ目の要素を指定するために「.getElementsByTagName("pre")(0)」と、末尾に要素番号(0)を記載する必要があります。
そして最後の「.innerHTML」は、 <pre>タグの内側のデータを指定します。逆に .outerHTMLとすると、<pre>タグも含めたデータが取得されます。
502行目「Close #1」でファイルを閉じることでファイルが完成し、504行目「.Quit」でIEを終了させています。今回は486行目「.Visible = False」でIEを開かずに作業をしているので、Quitの実行が無くても見掛けは変わりませんが、タスクとしてメモリー上にIEが残ってしまうため、Quitは必須です。
図で表すと、以下のように「Web上のファイル」→「IE上の文字列」→「LAN上のテキストファイル」と、文字列を移動している事になります。
WebテキストファイルをIE経由でダウンロード
図44

なお、Web上のテキストファイルの文字数よりもLAN上のテキストファイルの方が「1文字増えている」のは、最後に改行マークが入るためです。またIEを経由すると、改行マークは「vbLf」で保存されるようです。
なお図40のようにOpenステートメントでファイル作成をした場合は、保存ファイルはShift-JISとなります(「Openステートメント編」参照)。
この改行マークの増加と種類変更、文字コードの変更については、後処理を行う際には注意が必要そうです。
保存が完了した後、ダウンロードファイルの処理(509行目以降)を行っています。
なお現時点でもIEは標準のブラウザでは無くなっており、将来的にはVBAのIEオブジェクトも消えていく運命にあると思われます。よほどの事情が無い限りは、この手法は使わない方が良いかと思います。
またIEオブジェクトは「Set IE = Nothing」を実行してもすぐに消えてくれないのか、Test010のプロシージャを連続して実行すると、オブジェクト生成時にエラーが発生する事があります。しばらく置いてから実行すると問題ないようです。

アプリ実例・関連する項目

特に無し

サンプルファイル

今回、説明の中で紹介したコードは、以下のサンプルファイルの標準モジュール(Module1)に記載しています。実行するにはVBEから直接実行してください。
テキストファイルの読み取り_Webからダウンロード編(its-053.xlsm)
セキュリティ向上を目的として「インターネット経由でダウンロードしたOfficeファイル(Excel等)のマクロは、既定でブロック」されるようにOfficeアプリケーションの既定動作が変更になりました。(2022年4月より切替開始)
解除の方法については「ダウンロードファイルのブロック解除方法」を参照下さい。