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

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

図01
Web上からファイルをダウンロードする手法としては、Win32APIの「URLDownloadToFile関数」が良く紹介されていますが、以下のように複数の手法が存在します(これが全てではありません)。
手法 | 備考 | |
---|---|---|
Windowsコマンド | bitsadmin.exe | WindowsのBITS機能を使用 |
curl.exe | HTTPリクエストを行うためのツール | |
API | URLDownloadToFile | URL先からファイルをダウンロード |
InternetReadFile等 | URLハンドルから一定バイト単位でデータ読み取り | |
アプリを中継 | Excel | Web上のテキストファイルをシート上に開き、テキストファイルで保存 |
InternetExplorer | IE上にテキストデータを表示し、テキストファイルで保存 |
ここではテキストファイルを扱いますが、ダウンロードされたファイルの文字コードは、基本的にはWeb上のテキストファイルと同じ文字コードとなります。そのため、テキストの処理も文字コードに合わせた以下のような手法が必要となります。
読み書き手法 | 対応文字コード | |||
---|---|---|---|---|
Shift-JIS | UTF-8 | UTF-16 | ||
② | Openステートメント | 〇 | △ | |
③ | ADODB.Stream | 〇 | 〇 | 〇 |
④ | TextStream | 〇 | 〇 |
なお、以下の説明内で使っているサンプルコードでは、Web上に存在するテキストファイルをダウンロードし、その上でファイルを読み取って表示するという内容になっています。実際にサンプルファイルが動く為には、その「Web上のファイル」が実在する必要があり、ここでは本サイト上に以下の内容のファイルを置いています。
特に意味のあるファイルではありませんが、文字コードが違うとどうなるか等を試すには必要かと思い、内容は漢字・数字・アルファベット・半角カナ・ひらがなを含めたものにしています。
URL:https://atsumitm.iobb.net/its/excel/ [下表のファイル名]
ファイル名 | 文字コード | BOM | 内容 |
---|---|---|---|
Test_SJIS.txt | Shift-JIS | - | "Aアあ123" "Web上のファイル" "文字コード〇〇(BOM有無)" |
Test_UTF8N.txt | UTF-8 | 無し | |
Test_UTF8B.txt | UTF-8 | 付き | |
Test_UTF16LEN.txt | UTF-16LE | 無し | |
Test_UTF16LEB.txt | UTF-16LE | 付き | |
Test_UTF16BEN.txt | UTF-16BE | 無し | |
Test_UTF16BEB.txt | UTF-16BE | 付き | |
Test_HTML.php | UTF-8 | 無し | 1~2行目:GET(g1=, g2=)で送った文字列 3~4行目:POST(p1=, p2=)で送った文字列 PHPファイル内容はXMLHTTP編を参照下さい |
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 | 必須 | 保存先ファイル名(フルパス) |
name には、ジョブ名を指定します。この名前は、何でも良いようです。
なお試してみるとジョブ名を指定しなくても一応正しく動きます。これは「""(長さゼロの文字列)」がジョブ名として指定されたと認識しているのかもしれません。しかし異常時に対応が出来なくなる気がするので、少なくとも「文字として見えるジョブ名」を指定するのが良いと思います。
type には、ダウンロード(/DOWNLOAD を指定)をするのか、アップロード(/UPLOAD を指定)をするのかを指定します。既定はダウンロードです。
/PRIORITY job_priority には、優先度を指定します。既定はNORMALで4段階中の下から2番目です。
/ACLflags flags は、ファイルと同時にどのような情報をコピーするかの指定です。指定しない(既定)と、どの情報もCopyされないのですが、タイムスタンプ(更新日時)はCopyされるようです。
/DYNAMIC を指定すると、サーバー側のHTTP要件が緩和され、署名によるエラーが回避できる場合があります。
![]() 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) bitsadminでのダウンロード ============
- Sub Test001()
- Const FN As String = "https://atsumitm.iobb.net/its/excel/" & "Test_UTF8B.txt" '←ダウンロードするファイル
- Dim Temp As String '←保存先ファイルのフルパス
- Dim DT As Date '←ダウンロード開始日時
-
- Temp = Environ("TEMP") & "¥" & "its-053.txt"
-
- If Not Dir(Temp) = "" Then
- Kill Temp '←既存の保存先ファイルを削除
- End If
-
- Shell "bitsadmin /TRANSFER " & " myDL " & "/DYNAMIC " & FN & " " & Temp
-
- DT = Now()
- Do While Dir(Temp) = "" And DT + TimeValue("0:00:10") > Now()
- DoEvents: DoEvents
- Loop
-
- With CreateObject("ADODB.Stream")
- .type = 2
- .Charset = "UTF-8"
-
- .Open
- On Error Resume Next
- .LoadFromFile Temp
- On Error GoTo 0
-
- MsgBox .readtext
- .Close
- End With
-
- 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)」を指定しても、コマンドプロンプト画面は表示されません。
![]() 図06では、外部プログラム「bitsadmin.exe」をVBAのShell関数で実行させています。このShell関数を使う以外にも、外部プログラムを実行させる方法はあります。 その1つが、CreateObject("WScript.Shell")などを使いWSHshellオブジェクトを生成し、そのメソッドである「execメソッド」または「runメソッド」を使う方法です。 どちらも引数に「実行するコマンド」を指定するのは同じですが、以下のような違いがあります。 ・「exec」は、出力結果を戻り値として取得できるが、コマンドプロンプト画面が表示されてしまう。 ・「run」は、コマンドプロンプト画面は表示されないが、出力結果を取得できない。 しかし「bitsadmin.exe」をexecやrunで実行すると、どちらも下記のような「実行結果」が一瞬表示されてしまいます。 ![]() 図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 が成功したか否かのチェック法として、ダウンロードしたテキストが空文字列か否かを使用しています。但しこの判断には、ダウンロード時間大という原因も含まれてしまう事になります。 一方、よりみちで紹介した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」で取り出し、表示させます。
以下が出力例です。

