Bir ikili dosyayı HTTP üzerinden nasıl indirebilirim?


131

Ruby kullanarak bir ikili dosyayı HTTP üzerinden nasıl indirip kaydedebilirim?

URL http://somedomain.net/flv/sample/sample.flv.

Windows platformundayım ve herhangi bir harici program çalıştırmamayı tercih ederim.


Benim çözümüm, FireFox adres çubuğuna ruby dosyası indirmeyi yazdıktan sonra görünen Snippet.dzone.com/posts/show/2469'a dayanıyor ... peki bu soruyu sormadan önce internette herhangi bir araştırma yaptınız mı?
Dawid

@Dejw: Araştırma yaptım ve burada cevaplanmış bir soru buldum. Temelde bana verdiğin kodla. Bu resp.bodykısım kafamı karıştırıyor, cevabın sadece 'gövde' kısmını kurtaracağını düşündüm ama bütün / ikili dosyayı kaydetmek istiyorum. Ayrıca rio.rubyforge.org'un yardımcı olabileceğini buldum . Dahası, sorumla kimse böyle bir sorunun henüz cevaplanmadığını söyleyemez :-)
Radek

3
Vücut kısmı tam olarak bütün dosyadır. Yanıt, başlıklardan (http) ve gövdeden (dosya) oluşturulur, bu nedenle gövdeyi kaydettiğinizde Dosyayı kaydettiniz ;-)
Dawid

1
bir soru daha ... Diyelim ki dosya 100MB büyük ve indirme işlemi ortada kesiliyor. Herhangi bir şey kurtarılacak mı? Dosyayı devam ettirebilir miyim?
Radek

Maalesef hayır, çünkü http.get('...')çağrı bir istek gönderir ve yanıt alır (dosyanın tamamı). Bir dosyayı parçalar halinde indirmek ve aynı anda kaydetmek için aşağıdaki düzenlenmiş cevabıma bakın ;-) Devam etmek kolay değildir, belki de kaydettiğiniz baytları sayarsınız ve dosyayı yeniden indirdiğinizde bunları atlarsınız ( file.write(resp.body)yazılan bayt sayısını döndürür).
Dawid

Yanıtlar:


143

En basit yol, platforma özel çözümdür:

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

Muhtemelen arıyorsun:

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

Düzenleme: Değiştirildi. Teşekkür ederim.

Edit2: İndirirken dosyanın bir kısmını kaydeden çözüm:

# 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
Evet biliyorum. Bu yüzden öyle dedim a platform-specific solution.
Dawid

1
Daha fazla platforma özgü çözümler: GNU / Linux platformları sağlar wget. OS X curl( curl http://oh.no/its/pbjellytime.flv --output secretlylove.flv) sağlar. Windows'un bir Powershell eşdeğeri vardır (new-object System.Net.WebClient).DownloadFile('http://oh.no/its/pbjellytime.flv','C:\tmp\secretlylove.flv'). Wget ve curl için ikili dosyalar, indirme yoluyla tüm işletim sistemleri için de mevcuttur. Sadece kendi sevginiz için kod yazmadığınız sürece standart kitaplığı kullanmanızı şiddetle tavsiye ederim.
2013

1
açık blok formu kullanılıyorsa, başlangıç ​​... emin olun ... bitiş gerekli değildir. aç 'sample.flv' do | f | .... f. segmenti yaz
lab419

1
Metin olmayan dosya bozulmuş olarak gelir.
Paul

1
Kullanarak yığın halinde indirme kullanıyorum Net::HTTP. Ve dosyanın bir kısmını alıyorum ama yanıt alıyorum Net::HTTPOK. Dosyayı tamamen indirdiğimizden emin olmanın bir yolu var mı?
Nickolay Kondratenko

118

Bunun eski bir soru olduğunu biliyorum ama Google beni buraya fırlattı ve sanırım daha basit bir cevap buldum.

Gelen Railscasts # 179 Ryan Bates Yakut standart sınıf kullanılan openURI çok böyle istendi ne yapmalı:

( Uyarı : test edilmemiş kod. Değiştirmeniz / ince ayar yapmanız gerekebilir.)

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'yi ikili modda açacaktır.
zoli

1
@Isa'nın açıkladığı gibi open-uri'nin arabelleği doldurma konusunda akıllı olup olmadığını bilen var mı?
gdelfino

1
@gildefino Bunun için yeni bir soru açarsanız daha fazla cevap alırsınız. Pek çok kişinin bunu okuması olası değildir (ve aynı zamanda Stack Overflow'da yapılacak uygun bir şeydir).
kikito

2
Muhteşem. HTTP=> HTTPSYönlendirme ile ilgili sorunlar yaşadım ve bunu open_uri_redirectionsGem
öğrendim

1
FWIW bazı insanlar, açık-uri'nin tehlikeli olduğunu düşünür, çünkü kütüphane kodu da dahil olmak üzere open, çağıran kodun tahmin edemeyeceği yeni bir yetenekle kullanan tüm kodları maymun olarak yükler . openZaten iletilen kullanıcı girdisine güvenmemelisiniz , ancak şimdi iki kat daha dikkatli olmalısınız.
yöntem

42

İşte kullanarak dosyalamak için Ruby http'im 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

Buradaki ana avantajı kısa ve basittir, çünkü openağır işlerin çoğunu yapar. Ve hafızadaki tüm yanıtı okumaz.

openYöntemi> a 1kb yanıt akışı olacaktır Tempfile. Dosyaya bu yalın indirme yöntemini uygulamak için bu bilgiden yararlanabiliriz. OpenURI::BufferUygulamaya buradan bakın .

Lütfen kullanıcı tarafından sağlanan girdilere dikkat edin! kullanıcı girdisinden geliyorsa open(name, *rest, &block)güvenli değildir name!


4
Bu, kısa ve basit olduğu için kabul edilen cevap olmalıdır ve tüm dosyayı belleğe ~ + performansa yüklemiyor (burada tahmin edin).
Nikkolasg

Nikkolasg'a katılıyorum. Sadece kullanmayı denedim ve çok iyi çalışıyor. Bunu biraz değiştirdim, örneğin, yerel yol verilen URL'den otomatik olarak çıkarılacak, yani örneğin "yol = nil" ve sonra nil kontrolü yapılacak; sıfırsa, yerel yolu çıkarmak için url'de File.basename () kullanırım.
shevy

1
Bu en iyi cevap olacak ama açık uri GELMEZ bellek içinde tüm dosyayı yüklemek stackoverflow.com/questions/17454956/...
Simon Perepelitsa

2
@SimonPerepelitsa hehe. Bir kez daha revize ettim, şimdi bellekteki tüm yanıtı okumayan kısa bir dosyaya indirme yöntemi sağladım. Önceki cevabım yeterli olurdu, çünkü openaslında hafızadaki cevabı okumaz, 10240 bayttan büyük herhangi bir cevap için onu geçici bir dosyaya okur. Yani haklıydın ama değilsin. Gözden geçirilmiş cevap, bu yanlış anlaşılmayı giderir ve umarız Ruby'nin gücüne harika bir örnek olur :)
Overbryd

3
EACCES: permission deniedDosya mvadını komutla değiştirirken bir hata alırsanız, çünkü önce dosyayı kapatmanız gerekir. Bu bölümü şu şekilde değiştirmeyi önerinTempfile then io.close;
David Douglas

28

Ruby'nin net / http belgelerindeki Örnek 3, bir belgenin HTTP üzerinden nasıl indirileceğini ve sadece belleğe yüklemek yerine dosyanın nasıl çıktılacağını gösterir, örneğin Dejw'in cevabında gösterildiği gibi, bir dosyaya ikili yazma ile koyar.

Aynı belgede daha karmaşık durumlar daha aşağıda gösterilmiştir.


Mevcut belgelere ve diğer örneklere işaret eden +1.
semperos


26

Tek satırlık açık-uri kullanabilirsiniz

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

Veya net / http kullanarak

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

10
Bu, diske yazmadan önce tüm dosyayı belleğe okur, bu yüzden ... bu kötü olabilir.
kgilpin

@kgilpin iki çözüm de?
KrauseFx

1
Evet, her iki çözüm de.
eltiare

Bununla birlikte, eğer konum Tamam dedi ki, daha kısa bir versiyonu (varsayarak url ve dosya adı değişkenler içindedir urlve filekullanma sırasıyla) open-uri: İlk olarak File.write(file, open(url).read)... Ölü basit, önemsiz indir durumu için.
lindes

17

Dejw'in cevabını genişletmek (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 
      }
    }
  }
}

