web-dev-qa-db-fra.com

Comment télécharger un fichier binaire sur HTTP?

Comment télécharger et enregistrer un fichier binaire sur HTTP à l'aide de Ruby?

L'URL est http://somedomain.net/flv/sample/sample.flv.

Je suis sur la plate-forme Windows et je préférerais ne pas exécuter de programme externe.

128
Radek

Le moyen le plus simple est la solution spécifique à la plate-forme:

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

Vous recherchez probablement:

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: modifié. Merci.

Edit2: La solution qui enregistre une partie du fichier lors du téléchargement:

# 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
140
Dawid

Je sais que c'est une vieille question, mais Google m'a jeté ici et je pense avoir trouvé une réponse plus simple.

Dans Railscasts # 179 , Ryan Bates a utilisé le Ruby classe standard OpenURI ] pour faire en grande partie ce qui a été demandé comme ceci:

( Warning : code non testé. Vous devrez peut-être le modifier/le modifier.)

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
114
kikito

Voici mon Ruby http dans un fichier utilisant 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

Le principal avantage ici est qu’il est concis et simple, car open s’occupe beaucoup des tâches lourdes. Et il ne lit pas l'intégralité de la réponse en mémoire.

La méthode open transmettra les réponses> 1 Ko à Tempfile. Nous pouvons exploiter cette connaissance pour implémenter cette méthode de téléchargement au fichier simplifiée. Voir la mise en œuvre OpenURI::Buffer ici.

S'il vous plaît soyez prudent avec l'entrée fournie par l'utilisateur! open(name, *rest, &block) est dangereux si name provient d'une entrée utilisateur!

41
Overbryd

L'exemple 3 de la documentation net/http de Ruby montre comment télécharger un document via HTTP et comment exporter le fichier au lieu de le charger en mémoire, remplacez la place par une écriture binaire dans un fichier, par exemple. comme indiqué dans la réponse de Dejw.

Les cas plus complexes sont présentés plus bas dans le même document.

28
Arkku

Vous pouvez utiliser open-uri, qui est un one-liner

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

Ou en utilisant net/http

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

Développer la réponse de 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 et url sont des chaînes.

La commande sleep est un hack qui peut dramatiquement réduire l'utilisation du processeur lorsque le réseau est le facteur limitant. Net :: HTTP n'attend pas que la mémoire tampon (16 Ko dans la version 1.9.2) se remplisse avant de céder, de sorte que le processeur se déplace lui-même. Dormir un instant donne au tampon une chance de se remplir entre les écritures, et l'utilisation du processeur est comparable à une solution curl, une différence de 4-5x dans mon application. Une solution plus robuste pourrait examiner les progrès de f.pos Et ajuster le délai d'expiration pour cibler, par exemple, 95% de la taille de la mémoire tampon. En fait, c'est ainsi que j'ai obtenu le nombre 0,005 dans mon exemple.

Désolé, mais je ne connais pas de façon plus élégante d’avoir Ruby attendre que le tampon se remplisse.

Modifier:

Il s’agit d’une version qui s’ajuste automatiquement pour maintenir la mémoire tampon au maximum ou au-dessous de sa capacité. C'est une solution peu élégante, mais elle semble être tout aussi rapide et utiliser peu de temps de calcul que l'appelant curl.

Cela fonctionne en trois étapes. Une brève période d'apprentissage avec une période de sommeil délibérément longue établit la taille d'un tampon complet. La période de largage réduit rapidement le temps de sommeil à chaque itération, en le multipliant par un facteur plus important, jusqu'à trouver un tampon sous-rempli. Ensuite, pendant la période normale, il s’ajuste par un facteur plus petit.

Mon Ruby est un peu rouillé, donc je suis sûr que cela peut être amélioré. Tout d'abord, il n'y a pas de traitement d'erreur. En outre, peut-être pourrait-il être séparé en un objet, loin du téléchargement lui-même, pour que vous appeliez simplement autosleep.sleep(f.pos) dans votre boucle? Mieux encore, Net :: HTTP pourrait être modifié pour attendre un tampon complet avant de céder :-)

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
17
Isa

Il y a plus de bibliothèques conviviales à l'API que Net::HTTP _, par exemple 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
13
fguillen

J'ai eu des problèmes, si le fichier contenait des trémas allemands (ä, ö, ü). Je pourrais résoudre le problème en utilisant:

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

si vous cherchez un moyen de télécharger un fichier temporaire, de faire des choses et de le supprimer, essayez cette gemme 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
0
equivalent8