図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 data | HTTP POSTデータをエンコードする (dataに指定したワード("&"印を含む)を全てEncode) |
-H header --header header | リクエストヘッダの追加 |
-L --location | リダイレクトに従う |
-u user:password --user user:password | サーバーユーザー名とパスワード |
-U user:password --proxy-user user:password | プロキシユーザーとパスワード |
なお、Optionsを1つも指定しない(curl URL)場合はエラーは出ないのですが、どこに何がダウンロードされたのか(=既定は何なのか)が把握できませんでした。またOptions と URLを逆に記述(つまり、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ステートメント」を使うことで変更できます。コード例は以下です。
- '========== ⇩(3) curlでのダウンロード(カレントディレクトリ) ============
- Sub Test002()
- Const Fname = "Test_UTF8N.txt" '←ダウンロードするファイル名
- Const FN As String = "https://atsumitm.iobb.net/its/excel/" & Fname '←ダウンロード元URL
- Dim Temp As String '←ダウンロードされる先
- Dim DT As Date '←ダウンロード待ち時間
- Temp = CurDir & "¥" & Fname '←ダウンロード先のパス+ファイル名
- If Not Dir(Temp) = "" Then
- Kill Temp
- End If
- Shell "curl.exe --remote-name " & FN
- DT = Now()
- Do While Dir(Temp) = "" And DT + TimeValue("0:00:10") > Now()
- DoEvents: DoEvents
- Loop
- Application.Wait (Now() + TimeValue("0:00:01"))
- With CreateObject("ADODB.Stream")
- .type = 2
- .Charset = "UTF-8"
- .Open
- On Error Resume Next
- .LoadFromFile Temp
- On Error GoTo 0
- MsgBox .readtext
- .Close
- End With
- End Sub
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上のファイルを「指定したパス+ファイル名にダウンロード」します。指定ファイルのパスを省略すると、カレントディレクトリのファイルと認識するようですが、ファイル名は省略不可(≒ダウンロードされない)です。コード例は以下です。
- '========== ⇩(4) curlでのダウンロード(指定ディレクトリ+ファイル名) ============
- Sub Test003()
- Const FN As String = "https://atsumitm.iobb.net/its/excel/" & "Test_UTF8N.txt" '←ダウンロード元URL
- Dim Temp As String '←ダウンロードされる先
- Dim DT As Date '←ダウンロード待ち時間
- Temp = Environ("TEMP") & "¥" & "its-053.txt" '←ダウンロード先のパス+ファイル名
- If Not Dir(Temp) = "" Then
- Kill Temp
- End If
- Shell "curl.exe --output " & Temp & " " & FN
- DT = Now()
- Do While Dir(Temp) = "" And DT + TimeValue("0:00:10") > Now()
- DoEvents: DoEvents
- Loop
- Application.Wait (Now() + TimeValue("0:00:01"))
- With CreateObject("ADODB.Stream")
- .type = 2
- .Charset = "UTF-8"
- .Open
- On Error Resume Next
- .LoadFromFile Temp
- On Error GoTo 0
- MsgBox .readtext
- .Close
- End With
- End Sub
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」を付けると、ステータスライン+レスポンスヘッダ情報+ファイル内容を出力します。コード例は以下です。
- '========== ⇩(5) curlでのダウンロード(レスポンスヘッダの出力) ============
- Sub Test004()
- Const FN As String = "https://atsumitm.iobb.net/its/excel/" & "Test_UTF8N.txt" '←ダウンロード元URL
- Dim Temp As String '←ダウンロードされる先
- Dim DT As Date '←ダウンロード待ち時間
- Temp = Environ("TEMP") & "¥" & "its-053.txt" '←ダウンロード先のパス+ファイル名
- If Not Dir(Temp) = "" Then
- Kill Temp
- End If
- Shell "curl.exe --head -o " & Temp & " " & FN
' Shell "curl.exe --include -o " & Temp & " " & FN- DT = Now()
- Do While Dir(Temp) = "" And DT + TimeValue("0:00:10") > Now()
- DoEvents: DoEvents
- Loop
- Application.Wait (Now() + TimeValue("0:00:01"))
- With CreateObject("ADODB.Stream")
- .type = 2
- .Charset = "UTF-8"
- .Open
- On Error Resume Next
- .LoadFromFile Temp
- On Error GoTo 0
- MsgBox .readtext
- .Close
- End With
- End Sub
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法」でデータをサーバーに送信し、その結果を得ています。- '========== ⇩(6) curlでのダウンロード(GET法でデータ送信) ============
- Sub Test005()
- Const FN As String = "https://atsumitm.iobb.net/its/excel/" & "Test_HTML.php" & "?g1=Aアあ&g2=Bイい"
- Dim Temp As String '←ダウンロードされる先
- Dim DT As Date '←ダウンロード待ち時間
- Temp = Environ("TEMP") & "¥" & "its-053.txt" '←ダウンロード先のパス+ファイル名
- If Not Dir(Temp) = "" Then
- Kill Temp
- End If
- Shell "curl.exe --request GET --get -o " & Temp & " " & FN
- DT = Now()
- Do While Dir(Temp) = "" And DT + TimeValue("0:00:10") > Now()
- DoEvents: DoEvents
- Loop
- Application.Wait (Now() + TimeValue("0:00:01"))
- With CreateObject("ADODB.Stream")
- .type = 2
- .Charset = "UTF-8"
- .Open
- On Error Resume Next
- .LoadFromFile Temp
- On Error GoTo 0
- MsgBox .readtext
- .Close
- End With
- End Sub
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法」でデータをサーバーに送信し、その結果を得ています。- '========== ⇩(7) curlでのダウンロード(POST法でデータ送信) ============
- Sub Test006()
- Const FN As String = "https://atsumitm.iobb.net/its/excel/" & "Test_HTML.php"
- Dim Temp As String '←ダウンロードされる先
- Dim DT As Date '←ダウンロード待ち時間
- Temp = Environ("TEMP") & "¥" & "its-053.txt" '←ダウンロード先のパス+ファイル名
- If Not Dir(Temp) = "" Then
- Kill Temp
- End If
- Shell "curl.exe --request POST --data p1=Cウう&p2=Dエえ -o " & Temp & " " & FN
- DT = Now()
- Do While Dir(Temp) = "" And DT + TimeValue("0:00:10") > Now()
- DoEvents: DoEvents
- Loop
- Application.Wait (Now() + TimeValue("0:00:01"))
- With CreateObject("ADODB.Stream")
- .type = 2
- .Charset = "UTF-8"
- .Open
- On Error Resume Next
- .LoadFromFile Temp
- On Error GoTo 0
- MsgBox .readtext
- .Close
- End With
- End Sub
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で送信した時のパケットデータの違いは以下のようになります。

図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法でデータを渡し、その出力表示をした形は以下の様になります。

図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宣言をしておく必要があります。- '========== ⇩(8) URLDownloadToFile関数のDeclare宣言 ============
- #If Win64 Then ' Office 64bitアプリケーション用
- Declare 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
- #Else ' Office 32bitアプリケーション用
- Declare 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
- #End If
使用するアプリ(ここでは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関数の引数・戻り値の内容は以下になります。役割 | 関数名 | 宣言 | |||
---|---|---|---|---|---|
引数 | 引数の内容 | 構造体 | 戻り値 | ファイルを ダウンロード | URLDownloadToFile | Declare 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参照) |
URLDownloadToFile関数に指定する引数は全部で5個です。しかし、第2引数(szURL)に取得するURLを、第3引数(szFileName)に保存するファイル名を指定するだけで、残りは全てゼロを指定しておけば良いようです。
![]() ここでは、大元の関数名として「URLDownloadToFileA」を使っています。 一方似た名前で「URLDownloadToFileW」を大元の関数として使う場合は、URLを指定する引数szURL、保存ファイル名を指定する引数szFileNameは、両方ともLongPtr型で宣言をします。 つまりURLやファイル名の先頭番地を指定する事になるため、「StrPtr(URL)」や「StrPtr(ファイル名)」のように指定する必要があります。 |
URLDownloadToFile関数の戻り値は、以下のようになっているようです。
定数 | 値 | 内容 |
---|---|---|
S_OK | 0 | 成功 |
E_OUTOFMEMORY | 0x8007000E (-2147024882) | バッファ長が無効,メモリ不足 |
INET_E INET_E_INVALID_URL | 0x800C0002 (-2146697214) | URLを解析できず |
INET_E | 0x800C0003 (-2146697213) | セッションが確立されず |
INET_E | 0x800C0004 (-2146697212) | 接続に失敗 |
INET_E | 0x800C0005 (-2146697211) | サーバーまたはプロキシが見つからず (ドメインのミス、ネット未接続 等) |
INET_E | 0x800C0006 (-2146697210) | オブジェクトが見つからず(ファイル名のミス 等) |
INET_E | 0x800C0007 (-2146697209) | 接続されたがデータを取得出来ない |
INET_E | 0x800C0008 (-2146697208) | ダウンロードに失敗し、接続が中断。(拡張子?のミス 等) |
INET_E | 0x800C0009 (-2146697207) | アクセスするには認証が必要 |
INET_E | 0x800C000A (-2146697206) | 許容していないMIMEタイプ |
INET_E | 0x800C000B (-2146697205) | 接続がタイムアウト |
INET_E | 0x800C000C (-2146697204) | リクエストは無効 |
INET_E | 0x800C000D (-2146697203) | プロトコルが不明 |
INET_E | 0x800C000E (-2146697202) | セキュリティ問題が発生 |
INET_E | 0x800C000F (-2146697201) | オブジェクトを読み込めず |
INET_E | 0x800C0010 (-2146697200) | オブジェクトをインスタンス化できず |
INET_E | 0x800C0011 (-2146697199) | デフォルトのセキュリティマネージャを使用する必要有り |
INET_E | 0x800C0011 (-2146697198) | デフォルトのプロトコルハンドラーを使用する |
INET_E | 0x800C0012 (-2146697197) | デフォルト設定を使用する |
INET_E | 0x800C0013 (-2146697196) | 不明 |
INET_E INET_E_REDIRECTING | 0x800C0014 (-2146697195) | リダイレクトされた、またはリダイレクト不可 |
INET_E | 0x800C0015 (-2146697194) | ディレクトリにリダイレクトされた |
INET_E | 0x800C0016 (-2146697193) | リソースをロック出来ず |
INET_E | 0x800C0017 (-2146697192) | 拡張バインディングで再リクエストする |
INET_E | 0x800C0018 (-2146697191) | バインディングが終了 |
INET_E | 0x800C0019 (-2146697190) | SSL証明書が無効 |
INET_E | 0x800C001A (-2146697189) | (IE8の予約) |
INET_E | 0x800C001B (-2146697188) | SIDが一致せず、ブロックされた |
INET_E | 0x800C0100 (-2146696960) | ダウンロードがユーザーによって拒否された |
INET_E | 0x800C0200 (-2146696704) | 中止呼び出しをキャンセルした |
INET_E | 0x800C0300 (-2146696448) | SFPファイルは保護されている為、置換不可 |
INET_E | 0x800C0400 (-2146696192) | ページでActiveXコントロールのインストールが制限されている |
INET_E | 0x800C0500 (-2146695936) | レジストリキーポリシーにより、ActiveXコントロールのインストールは不許可 |
INET_E INET_E_ERROR_LAST | 0x800C0501 (-2146695935) | プライベートブラウジングセッションの為、ダウンロードは許可されず |
なお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関数を使用したコード例が以下になります。- '========== ⇩(9) URLDownloadToFileでのダウンロード ============
- Sub Test007()
- Const FN As String = "https://atsumitm.iobb.net/its/excel/" & "Test_UTF8N.txt"
- Dim Temp As String '←ダウンロードされる先
- Dim Res As Long '←URLDownloadToFileの戻り値
- Temp = Environ("TEMP") & "¥" & "its-053.txt" '←ダウンロード先のパス+ファイル名
- If Not Dir(Temp) = "" Then
- Kill Temp
- End If
' DeleteUrlCacheEntry (FN)'←キャッシュ削除- Res = URLDownloadToFile(0, FN, Temp, 0, 0)
- If Res = 0 Then
- With CreateObject("ADODB.Stream")
- .type = 2
- .Charset = "UTF-8"
- .Open
- .LoadFromFile Temp
- MsgBox .readtext
- .Close
- End With
- End If
- End Sub
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宣言が必要になります。
- '========== ⇩(10) DeleteUrlCacheEntry関数の宣言 ============
- #If Win64 Then ' Office 64bitアプリケーション用
- Declare PtrSafe Function DeleteUrlCacheEntry Lib "wininet" Alias "DeleteUrlCacheEntryA" ( _
- ByVal lpszUrlName As String) As Long
- #Else ' Office 32bitアプリケーション用
- Declare Function DeleteUrlCacheEntry Lib "wininet" Alias "DeleteUrlCacheEntryA" ( _
- ByVal lpszUrlName As String) As Long
- #End If
DeleteUrlCacheEntry関数には、キャッシュを削除したいURLを指定します。削除が成功したか否かは戻り値で判断できます。
役割 | 関数名 | 宣言 | |||
---|---|---|---|---|---|
引数 | 引数の内容 | 構造体 | 戻り値 | キャッシュを削除 | DeleteUrlCacheEntry | Declare PtrSafe Function DeleteUrlCacheEntry Lib "wininet" Alias "DeleteUrlCacheEntryA" ( _
ByVal lpszUrlName As String) As Long |
lpszUrlName | URL | ー | True(値=1)=削除完了 False(値=0)=削除対象無し,削除失敗 |
図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上のファイルをダウンロード(「読み取る」と言った方が正確かもしれません)できます。 プログラムの流れとしては下記のようになります。
図25
事前処理として「InternetOpen」「InternetOpenUrl」関数でハンドルを取得し、その後「InternetReadFile」関数を使ってWeb上のファイルを一定バイト単位で取得します。その一定バイト単位のデータをOpenステートメント等を利用してPC上のファイルに復元させます。
ダウンロードが完了したら、取得したハンドルを「InternetCloseHandle」関数で閉じます。
その4つの関数のDeclare宣言は以下のようになります。
- '========== ⇩(11) wininetの各関数のDeclare宣言 ============
- #If Win64 Then
- Declare 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
- Declare 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
- Declare PtrSafe Function InternetReadFile Lib "wininet" ( _
- ByVal hFile As LongPtr, ByRef lpBuffer As Any, ByVal lNumBytesToRead As Long, _
- ByRef lNumberOfBytesRead As Long) As Long
- Declare PtrSafe Function InternetCloseHandle Lib "wininet" (ByVal HINet As LongPtr) As Integer
- #Else
- Declare 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
- Declare 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
- Declare Function InternetReadFile Lib "wininet" ( _
- ByVal hFile As LongPtr, ByRef lpBuffer As Any, ByVal lNumBytesToRead As Long, _
- ByRef lNumberOfBytesRead As Long) As Long
- Declare Function InternetCloseHandle Lib "wininet" (ByVal HINet As LongPtr) As Integer
- #End If
2-2-1.InternetOpen関数
InternetOpen関数は、WinINet関数のアプリケーションの使用を初期化する機能です。役割 | 関数名 | 宣言 | |||
---|---|---|---|---|---|
引数 | 引数の内容 | 構造体 | 戻り値 | 初期化 | InternetOpen | Declare 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 |
引数は5つあります。
第1引数(sAgent)は、WinINet関数を呼び出すアプリケーション名です。
省略さえしなければ、「""(長さゼロの文字列)」でも構わないようです。
第2引数(lAccessType)は、アクセスの種類を下記の中から指定します。
定数 | 値 | 内容 |
---|---|---|
INTERNET_OPEN_TYPE | 0 | レジストリ設定を使用 |
INTERNET_OPEN_TYPE | 1 | ネットに直接アクセス |
INTERNET_OPEN_TYPE | 3 | プロキシを経由 |
INTERNET_OPEN_TYPE | 4 | java/script/INSの使用を禁止 |
第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_ASYNC | 0x10000000 | データをサーバーから取得する(非同期) |
INTERNET_FLAG_FROM_CACHE INTERNET_FLAG_OFFLINE | 0x01000000 | データをキャッシュから取得。 キャッシュが無い場合はエラー |
キャッシュが無い場合に、第5引数に「0x01000000(VBAでは &H1000000 や WorksheetFunction.Hex2Dec(1000000) 等と記載)」を指定すると、エラーが出るとMicrosoftでは説明しています。しかしVBAで実行してみるとエラーとはなりませんが、結果的にファイルが取得できない(空のデータが返る)状態になるようです。
2-2-2.InternetOpenUrl関数
InternetOpenUrlは、指定したURLのリソース(ファイル)を開きます。役割 | 関数名 | 宣言 | |||
---|---|---|---|---|---|
引数 | 引数の内容 | 構造体 | 戻り値 | リソースを開く | InternetOpenUrl | Declare 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 |
引数は6つです。
第1引数(hInternetSession)には、InternetOpen関数で取得したハンドル(戻り値)を指定します。
第2引数(lpszUrl)は、開くリソースのURLを指定します。
第3引数(lpszHeaders)は、サーバに送るヘッダです。通常は設定不要なのでNull(VBAではvbNullString)とします。
第4引数(dwHeadersLength)は、第3引数で指定したヘッダの文字数です。ヘッダを指定しない場合はゼロになります。
第5引数(dwFlags)は、種別を指定するビットマスク値で、下記のように多くの項目があります。不要の時はゼロを指定するようです。
定数 | 値 | 内容 |
---|---|---|
INTERNET_FLAG | 0x0000 | ファイルがキャッシュできない場合に一時ファイルを作成 |
INTERNET_FLAG | 0x0000 | キャッシュがプロキシに存在する場合でも、Webサーバーに強制的にリクエストする |
INTERNET_FLAG | 0x0000 | Cookieダイアログボックスを無効にする |
INTERNET_FLAG | 0x0000 | 再読み込みすべきかどうかの判断時に有効期限・最終更新時刻がサーバから返されない場合強制的に再読み込み |
INTERNET_FLAG | 0x0000 | FTPリソースをサーバから再読み込み |
INTERNET_FLAG | 0x0000 | 証明書のチェックを無効化 |
INTERNET_FLAG | 0x0000 | 証明書の有効期間チェックを無効化 |
INTERNET_FLAG | 0x0000 | HTTP→HTTPSへのリダイレクトを許可 |
INTERNET_FLAG | 0x0000 | HTTPS→HTTPへのリダイレクトを許可 |
INTERNET_FLAG | 0x0004 | 認証を自動的にしない |
INTERNET_FLAG | 0x0008 | リクエストにCookieヘッダーを自動的に追加しない |
INTERNET_FLAG | 0x0020 | 自動的にリダイレクトしない |
INTERNET_FLAG | 0x0040 | 接続可能なら、サポートするデータを使う |
INTERNET_FLAG | 0x0080 | 安全なトランザクションを使用。SSL/PCT使用に変換され、HTTP要求のみで有効 |
INTERNET_FLAG | 0x0400 | 返されたエンティティをキャッシュに追加しない |
INTERNET_FLAG | 0x0800 | パッシブモードで接続 |
INTERNET_FLAG | 0x2000 | 可能ならばサーバとの接続を再利用する |
INTERNET_FLAG | 0x4000 | FTPディレクトリ情報の取得時に、データをWIN32_FIND_DATA構造体として返す |
INTERNET_FLAG | 0x8000 | 要求したファイルをキャッシュからで無く、強制的にサーバから再読み込み |
第6引数(dwContext)は、コールバック関数に渡されるアプリケーション定義値です。VBAではゼロを指定すれば良さそうです。
2-2-3.InternetReadFile関数
InternetReadFileは、InternetOpenUrl関数によって開かれたハンドルからデータを読み取ります。役割 | 関数名 | 宣言 | |||
---|---|---|---|---|---|
引数 | 引数の内容 | 構造体 | 戻り値 | データを読み取る | InternetReadFile | Declare 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)=削除対象無し,削除失敗 |
引数は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)で、ハンドル値を指定します。
役割 | 関数名 | 宣言 | |||
---|---|---|---|---|---|
引数 | 引数の内容 | 構造体 | 戻り値 | ハンドルを閉じる | InternetCloseHandle | Declare PtrSafe Function InternetCloseHandle Lib "wininet" (ByVal HINet As LongPtr) As Integer |
HINet | ハンドル | ー | True(値=1)=削除完了 False(値=0)=削除対象無し,削除失敗 |
2-2-5.ファイルダウンロードのコード例
InternetReadFile関数等を使用したファイルのダウンロードのコード例は以下になります。- '========== ⇩(12) InternetReadFile関数等でのダウンロード ============
- Sub Test008()
- Const FN As String = "https://atsumitm.iobb.net/its/excel/" & "Test_UTF8N.txt"
- Dim Temp As String 'ダウンロードされる先
- Dim iOpen As LongPtr '接続に使用されるハンドル
- Dim iURL As LongPtr 'URLへのハンドル
- Dim buf() As Byte '読み取ったデータを一時的に保存する動的配列
- Dim dwSize As Long '読み取ったバイトサイズ
- Temp = Environ("TEMP") & "¥" & "its-053.txt" '←ダウンロード先のパス+ファイル名
- If Not Dir(Temp) = "" Then
- Kill Temp
- End If
- Open Temp For Binary As #1
- iOpen = InternetOpen("Excel VBA", 1, vbNullString, vbNullString, 0)
- iURL = InternetOpenUrl(iOpen, FN, vbNullString, 0, &H80000000, 0)
- Do
- ReDim buf(1 To 1024)
- InternetReadFile iURL, buf(1), 1024, dwSize
- If dwSize = 0 Then
- Exit Do
- Else
- ReDim Preserve buf(1 To dwSize)
- Put #1, , buf
- End If
- Loop
- InternetCloseHandle iURL
- InternetCloseHandle iOpen
- Close #1
- With CreateObject("ADODB.Stream")
- .type = 2
- .Charset = "UTF-8"
- .Open
- .LoadFromFile Temp
- MsgBox .readtext
- .Close
- End With
- End Sub
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 | 0x0000 | 0x0000 | 0x8000 | ||
InternetOpen の第5引数 | 0 | △ | △ | △ | 〇 |
0x1000 | △ | × | × | × | |
0x0100 | △ | × | × | △ |
図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バイトとしています。

