Bagaimana cara mengunduh file biner melalui HTTP?

131

Bagaimana cara saya mengunduh dan menyimpan file biner melalui HTTP menggunakan Ruby?

URL-nya adalah http://somedomain.net/flv/sample/sample.flv.

Saya menggunakan platform Windows dan saya lebih suka tidak menjalankan program eksternal.

Radek
sumber
Solusi saya sangat didasarkan pada snippets.dzone.com/posts/show/2469 yang muncul setelah saya mengetik unduhan file ruby di bilah alamat FireFox ... jadi, apakah Anda melakukan riset di internet sebelum mengajukan pertanyaan ini?
Dawid
@ Djww: Saya melakukan riset dan menemukan pertanyaan yang dijawab di sini. Pada dasarnya dengan kode yang sama Anda memberi saya. Bagian resp.bodyini membingungkan saya, saya pikir itu akan menyelamatkan hanya bagian 'tubuh' dari respon tetapi saya ingin menyimpan seluruh file / biner. Saya juga menemukan bahwa rio.rubyforge.org dapat membantu. Apalagi dengan pertanyaan saya, tidak ada yang bisa mengatakan bahwa pertanyaan seperti itu belum dijawab :-)
Radek
3
Bagian tubuh persis seluruh file. Respons dibuat dari tajuk (http) dan tubuh (file), jadi ketika Anda menyimpan tubuh Anda menyimpan file ;-)
Dawid
1
satu pertanyaan lagi ... misalkan file berukuran 100MB dan proses pengunduhan terganggu di tengah. Apakah akan ada sesuatu yang diselamatkan? Bisakah saya melakukan resume file?
Radek
Sayangnya tidak, karena http.get('...')panggilan mengirim permintaan dan menerima respons (seluruh file). Untuk mengunduh file dalam potongan dan menyimpannya secara bersamaan, lihat jawaban saya yang diedit di bawah ini ;-) Memulai kembali tidak mudah, mungkin Anda menghitung byte yang Anda simpan dan kemudian lewati ketika Anda mengunduh ulang file ( file.write(resp.body)mengembalikan jumlah byte yang ditulis).
Dawid

Jawaban:

143

Cara paling sederhana adalah solusi khusus platform:

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

Mungkin Anda sedang mencari:

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."

Edit: Diubah. Terima kasih.

Edit2: Solusi yang menyimpan sebagian file saat mengunduh:

