Minha solução é fortemente baseada em snippets.dzone.com/posts/show/2469, que apareceu depois que eu digitei o download do arquivo ruby na barra de endereços do FireFox ... você fez alguma pesquisa na Internet antes de fazer esta pergunta?
Dawid
@ Dejw: Eu pesquisei e encontrei uma pergunta respondida aqui. Basicamente, com o mesmo código que você me deu. A resp.bodyparte está me confundindo. Pensei que salvaria apenas parte do 'corpo' da resposta, mas quero salvar o arquivo inteiro / binário. Também achei que o rio.rubyforge.org poderia ser útil. Além disso, com a minha pergunta ninguém pode dizer que tal questão não foi respondida ainda :-)
Radek
3
A parte do corpo é exatamente o arquivo inteiro. Response é criado a partir de cabeçalhos (http) e do corpo (o arquivo), então quando você salva o corpo que você salvou o arquivo ;-)
Dawid
1
mais uma pergunta ... digamos que o arquivo tenha 100 MB e o processo de download seja interrompido no meio. Haverá algo salvo? Posso retomar o arquivo?
Radek
Infelizmente não, porque a http.get('...')chamada envia uma solicitação e recebe resposta (o arquivo inteiro). Para baixar um arquivo em pedaços e salvá-lo simultaneamente, veja minha resposta editada abaixo ;-) Retomar não é fácil, talvez Você conte os bytes que você salvou e, em seguida, pule-os ao baixar novamente o arquivo ( file.write(resp.body)retorna o número de bytes gravados).
Dawid
Respostas:
143
A maneira mais simples é a solução específica da plataforma:
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)endend
puts "Done."
Editar: alterado. Obrigado.
Edit2: A solução que salva parte de um arquivo durante o download:
# instead of http.get
f = open('sample.flv')begin
http.request_get('/sample.flv')do|resp|
resp.read_body do|segment|
f.write(segment)endendensure
f.close()end
Sim eu conheço. Por isso eu disse que é a platform-specific solution.
Dawid
1
Mais soluções específicas da plataforma: as plataformas GNU / Linux fornecem wget. O OS X fornece curl( curl http://oh.no/its/pbjellytime.flv --output secretlylove.flv). O Windows possui um equivalente do PowerShell (new-object System.Net.WebClient).DownloadFile('http://oh.no/its/pbjellytime.flv','C:\tmp\secretlylove.flv'). Existem binários para wget e curl para todos os sistemas operacionais também por download. Eu ainda recomendo usar a biblioteca padrão, a menos que seu código de escrita seja apenas para seu próprio amor.
FNY
1
o início ... garantir ... o fim não é necessário se o formulário de bloco aberto for usado. abra 'sample.flv' do | f | ... segmento f.write
lab419
1
O arquivo não-texto chega corrompido.
Paul
1
Eu uso o download em pedaços usando Net::HTTP. E recebo a parte do arquivo, mas recebo resposta Net::HTTPOK. Existe alguma maneira de garantir o download completo do arquivo?
Nickolay Kondratenko
118
Sei que essa é uma pergunta antiga, mas o Google me jogou aqui e acho que encontrei uma resposta mais simples.
No Railscasts # 179 , Ryan Bates usou a classe padrão Ruby OpenURI para fazer muito do que foi solicitado assim:
( Aviso : código não testado. Pode ser necessário alterá-lo / ajustá-lo.)
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)endend
open("http://somedomain.net/flv/sample/sample.flv", 'rb')abrirá o URL no modo binário.
Zoli
1
alguém sabe se o open-uri é inteligente em preencher o buffer como o @Isa explicou?
Gdelfino 26/10/12
1
@gildefino Você receberá mais respostas se abrir uma nova pergunta para isso. É improvável que muitas pessoas leiam isso (e também é a coisa apropriada a se fazer no Stack Overflow).
FWIW, algumas pessoas pensam que a uri aberta é perigosa porque monitora todo o código, incluindo o código da biblioteca, que usa opencom uma nova habilidade que o código de chamada pode não prever. Você não deve confiar na entrada do usuário transmitida de openqualquer maneira, mas precisa ser duplamente cuidadoso agora.
A principal vantagem aqui é concisa e simples, porque openfaz grande parte do trabalho pesado. E não lê toda a resposta na memória.
O openmétodo transmitirá respostas> 1kb para a Tempfile. Podemos explorar esse conhecimento para implementar esse método lean de download para arquivo. Veja a OpenURI::Bufferimplementação aqui.
Tenha cuidado com a entrada fornecida pelo usuário!
open(name, *rest, &block)é inseguro se namevier da entrada do usuário!
Essa deve ser a resposta aceita, pois é concisa e simples e não carrega o arquivo inteiro na memória ~ + desempenho (estimativa de estimativa aqui).
Nikkolasg
Eu concordo com Nikkolasg. Eu apenas tentei usá-lo e funciona muito bem. Eu o modifiquei um pouco, embora, por exemplo, o caminho local seja deduzido automaticamente da URL fornecida, por exemplo, "path = nil" e, em seguida, verificando se há nada; se for nulo, uso File.basename () no URL para deduzir o caminho local.
@SimonPerepelitsa hehe. Eu a revisei mais uma vez, agora fornecendo um método conciso de download para arquivo que não lê toda a resposta na memória. Minha resposta anterior teria sido suficiente, porque, openna verdade, não lê a resposta na memória, ela a lê em um arquivo temporário para quaisquer respostas> 10240 bytes. Então você estava certo, mas não. A resposta revista limpa este mal-entendido e espero que serve como um grande exemplo do poder do Ruby :)
Overbryd
3
Se você receber um EACCES: permission deniederro ao alterar o nome do arquivo com o mvcomando é porque é necessário fechar o arquivo primeiro. Sugira que mude essa parte paraTempfile then io.close;
David Douglas
28
O exemplo 3 da documentação net / http do Ruby mostra como fazer o download de um documento por HTTP e, como resultado, em vez de apenas carregá-lo na memória, o substituto coloca uma gravação binária em um arquivo, por exemplo, como mostrado na resposta de Dejw.
Casos mais complexos são mostrados mais abaixo no mesmo documento.
Isso lê o arquivo inteiro na memória antes de gravá-lo no disco, então ... isso pode ser ruim.
kgilpin
@kgilpin ambas as soluções?
KrauseFx #
1
Sim, ambas as soluções.
Eltiare 17/05
Dito isto, se você concorda com isso, uma versão mais curta (assumindo que URL e nome do arquivo estão em variáveis urle file, respectivamente), usando open-uricomo no primeiro: File.write(file, open(url).read)... Simples, para o caso trivial de download.
Lindes
17
Expandindo a resposta 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}}}}
onde filenameeurl são strings.
O sleepcomando é um hack que pode reduzir drasticamente o uso da CPU quando a rede é o fator limitante. O Net :: HTTP não espera que o buffer (16kB na v1.9.2) seja preenchido antes de render, então a CPU se ocupa movendo pequenos pedaços. Dormir por um momento dá ao buffer a chance de preencher entre gravações e o uso da CPU é comparável a uma solução de curl, diferença de 4-5x no meu aplicativo. Uma solução mais robusta pode examinar o progresso def.pos e ajustar o tempo limite para atingir, digamos, 95% do tamanho do buffer - na verdade, foi assim que obtive o número 0,005 no meu exemplo.
Desculpe, mas não conheço uma maneira mais elegante de fazer com que Ruby aguarde o buffer preencher.
Editar:
Esta é uma versão que se ajusta automaticamente para manter o buffer igual ou inferior à capacidade. É uma solução deselegante, mas parece ser tão rápida e usar tão pouco tempo de CPU, como está chamando a curl.
Funciona em três etapas. Um breve período de aprendizado com um tempo de sono deliberadamente longo estabelece o tamanho de um buffer completo. O período de queda reduz o tempo de suspensão rapidamente a cada iteração, multiplicando-o por um fator maior, até encontrar um buffer insuficiente. Então, durante o período normal, ele se ajusta para cima e para baixo por um fator menor.
Meu Ruby está um pouco enferrujado, então tenho certeza de que isso pode ser melhorado. Primeiro de tudo, não há tratamento de erros. Além disso, talvez ele possa ser separado em um objeto, longe do próprio download, para que você apenas chame autosleep.sleep(f.pos)no seu loop? Melhor ainda, o Net :: HTTP pode ser alterado para aguardar um buffer completo antes de produzir :-)
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 =0File.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 endif learn <=0then
learn -=1elsif delta == max_delta thenif drop_period then
pause /= opt[:drop_factor]else
pause /= opt[:adjust]endelsif delta < max_delta then
drop_period =false
pause *= opt[:adjust]end
sleep(pause)}}}}end
resp.body
parte está me confundindo. Pensei que salvaria apenas parte do 'corpo' da resposta, mas quero salvar o arquivo inteiro / binário. Também achei que o rio.rubyforge.org poderia ser útil. Além disso, com a minha pergunta ninguém pode dizer que tal questão não foi respondida ainda :-)http.get('...')
chamada envia uma solicitação e recebe resposta (o arquivo inteiro). Para baixar um arquivo em pedaços e salvá-lo simultaneamente, veja minha resposta editada abaixo ;-) Retomar não é fácil, talvez Você conte os bytes que você salvou e, em seguida, pule-os ao baixar novamente o arquivo (file.write(resp.body)
retorna o número de bytes gravados).Respostas:
A maneira mais simples é a solução específica da plataforma:
Provavelmente você está procurando:
Editar: alterado. Obrigado.
Edit2: A solução que salva parte de um arquivo durante o download:
fonte
a platform-specific solution
.wget
. O OS X fornececurl
(curl http://oh.no/its/pbjellytime.flv --output secretlylove.flv
). O Windows possui um equivalente do PowerShell(new-object System.Net.WebClient).DownloadFile('http://oh.no/its/pbjellytime.flv','C:\tmp\secretlylove.flv')
. Existem binários para wget e curl para todos os sistemas operacionais também por download. Eu ainda recomendo usar a biblioteca padrão, a menos que seu código de escrita seja apenas para seu próprio amor.Net::HTTP
. E recebo a parte do arquivo, mas recebo respostaNet::HTTPOK
. Existe alguma maneira de garantir o download completo do arquivo?Sei que essa é uma pergunta antiga, mas o Google me jogou aqui e acho que encontrei uma resposta mais simples.
No Railscasts # 179 , Ryan Bates usou a classe padrão Ruby OpenURI para fazer muito do que foi solicitado assim:
( Aviso : código não testado. Pode ser necessário alterá-lo / ajustá-lo.)
fonte
open("http://somedomain.net/flv/sample/sample.flv", 'rb')
abrirá o URL no modo binário.HTTP
=>HTTPS
redirecionamento, e descobriu como resolvê-lo usandoopen_uri_redirections
Gemopen
com uma nova habilidade que o código de chamada pode não prever. Você não deve confiar na entrada do usuário transmitida deopen
qualquer maneira, mas precisa ser duplamente cuidadoso agora.Aqui está o meu http Ruby para arquivar usando
open(name, *rest, &block)
.A principal vantagem aqui é concisa e simples, porque
open
faz grande parte do trabalho pesado. E não lê toda a resposta na memória.O
open
método transmitirá respostas> 1kb para aTempfile
. Podemos explorar esse conhecimento para implementar esse método lean de download para arquivo. Veja aOpenURI::Buffer
implementação aqui.Tenha cuidado com a entrada fornecida pelo usuário!
open(name, *rest, &block)
é inseguro sename
vier da entrada do usuário!fonte
open
na verdade, não lê a resposta na memória, ela a lê em um arquivo temporário para quaisquer respostas> 10240 bytes. Então você estava certo, mas não. A resposta revista limpa este mal-entendido e espero que serve como um grande exemplo do poder do Ruby :)EACCES: permission denied
erro ao alterar o nome do arquivo com omv
comando é porque é necessário fechar o arquivo primeiro. Sugira que mude essa parte paraTempfile then io.close;
O exemplo 3 da documentação net / http do Ruby mostra como fazer o download de um documento por HTTP e, como resultado, em vez de apenas carregá-lo na memória, o substituto coloca uma gravação binária em um arquivo, por exemplo, como mostrado na resposta de Dejw.
Casos mais complexos são mostrados mais abaixo no mesmo documento.
fonte
Você pode usar o open-uri, que é um liner
Ou usando net / http
fonte
url
efile
, respectivamente), usandoopen-uri
como no primeiro:File.write(file, open(url).read)
... Simples, para o caso trivial de download.Expandindo a resposta de Dejw (edit2):
onde
filename
eurl
são strings.O
sleep
comando é um hack que pode reduzir drasticamente o uso da CPU quando a rede é o fator limitante. O Net :: HTTP não espera que o buffer (16kB na v1.9.2) seja preenchido antes de render, então a CPU se ocupa movendo pequenos pedaços. Dormir por um momento dá ao buffer a chance de preencher entre gravações e o uso da CPU é comparável a uma solução de curl, diferença de 4-5x no meu aplicativo. Uma solução mais robusta pode examinar o progresso def.pos
e ajustar o tempo limite para atingir, digamos, 95% do tamanho do buffer - na verdade, foi assim que obtive o número 0,005 no meu exemplo.Desculpe, mas não conheço uma maneira mais elegante de fazer com que Ruby aguarde o buffer preencher.
Editar:
Esta é uma versão que se ajusta automaticamente para manter o buffer igual ou inferior à capacidade. É uma solução deselegante, mas parece ser tão rápida e usar tão pouco tempo de CPU, como está chamando a curl.
Funciona em três etapas. Um breve período de aprendizado com um tempo de sono deliberadamente longo estabelece o tamanho de um buffer completo. O período de queda reduz o tempo de suspensão rapidamente a cada iteração, multiplicando-o por um fator maior, até encontrar um buffer insuficiente. Então, durante o período normal, ele se ajusta para cima e para baixo por um fator menor.
Meu Ruby está um pouco enferrujado, então tenho certeza de que isso pode ser melhorado. Primeiro de tudo, não há tratamento de erros. Além disso, talvez ele possa ser separado em um objeto, longe do próprio download, para que você apenas chame
autosleep.sleep(f.pos)
no seu loop? Melhor ainda, o Net :: HTTP pode ser alterado para aguardar um buffer completo antes de produzir :-)fonte
sleep
hack!Existem mais bibliotecas compatíveis com API do que
Net::HTTP
, por exemplo, activationparty :fonte
Eu tive problemas, se o arquivo continha trema alemão (ä, ö, ü). Eu poderia resolver o problema usando:
fonte
se você estiver procurando uma maneira de baixar um arquivo temporário, faça coisas e exclua-o, tente esta gema https://github.com/equivalent/pull_tempfile
fonte