HTTP経由でバイナリファイルをダウンロードするにはどうすればよいですか?


131

Rubyを使用してバイナリファイルをHTTP経由でダウンロードして保存するにはどうすればよいですか?

URLはhttp://somedomain.net/flv/sample/sample.flvです。

私はWindowsプラットフォームを使用しており、外部プログラムを実行したくない。


私の解決策は、FireFoxアドレスバーにルビーファイルのダウンロードを入力した後に表示されたsnippets.dzone.com/posts/show/2469に強く基づいています...この質問をする前に、インターネットで調査を行いましたか?
Dawid

@Dejw:私は調査を行い、回答された質問をここで見つけました。基本的に、あなたが私に与えたのと同じコードで。そのresp.body部分は私を混乱させます。私はそれが応答の「ボディ」部分のみを保存するだろうと思いましたが、私は全体/バイナリファイルを保存したいと思います。また、rio.rubyforge.org役立つこともあります。さらに、私の質問では、そのような質問はまだ回答されていないと誰も言うことができません:-)
Radek

3
本文部分はファイル全体です。応答はヘッダー(http)と本文(ファイル)から作成されるため、本文を保存するとファイルを保存しました;-)
Dawid

1
もう1つ質問があります。ファイルのサイズが100MBで、ダウンロードプロセスが途中で中断されたとします。保存されるものはありますか?ファイルを再開できますか?
Radek、2010

残念ながら、http.get('...')呼び出しは要求を送信し、応答(ファイル全体)を受信します。チャンクでファイルをダウンロードして同時に保存するには、以下の編集済み回答を参照してください;-)再開は簡単ではありません。おそらく、保存したバイト数をカウントし、ファイルを再ダウンロードするときにスキップします(file.write(resp.body)書き込まれたバイト数を返します)。
Dawid

回答:


143

最も簡単な方法は、プラットフォーム固有のソリューションです。

 #!/usr/bin/env ruby
`wget http://somedomain.net/flv/sample/sample.flv`

おそらくあなたは探しています:

require 'net/http'
# Must be somedomain.net instead of somedomain.net/, otherwise, it will throw exception.
Net::HTTP.start("somedomain.net") do |http|
    resp = http.get("/flv/sample/sample.flv")
    open("sample.flv", "wb") do |file|
        file.write(resp.body)
    end
end
puts "Done."

編集:変更されました。ありがとうございました。

Edit2:ダウンロード中にファイルの一部を保存するソリューション:

# instead of http.get
f = open('sample.flv')
begin
    http.request_get('/sample.flv') do |resp|
        resp.read_body do |segment|
            f.write(segment)
        end
    end
ensure
    f.close()
end

15
はい、知っています。それが私がそうだと言った理由ですa platform-specific solution
Dawid 2013年

1
プラットフォーム固有のソリューションの追加:GNU / Linuxプラットフォームはを提供しますwget。OS Xはcurlcurl http://oh.no/its/pbjellytime.flv --output secretlylove.flv)を提供します。Windowsには同等のPowershellがあり(new-object System.Net.WebClient).DownloadFile('http://oh.no/its/pbjellytime.flv','C:\tmp\secretlylove.flv')ます。wgetとcurlのバイナリは、ダウンロードによってすべてのオペレーティングシステムに存在します。あなた自身の愛情のためだけにコードを書くのでない限り、私は標準ライブラリを使うことを強くお勧めします。
2013年

1
オープンブロックフォームを使用する場合、開始...確認...終了は不要です。'sample.flv'を開きます| f | .... f.writeセグメント
lab419 2014

1
非テキストファイルが破損して到着します。
ポール、

1
私はを使用したチャンクダウンロードを使用していNet::HTTPます。そして、私はファイルの一部を受け取りますが、応答を受け取りますNet::HTTPOK。ファイルを完全にダウンロードする方法はありますか?
Nickolay Kondratenko 2015

118

私はこれが古い質問であることを知っていますが、Googleが私をここに投げ込みました。

Railscasts#179、ライアンベイツは、Ruby標準クラスを使用OpenURIを次のように頼まれたものの多くを行うには:

警告:テストされていないコード。変更または微調整が必​​要になる場合があります。)

require 'open-uri'

File.open("/my/local/path/sample.flv", "wb") do |saved_file|
  # the following "open" is provided by open-uri
  open("http://somedomain.net/flv/sample/sample.flv", "rb") do |read_file|
    saved_file.write(read_file.read)
  end