図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」のみです。
- '========== ⇩(13) Excelシートを経由してダウンロード ============
- Sub Test009()
- Const FN As String = "https://atsumitm.iobb.net/its/excel/" & "Test_SJIS.txt"
- Dim Temp As String
- Temp = Environ("TEMP") & "¥" & "its-053.txt"
- If Not Dir(Temp) = "" Then
- Kill Temp
- End If
- With Workbooks.Open(Filename:=FN)
- Application.DisplayAlerts = False
- .SaveAs Filename:=Temp
- Application.DisplayAlerts = True
- .Close
- End With
- With CreateObject("ADODB.Stream")
- .type = 2
- .Charset = "Shift-JIS"
- .Open
- .LoadFromFile Temp
- MsgBox .readtext
- .Close
- End With
- End Sub
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行目で設定)です。
![]() 443行目のSaveAsメソッドには、今回 Filename値のみを指定しましたが、これ以外にも多くの引数が指定できます。 その中の1つに「FileFormat」があります。この引数は「保存する時のファイル形式」を指定するものですが、今回テキストデータをExcelシートに表示し、そのExcelをSaveAsで保存する場合には、以下のようなFileFormat値が設定可能なようです。
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上のテキストファイル」と、文字列を移動している事になります。

図39
なお、Web上のテキストファイルの文字数よりもLAN上のテキストファイルの方が「1文字増えている」のは、文末に改行マーク(vbCrLf)が入るためです。この改行マークについては、図37では削除処理はしていません。
ファイル保存の完了後は、ダウンロードファイルの処理(448行目以降)を行っています。
3-2.Internet Explorerを使用
Web上のテキストファイルをIE(Internet Explorer)に表示をさせ、その文字データをテキストファイルとして保存する事で「ダウンロードしたように見せかける」方法が以下になります。なお、この方法で対応できるWeb上ファイルの文字コードは「Shift-JIS」「UTF-8(BOM有/無)」の計3種です。
- '========== ⇩(14) Internet Explorerを経由してダウンロード ============
- Sub Test010()
' Const FN As String = "https://atsumitm.iobb.net/its/excel/" & "Test_SJIS.txt"- Const FN As String = "https://atsumitm.iobb.net/its/excel/" & "Test_UTF8N.txt"
' Const FN As String = "https://atsumitm.iobb.net/its/excel/" & "Test_UTF8B.txt"- Dim Temp As String
- Dim IE As Object
- Temp = Environ("TEMP") & "¥" & "its-053.txt"
- If Not Dir(Temp) = "" Then
- Kill Temp
- End If
- Set IE = CreateObject("InternetExplorer.Application")
- With IE
- .Visible = False 'Trueを指定するとIEが開いてしまう
- .navigate FN
- Do While .busy = True Or .readystate < 4
- DoEvents: DoEvents
- Loop
- .Document.Charset = "utf-8"
- .Refresh
- Do While .busy = True Or .readystate < 4
- DoEvents: DoEvents
- Loop
- Open Temp For Output As #1
- Print #1, .document.getElementsByTagName("pre")(0).innerHTML
- Close #1
- .Quit
- End With
- Set IE = Nothing
- With CreateObject("ADODB.Stream")
- .type = 2
- .Charset = "Shift-JIS"
- .Open
- .LoadFromFile Temp
- MsgBox .readtext
- .Close
- End With
- End Sub
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_UNINITIALIZED | 0 | 未完了状態(既定) |
READYSTATE_LOADING | 1 | IEオブジェクトのロード中状態 |
READYSTATE_LOADED | 2 | IEオブジェクトのロード完了状態。ただし操作不可能状態 |
READYSTATE_INTERACTIVE | 3 | IEオブジェクトの操作可能状態 |
READYSTATE_COMPLETE | 4 | IEオブジェクトの全データ読み込み完了状態 |
489行目では「Busy」と「ReadyState」のどちらかの条件が成立している間は待つ事になります。しかし、2つを見比べてみるとReadyStateプロパティのみで条件としては用が足りている気がしますが、一般的には併用して使うようです。もしかしたら歴史的な経緯があるのかもしれません。
ここまでで得られるIE上の表示(Webファイルの文字コード別)は、以下のようになります。