# 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
Dawid
sumber
15
Ya saya tahu. Itu sebabnya saya mengatakan itu a platform-specific solution.
Dawid
1
Lebih banyak solusi spesifik platform: platform GNU / Linux menyediakan wget. OS X menyediakan curl( curl http://oh.no/its/pbjellytime.flv --output secretlylove.flv). Windows memiliki padanan Powershell (new-object System.Net.WebClient).DownloadFile('http://oh.no/its/pbjellytime.flv','C:\tmp\secretlylove.flv'). Biner untuk wget dan curl ada untuk semua sistem operasi melalui unduhan juga. Saya masih sangat merekomendasikan menggunakan perpustakaan standar kecuali jika kode tulisan Anda hanya untuk kekasih Anda sendiri.
fny
1
awal ... memastikan ... ujung tidak diperlukan jika formulir blok terbuka digunakan. buka 'sample.flv' do | f | .... f.write segment
lab419
1
File non-teks tiba rusak.
Paul
1
Saya menggunakan unduhan chunked menggunakan Net::HTTP. Dan saya menerima bagian dari file tetapi mendapat respons Net::HTTPOK. Apakah ada cara untuk memastikan kami mengunduh file sepenuhnya?
Nickolay Kondratenko
118

Saya tahu ini adalah pertanyaan lama, tetapi Google melemparkan saya ke sini dan saya pikir saya menemukan jawaban yang lebih sederhana.

Dalam Railscasts # 179 , Ryan Bates menggunakan OpenURI kelas standar Ruby untuk melakukan banyak hal seperti ini:

( Peringatan : kode yang belum diuji. Anda mungkin perlu mengubah / menyesuaikannya.)

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
kikito
sumber
9
open("http://somedomain.net/flv/sample/sample.flv", 'rb')akan membuka URL dalam mode biner.
zoli
1
ada yang tahu kalau open-uri pintar mengisi buffer seperti @Isa jelaskan?
gdelfino
1
@gildefino Anda akan mendapatkan lebih banyak jawaban jika Anda membuka pertanyaan baru untuk itu. Sangat tidak mungkin bahwa banyak orang akan membaca ini (dan itu juga merupakan hal yang tepat untuk dilakukan di Stack Overflow).
kikito
2
Luar biasa. Saya mempunyai masalah dengan HTTP=> HTTPSpengalihan, dan menemukan cara menyelesaikannya menggunakan open_uri_redirectionsPermata
mathielo
1
FWIW beberapa orang berpikir bahwa open-uri berbahaya karena monkeypatches semua kode, termasuk kode library, yang digunakan opendengan kemampuan baru yang mungkin tidak diantisipasi oleh kode panggilan. Anda seharusnya tidak mempercayai input pengguna yang diteruskan open, tetapi Anda harus berhati-hati dua kali lipat sekarang.
metode
42

Ini adalah http Ruby saya untuk menggunakan file 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

Keuntungan utama di sini adalah ringkas dan sederhana, karena opentidak banyak mengangkat beban. Dan itu tidak membaca seluruh respons dalam memori.

The openMetode akan streaming tanggapan> 1kb ke Tempfile. Kami dapat memanfaatkan pengetahuan ini untuk menerapkan metode unduhan lean ke file ini. Lihat OpenURI::Bufferimplementasinya di sini.

Harap berhati-hati dengan input yang diberikan pengguna! open(name, *rest, &block)tidak aman jika nameberasal dari input pengguna!

Overbryd
sumber
4
Ini harus menjadi jawaban yang diterima karena ringkas & sederhana & tidak memuat seluruh file dalam memori ~ kinerja + (perkiraan di sini).
Nikkolasg
Saya setuju dengan Nikkolasg. Saya hanya mencoba menggunakannya dan berfungsi dengan sangat baik. Saya memodifikasinya sedikit, misalnya, jalur lokal akan dideduksi secara otomatis dari URL yang diberikan, jadi mis. "Path = nil" dan kemudian memeriksa nil; jika nil, maka saya menggunakan File.basename () pada url untuk menyimpulkan jalur lokal.
shevy
1
Ini akan menjadi jawaban terbaik, tetapi open-uri TIDAK memuat seluruh file dalam memori stackoverflow.com/questions/17454956/...
Simon Perepelitsa
2
@SimonPerepelitsa hehe. Saya merevisinya lagi, sekarang menyediakan metode unduh-ke-file singkat yang tidak membaca seluruh respons dalam memori. Jawaban saya sebelumnya sudah cukup, karena opensebenarnya tidak membaca respons di memori, itu membacanya menjadi file sementara untuk setiap tanggapan> 10240 byte. Jadi Anda baik-baik saja tetapi tidak. Jawaban yang direvisi membersihkan kesalahpahaman ini dan semoga berfungsi sebagai contoh yang bagus tentang kekuatan Ruby :)
Overbryd
3
Jika Anda mendapatkan EACCES: permission deniedkesalahan saat mengubah nama file dengan mvperintah itu karena Anda harus menutup file terlebih dahulu. Sarankan mengubah bagian itu keTempfile then io.close;
David Douglas
28

Contoh 3 dalam dokumentasi / http net Ruby menunjukkan cara mengunduh dokumen melalui HTTP, dan untuk menghasilkan file alih-alih hanya memuatnya ke dalam memori, gantilah dengan biner tulis ke file, misalnya seperti yang ditunjukkan dalam jawaban Dejw.

Kasus yang lebih kompleks ditunjukkan lebih jauh ke bawah dalam dokumen yang sama.