end

9
open("http://somedomain.net/flv/sample/sample.flv", 'rb')URLをバイナリモードで開きます。
zoli 2012

1
@Isaが説明しているように、open-uriがバッファーを埋めることについてインテリジェントであるかどうかは誰にもわかりますか
gdelfino 2012年

1
@gildefino新しい質問を開くと、より多くの回答が得られます。多くの人がこれを読むことはまずありません(また、スタックオーバーフローで行うのが適切です)。
キキト2012年

2
驚くばかり。HTTP=> HTTPSリダイレクトに問題があり、Gemを使用して解決する方法を見つけましたopen_uri_redirections
mathielo

1
FWIW一部の人々は、open-uriはopen、呼び出し元のコードが予期しない可能性のある新しい機能で使用するライブラリコードを含むすべてのコードをモンキーパッチするため、危険だと考えています。openとにかく渡されるユーザー入力を信頼するべきではありませんが、今は慎重に注意する必要があります。
メソッド

42

これは、Rubyのhttpを使用したファイルですopen(name, *rest, &block)

require "open-uri"
require "fileutils"

def download(url, path)
  case io = open(url)
  when StringIO then File.open(path, 'w') { |f| f.write(io) }
  when Tempfile then io.close; FileUtils.mv(io.path, path)
  end
end

ここでの主な利点は簡潔でシンプルです。 openは、手間のかかる作業の多くを行うです。また、メモリ内の応答全体を読み取ることはありません。

このopenメソッドは、1 kbを超えるレスポンスをにストリーミングしますTempfile。この知識を利用して、この無駄のないファイルへのダウンロード方式を実装できます。を参照してくださいOpenURI::Buffer実装をご覧ください。

ユーザー提供の入力に注意してください! ユーザー入力から来ているopen(name, *rest, &block)場合は安全ではありませんname


4
簡潔でシンプルであり、メモリ全体にファイル全体をロードしないため、これは受け入れられる答えである必要があります〜+パフォーマンス(ここで推測)。
Nikkolasg 2016

日航に同意します。私はそれを使おうとしただけで、とてもうまくいきました。少し変更しましたが、たとえば、ローカルパスは指定されたURLから自動的に推定されるため、たとえば "path = nil"にしてからnilをチェックします。nilの場合、urlでFile.basename()を使用してローカルパスを推定します。
シェビー2017

1
これが最良の答えになりますが、オープン-URIはDOESメモリ内ファイル全体をロードstackoverflow.com/questions/17454956/...
サイモンPerepelitsa

2
@SimonPerepelitsaへへ。私はそれをもう一度改訂し、メモリ内の応答全体を読み取らない簡潔なファイルへのダウンロード方法を提供します。open実際にはメモリ内の応答を読み取らないため、10240バイトを超える応答の場合はそれを一時ファイルに読み取ります。だからあなたはちょっと正しかったが、そうではなかった。改訂された回答は、この誤解を
解消

3
あなたが取得した場合EACCES: permission denied、エラーを持つファイル名を変更するときにmvコマンドをその最初のファイルをクローズする必要があるため。その部分を次のように変更することをTempfile then io.close;
David Douglas

28

Rubyのnet / httpドキュメンテーションの例3は、HTTP経由でドキュメントをダウンロードし、単にメモリにロードする代わりにファイルを出力する方法を示しています。たとえば、Dejwの回答に示すように、putをバイナリでファイルに書き込みます。

より複雑なケースは、同じドキュメントのさらに下に示されています。


+1は、既存のドキュメントおよびその他の例を示すために使用します。
semperos 2010

1
具体的なリンクは次のとおり
doc.org

26

ワンライナーであるopen-uriが使えます

require 'open-uri'
content = open('http://example.com').read

またはnet / httpを使用する

require 'net/http'
File.write("file_name", Net::HTTP.get(URI.parse("http://url.com")))

10
これはディスクに書き込む前にファイル全体をメモリに読み込むので、それは悪いことです。
kgilpin 2014年

@kgilpin両方のソリューション?
KrauseFx 2014年

1
はい、両方のソリューションです。
eltiare

そうは言っても、問題がなければ、最初のように短いバージョン(URLとファイル名がそれぞれ変数urlfileにあると想定)を使用open-uriFile.write(file, open(url).read)ます。
lindes

17

Dejwの答えを拡張(edit2):