図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行目は必要となります。

図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上のテキストファイル」と、文字列を移動している事になります。

図44
なお、Web上のテキストファイルの文字数よりもLAN上のテキストファイルの方が「1文字増えている」のは、最後に改行マークが入るためです。またIEを経由すると、改行マークは「vbLf」で保存されるようです。
なお図40のようにOpenステートメントでファイル作成をした場合は、保存ファイルはShift-JISとなります(「Openステートメント編」参照)。
この改行マークの増加と種類変更、文字コードの変更については、後処理を行う際には注意が必要そうです。
保存が完了した後、ダウンロードファイルの処理(509行目以降)を行っています。
なお現時点でもIEは標準のブラウザでは無くなっており、将来的にはVBAのIEオブジェクトも消えていく運命にあると思われます。よほどの事情が無い限りは、この手法は使わない方が良いかと思います。
またIEオブジェクトは「Set IE = Nothing」を実行してもすぐに消えてくれないのか、Test010のプロシージャを連続して実行すると、オブジェクト生成時にエラーが発生する事があります。しばらく置いてから実行すると問題ないようです。
アプリ実例・関連する項目
特に無しサンプルファイル
今回、説明の中で紹介したコードは、以下のサンプルファイルの標準モジュール(Module1)に記載しています。実行するにはVBEから直接実行してください。
セキュリティ向上を目的として「インターネット経由でダウンロードしたOfficeファイル(Excel等)のマクロは、既定でブロック」されるようにOfficeアプリケーションの既定動作が変更になりました。(2022年4月より切替開始) 解除の方法については「ダウンロードファイルのブロック解除方法」を参照下さい。 |