dizeler nerede filenameve nerede url.

sleepKomut edebilir kesmek dramatik ağ sınırlayıcı faktör olduğu zaman CPU kullanımını azaltmak. Net :: HTTP, verimi vermeden önce arabelleğin (v1.9.2'de 16kB) dolmasını beklemez, bu nedenle CPU küçük parçaları hareket ettirerek meşgul olur. Bir anlığına uyumak, arabelleğe yazma işlemleri arasında dolma şansı verir ve CPU kullanımı, uygulamamdaki 4-5x fark olan curl çözümüyle karşılaştırılabilir. Daha sağlam bir çözüm ilerlemeyi inceleyebilir f.posve zaman aşımını hedeflemek için zaman aşımını ayarlayabilir, örneğin tampon boyutunun% 95'ini - aslında benim örneğimde 0.005 sayısını elde ettim.

Üzgünüm, ama Ruby'nin arabelleğin dolmasını beklemesinin daha zarif bir yolunu bilmiyorum.

Düzenle:

Bu, arabelleği tam kapasitede veya altında tutmak için kendini otomatik olarak ayarlayan bir versiyondur. Bu uygun olmayan bir çözüm, ancak aynı derecede hızlı ve kıvrılma çağrısı yaptığı kadar az CPU zamanı kullanıyor gibi görünüyor.

Üç aşamalı olarak çalışır. Kasıtlı olarak uzun bir uyku süresine sahip kısa bir öğrenme dönemi, tam bir arabellek boyutunu belirler. Düşme süresi, yetersiz doldurulmuş bir tampon bulana kadar daha büyük bir faktörle çarparak her yinelemede uyku süresini hızlı bir şekilde azaltır. Daha sonra normal dönemde daha küçük bir faktörle yukarı ve aşağı ayarlanır.

Benim Ruby'm biraz paslanmış, bu yüzden bunun iyileştirilebileceğinden eminim. Her şeyden önce, herhangi bir hata işleme yoktur. Ayrıca, belki indirmenin kendisinden uzakta bir nesneye ayrılabilir, böylece sadece döngünüzü çağırırsınız autosleep.sleep(f.pos)? Daha da iyisi, Net :: HTTP, vermeden önce tam bir arabellek bekleyecek şekilde değiştirilebilir :-)

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

sleepHack'i beğendim !
Radek

13

Daha api dostu kütüphaneler vardır Net::HTTPörnek için, 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

3

Dosyada Almanca Umlautlar (ä, ö, ü) içeriyorsa sorun yaşadım. Sorunu kullanarak çözebilirim:

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

0

Geçici dosyayı nasıl indireceğiniz, bir şeyler yapıp sileceğiniz bir yol arıyorsanız bu mücevheri deneyin 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
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.