File.open(filename,'w'){ |f|
  uri = URI.parse(url)
  Net::HTTP.start(uri.host,uri.port){ |http| 
    http.request_get(uri.path){ |res| 
      res.read_body{ |seg|
        f << seg
#hack -- adjust to suit:
        sleep 0.005 
      }
    }
  }
}

どこfilenameとはurl文字列です。

このsleepコマンドは、ネットワークが制限要因である場合に、CPU使用率を劇的に削減できるハックです。Net :: HTTPは、バッファ(v1.9.2では16kB)が一杯になるのを待たずにCPUが解放されるため、CPUは小さなチャンクを移動してビジー状態になります。少しの間スリープすると、バッファは書き込みと書き込みの間を埋める機会が与えられ、CPU使用率はカールソリューションに匹敵します(アプリケーションでは4〜5倍の違いがあります)。より堅牢なソリューションでは、進捗状況を調べf.posてタイムアウトを調整し、たとえばバッファサイズの95%をターゲットに設定できます。実際、この例では0.005の数値を取得しています。

申し訳ありませんが、Rubyにバッファがいっぱいになるまで待機させるよりエレガントな方法はわかりません。

編集:

これは、バッファを容量以下に保つように自動的に調整されるバージョンです。それは洗練されていない解決策ですが、カールするように求められているのと同じくらい高速で、CPU時間をほとんど使用しないようです。

3つの段階で機能します。意図的に長いスリープ時間を伴う短い学習期間は、完全なバッファーのサイズを確立します。ドロップ期間は、十分に満たされていないバッファが見つかるまで、より大きな係数を掛けることによって、反復ごとにスリープ時間をすばやく短縮します。次に、通常の期間中に、より小さな係数で上下に調整します。

私のルビーは少し錆びているので、これは改善できると確信しています。まず、エラー処理はありません。また、ダウンロード自体から離れてオブジェクトに分離されている可能性があるためautosleep.sleep(f.pos)、ループで呼び出すだけですか?さらに良いことに、Net :: HTTPを変更して、バッファがいっぱいになるまで待機してから:-)

def http_to_file(filename,url,opt={})
  opt = {
    :init_pause => 0.1,    #start by waiting this long each time
                           # it's deliberately long so we can see 
                           # what a full buffer looks like
    :learn_period => 0.3,  #keep the initial pause for at least this many seconds
    :drop => 1.5,          #fast reducing factor to find roughly optimized pause time
    :adjust => 1.05        #during the normal period, adjust up or down by this factor
  }.merge(opt)
  pause = opt[:init_pause]
  learn = 1 + (opt[:learn_period]/pause).to_i
  drop_period = true
  delta = 0
  max_delta = 0
  last_pos = 0
  File.open(filename,'w'){ |f|
    uri = URI.parse(url)
    Net::HTTP.start(uri.host,uri.port){ |http|
      http.request_get(uri.path){ |res|
        res.read_body{ |seg|
          f << seg
          delta = f.pos - last_pos
          last_pos += delta
          if delta > max_delta then max_delta = delta end
          if learn <= 0 then
            learn -= 1
          elsif delta == max_delta then
            if drop_period then
              pause /= opt[:drop_factor]
            else
              pause /= opt[:adjust]
            end
          elsif delta < max_delta then
            drop_period = false
            pause *= opt[:adjust]
          end
          sleep(pause)
        }
      }
    }
  }
end

私はsleepハックが好きです!
Radek、2011

13

httpartyのようにNet::HTTP、APIよりも使いやすいライブラリがあります。

require "httparty"
File.open("/tmp/my_file.flv", "wb") do |f| 
  f.write HTTParty.get("http://somedomain.net/flv/sample/sample.flv").parsed_response
end

3

ファイルにドイツ語のウムラウト(ä、ö、ü)が含まれている場合、問題が発生しました。私は次の方法で問題を解決できます:

ec = Encoding::Converter.new('iso-8859-1', 'utf-8')
...
f << ec.convert(seg)
...

0

一時ファイルをダウンロードする方法を探している場合は、何かを行って削除してくださいこの宝石を試してくださいhttps://github.com/equivalent/pull_tempfile

require 'pull_tempfile'

PullTempfile.transaction(url: 'https://mycompany.org/stupid-csv-report.csv', original_filename: 'dont-care.csv') do |tmp_file|
  CSV.foreach(tmp_file.path) do |row|
    # ....
  end
end
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.