Arkku
sumber
+1 untuk menunjukkan dokumentasi yang ada dan contoh lebih lanjut.
semperos
1
Inilah tautannya secara khusus: ruby-doc.org/stdlib-2.1.4/libdoc/net/http/rdoc/Net/…
kgilpin
26

Anda dapat menggunakan open-uri, yang merupakan liner satu

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

Atau dengan menggunakan net / http

require 'net/http'
File.write("file_name", Net::HTTP.get(URI.parse("http://url.com")))
KrauseFx
sumber
10
Ini membaca seluruh file ke dalam memori sebelum menulisnya ke disk, jadi ... itu bisa buruk.
kgilpin
@ kgilpin kedua solusi?
KrauseFx
1
Ya, keduanya solusi.
eltiare
Yang mengatakan, jika Anda setuju dengan itu, versi yang lebih pendek (dengan asumsi url dan nama file dalam variabel urldan file, masing-masing), menggunakan open-uriseperti pada yang pertama: File.write(file, open(url).read)... Sangat sederhana, untuk kasus unduhan sepele.
lindes
17

Memperluas jawaban 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 
      }
    }
  }
}

dimana filenamedan urlstring.

The sleepperintah adalah hack yang dapat secara dramatis mengurangi penggunaan CPU ketika jaringan adalah faktor pembatas. Net :: HTTP tidak menunggu buffer (16kB dalam v1.9.2) untuk diisi sebelum menghasilkan, sehingga CPU sibuk memindahkan potongan-potongan kecil. Tidur sebentar memberikan buffer kesempatan untuk mengisi antara menulis, dan penggunaan CPU sebanding dengan solusi keriting, perbedaan 4-5x dalam aplikasi saya. Solusi yang lebih kuat mungkin memeriksa kemajuan f.posdan menyesuaikan batas waktu untuk menargetkan, katakanlah, 95% dari ukuran buffer - sebenarnya itulah cara saya mendapatkan angka 0,005 dalam contoh saya.

Maaf, tapi saya tidak tahu cara yang lebih elegan untuk membuat Ruby menunggu buffer untuk mengisi.

Edit:

Ini adalah versi yang secara otomatis menyesuaikan diri untuk menjaga buffer hanya pada atau di bawah kapasitas. Ini adalah solusi yang tidak bagus, tetapi tampaknya sama cepatnya, dan menggunakan waktu CPU sesedikit mungkin, karena itu memanggil untuk mengeriting.

Ini bekerja dalam tiga tahap. Suatu periode pembelajaran singkat dengan waktu tidur yang sengaja panjang menetapkan ukuran buffer penuh. Periode drop mengurangi waktu tidur dengan cepat dengan setiap iterasi, dengan mengalikannya dengan faktor yang lebih besar, sampai menemukan buffer yang kurang terisi. Kemudian, selama periode normal, itu menyesuaikan atas dan ke bawah oleh faktor yang lebih kecil.

Ruby saya agak berkarat, jadi saya yakin ini bisa diperbaiki. Pertama-tama, tidak ada penanganan kesalahan. Juga, mungkin itu bisa dipisahkan menjadi objek, jauh dari pengunduhan itu sendiri, sehingga Anda hanya perlu menelepon autosleep.sleep(f.pos)di loop Anda? Bahkan lebih baik, Net :: HTTP dapat diubah untuk menunggu buffer penuh sebelum menghasilkan :-)

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
Adalah
sumber
Saya suka sleepretas!
Radek
13

Ada lebih banyak perpustakaan yang ramah api daripada Net::HTTP, misalnya httparty :

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
fguillen
sumber
3

Saya punya masalah, jika file tersebut berisi Umlaut Jerman (ä, ö, ü). Saya bisa memecahkan masalah dengan menggunakan:

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

jika Anda mencari cara bagaimana mengunduh file sementara, lakukan sesuatu dan hapus itu coba permata ini 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
setara8
sumber