4.3 HTTPサーバからファイルを取得する
HTTPサーバからファイルを取得するときは、HTTPサーバへリクエストを送信した後、API関数/InternetReadFileを使って取得します。ダウンロードするファイルは、html、jpeg、gif、swfなど各種可能です。

なお、この関数を使って取得したデータは、テキストファイルであってもバイナリデータとして考えます。そのため、ファイルの保存にはADO.Streamを使用します。

参考までに、この技はAccess VBA以外に、Excelマクロなど他のVBAでも使用することができます。
API関数/InternetReadFileを定義する
API関数を使うとき、まずはこの関数がどこにある・どんな関数であるかといった、この関数を使うための準備(API関数の宣言)が必要です。また、定義する場所はフォーム、モジュール、クラスモジュールの(General)(Declarations)です。

次の例は、API関数/InternetReadFileを使うときの記述例です。

'(General)(Declarations)へ記述します
Private Declare Function InternetReadFile _
            Lib "wininet.dll" _
         (ByVal hRequest As Long _
        , ByRef lpBuffer As Any _
        , ByVal dwNumberOfBytesToRead As Long _
        , ByRef lpdwNumberOfBytesRead As Long) As Integer
 ※決まり事なので、悩まずにコピペして使ってしまいましょう

なお、この関数の戻り値にはリクエストを受け付けたか、受け付けられなかったかが返ります。Trueが受け付けした、Falseが受け付けられなかったです。ただし、戻り値をCBool関数で変換してからTrue/Falseで判定しましょう。

関数の実行結果を判定する場合は、Err.LastDllErrorを参照するようにしてください。(0なら正常終了です)

 ◆InternetReadFileの引数
ByVal hRequest As Long HttpOpenRequestで取得したHTTPリクエストハンドルを指定します。
ByRef lpBuffer As Any 取得したデータはこの引数で返ってきます。この引数にはバイト型の配列変数の先頭を指定しましょう。
ByVal dwNumberOfBytesToRead As Long lpBufferに指定した変数の大きさ(配列の大きさ)を指定します。例えば、Dim bytDataArea(1023) As Byte という変数を使う場合は、1024(0〜1023で1024個)を指定します。
ByRef lpdwNumberOfBytesRead As Long 読み込んだデータのバイト数が返ります。
ファイルをダウンロードする例
InternetReadFileを使って、HTTPサーバからファイルをダウンロードする例です。Subプロシージャ「prcHTTPReadFileSample」を実行すると、HTTPサーバからファイルをダウンロードします。

HTTPサーバからファイルをダウンロードするときは、次の手順で行います。

■サンプルでのダウンロード手順
 1.サーバと接続

 2.リクエストを送信

 3.リクエストに対する結果(HTTPステータスコード)を取得
  HttpQueryInfoHTTP_QUERY_STATUS_CODEを指定。

 4.InternetReadFileでファイルをダウンロード
  複数回に分けて実行。

 5.ダウンロードしたファイルを保存
  ADO.Streamを使い、バイナリ形式で保存。

なお、ソースコード全体は、こちらをクリックすると別ウィンドウで表示されます。また、各手順のうち、この章で該当する3.以降の手順については、この下を参照してください。
手順3.リクエストに対する結果を取得
最初に、リクエストしたファイルがあるかどうかをチェックする必要があります。ファイルの有無をチェックするには、リクエストを送信した後、API関数/HttpQueryInfoでHTTPステータスコード(HTTP_QUERY_STATUS_CODE)を取得し行います。HTTPステータスコードに200番台が返ってくれば、リクエストしたファイルは有りです。

サンプルソースコードでは、以下の部分がその処理(ファイルの有無チェック)を行っている部分です。取得したHTTPステータスコードが200番台以外であれば、変数lngRc9をセットし、それ以降の処理(ヘッダーの取得やファイルのダウンロード等)を省略するようにしています。




(省略)


       'HTTPステータスコードを取得します
       If lngRc = 0 Then
          lngRc = fcHTTPQueryInfo(HTTP_QUERY_STATUS_CODE)

          'HTTPステータスコードを保存
          lngStatus = CLng(strBuffer) '取得した値を数値型に変換

          'HTTPステータスコードが200番台(ファイルあり)
          '以外なら以下の処理を実行しないように設定
          '(サーバの設定により、なぜか200を返す場合もあります)
          If lngStatus < 200 Or lngStatus > 299 Then
             lngRc = 9
          End If

       End If


(省略)


手順4.InternetReadFileでファイルをダウンロード
ファイルをダウンロードするときは、API関数/InternetReadFileを使います。Do 〜 Loopを使って繰り返しInternetReadFileを実行するようにしていますが、これはダウンロードするファイルが数百バイトずつ送られてくるためです。

ちなみに、右図がそのイメージです。太枠をファイル全体とした場合、ファイルの先頭から少しずつ届きます。なお、ダウンロードするときに送られてくるデータは、たとえそれがhtmlなどのテキストデータであっても、バイナリデータと考えて下さい。


次に、ダウンロードしたファイルの処理です。ダウンロードしたファイル(の一部)は、InternetReadFileの2番目の引数に返ってきます。ここにはtmpBuffer(0)というByte型の変数を使っていますが、これはダウンロードしたファイル(の一部)がバイナリデータであるためです。また、配列の0番目を指定しているのは、この引数がポインタ渡し(VBAだとByRef)の引数であるためです。ポインタ渡しで配列を使うときは、配列の先頭を指定します。

ちなみに、ポインタ渡しというのは、呼び出し側と呼び出される側でのデータのやりとりを、呼び出し側が用意した領域を(呼び出された側が)使って処理を行うときの方法です。呼び出し先のプログラムには、値そのもを渡すのではなく作業領域の場所情報(アドレス)を渡します。

InternetReadFileは、ダウンロードしたファイルの情報を呼び出し元へ渡すときに呼び出し側が用意した作業領域を使いますが、このときに用意した作業領域の大きさ(何バイトの領域か)についても渡す必要があります。それが、3番目の引数です。例では、tmpBuffer(n)が1023個の配列変数であるため1024(0〜1023で1024)を指定しています。こうすることで、InternetReadFileは呼び出し元が用意した作業領域を指定されている大きさだけを使って処理(ダウンロードしたデータの引き渡し)を行います。


それと、「なぜバイト型なんて変数を使うのか」もポイントです。バイト型の変数を使う理由は、ダウンロードしたデータをそのままの形で保存するためです。半角の「A」という文字の情報を保存するときは、1バイトで「&H41(10進数だと65)」と保存しなくてはならないのです。

半角のAを入れるなら文字列型(String型」でも良いのでは?と思うかもしれませんが、String型の変数は情報をUnicode(UTF-16)で持っているため、1文字を保存するのに2バイトの領域を使います。早い話が、String型だと取得したデータを参照する基準が違うので、データが壊れてしまうのです。


データを取得後は、いったん作業領域に移し替え、再度InternetReadFileを呼び出します。何バイトのデータを取得したかは、InternetReadFileの4番目の引数に指定した変数(例ではlngSize)に返ります。この変数の値に0以上が返ってきたら何らかのデータを取得した、0ならデータを取り終えたです。

なお、ReDim Preserve …は、変数に保存した情報を保持したまま、配列の領域を確保するときの記述です。



(省略)


Function fcHTTPReadFile() As Long

    Dim tmpIndex        As Long
    Dim lngSize         As Long
    Dim i               As Long
    Dim tmpBuffer(1023) As Byte

    'ファイルはいくつかに分割して受信するため
    'Do〜Loopで(InternetReadFileを)複数回繰り返します
    Do

       strBuffer = vbNullString
       lngSize = 0

       'ファイルを取得
       Call InternetReadFile(lngReqHnd _
                           , tmpBuffer(0) _
                           , 1024 _
                           , lngSize)

       'InternetReadFileが正常時のみ
       'データの保存処理を実行
       If Err.LastDllError = 0 Then

          '取得データ長が0なら取得終了
          If lngSize = 0 Then
             Exit Do
          End If

          'データ長の加算と取得データ保存領域の拡張
          lngDataLength = lngDataLength + lngSize
          ReDim Preserve bytDataArea(lngDataLength)

          '取得したデータの保存(一時領域から保存領域へ)
          For i = 0 To lngSize - 1
              bytDataArea(lngSavePos) = tmpBuffer(i)
              lngSavePos = lngSavePos + 1
          Next

       End If

    Loop

    '何らかのデータを取得しているときは
    '取得データ保存領域のデータ長を調整
    If lngDataLength > 0 Then
       ReDim Preserve bytDataArea(lngDataLength - 1)
    End If

    'この関数の戻り値には、APIの処理結果コードを返します
    fcHTTPReadFile = Err.LastDllError

End Function


(省略)


5.ダウンロードしたファイルを保存
InternetReadFileで取得したデータは、たとえテキストデータであってもバイナリデータとして扱います。そのため、ファイルへ保存するときにはADODB.Streamを使って行います。

なお、例ではFilesystemObjectも使ってみました。このSubプロシージャの引数には、保存先のフォルダと保存ファイル名が渡されて来ますが、それらを使ってパスを(BuildPathメソッドで)編集するためです。なお、FilesystemObjectBuildPathについては「VBS(WSH)事例集」の「2.2 パスを編集する」をどうぞ!

参考までに、ADODB.Streamを使うときは参照設定でMicrosoft ActiveX Data Objects 2.5 Library(ADO 2.5)以上が選択されていることが前提です。



(省略)


Sub fcDataSave(strPath As String, strFileName)

    'ファイルの保存はバイナリモードで行うため
    'ADO.Streamを使用します。
    'ADO.Streamを使う場合は「ADO 2.5以上」を指定しましょう

    Dim objADOStream As New ADODB.Stream
    Dim objFS        As Object
    
    Set objFS = CreateObject("Scripting.FilesystemObject")

    '保存形式はバイナリモード
    objADOStream.Type = adTypeBinary
    objADOStream.Open
    objADOStream.Write bytDataArea

    'データの保存(adSaveCreateOverWriteは上書きの意味)
    objADOStream.SaveToFile objFS.BuildPath(strPath, strFileName) _
                          , adSaveCreateOverWrite

    objADOStream.Close

    Set objFS = Nothing
    Set objADOStream = Nothing

End Sub


(省略)


Copyright(C) 1999-2006 結城圭介。 All rights reserved