Mi solución se basa fuertemente en snippets.dzone.com/posts/show/2469 que apareció después de que escribí la descarga del archivo ruby en la barra de direcciones de FireFox ... ¿investigó en Internet antes de hacer esta pregunta?
Dawid
@Dejw: Investigué y encontré una pregunta respondida aquí. Básicamente con el mismo código que me diste. La resp.bodyparte me confunde. Pensé que solo salvaría parte del cuerpo de la respuesta, pero quiero guardar todo el archivo / binario. También descubrí que rio.rubyforge.org podría ser útil. Además, con mi pregunta, nadie puede decir que esa pregunta aún no fue respondida :-)
Radek
3
La parte del cuerpo es exactamente el archivo completo. La respuesta se crea a partir de encabezados (http) y cuerpo (el archivo), por lo que cuando guarda el cuerpo, guarda el archivo ;-)
Dawid
1
una pregunta más ... digamos que el archivo es de 100 MB y el proceso de descarga se interrumpe en el medio. ¿Habrá algo guardado? ¿Puedo hacer reanudar el archivo?
Radek
Lamentablemente no, porque la http.get('...')llamada envía una solicitud y recibe respuesta (todo el archivo). Para descargar un archivo en fragmentos y guardarlo simultáneamente, vea mi respuesta editada a continuación ;-) Reanudar no es fácil, quizás cuente los bytes que guardó y luego los omita cuando file.write(resp.body)vuelva a descargar el archivo ( devuelve el número de bytes escritos).
Dawid
Respuestas:
143
La forma más simple es la solución específica de la 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: modificado. Gracias.
Edit2: la solución que guarda parte de un archivo durante la descarga:
# 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
Sí, lo sé. Por eso dije que es así a platform-specific solution.
Dawid
1
Más soluciones específicas de plataforma: las plataformas GNU / Linux proporcionan wget. OS X proporciona curl( curl http://oh.no/its/pbjellytime.flv --output secretlylove.flv). Windows tiene un equivalente de Powershell (new-object System.Net.WebClient).DownloadFile('http://oh.no/its/pbjellytime.flv','C:\tmp\secretlylove.flv'). También existen binarios para wget y curl para todos los sistemas operativos mediante descarga. Todavía recomiendo usar la biblioteca estándar a menos que escriba su código únicamente para su propio amor.
fny
1
el comienzo ... asegúrese ... el final no es necesario si se utiliza el formulario de bloque abierto. abra 'sample.flv' do | f | .... segmento de escritura
lab419
1
El archivo sin texto llega dañado.
Paul
1
Yo uso la descarga fragmentada usando Net::HTTP. Y recibo la parte del archivo pero obtengo respuesta Net::HTTPOK. ¿Hay alguna manera de garantizar que hayamos descargado el archivo por completo?
Nickolay Kondratenko
118
Sé que esta es una vieja pregunta, pero Google me arrojó aquí y creo que encontré una respuesta más simple.
En Railscasts # 179 , Ryan Bates utilizó la clase estándar Ruby OpenURI para hacer mucho de lo que se le pidió de esta manera:
( Advertencia : código no probado. Es posible que deba cambiarlo / modificarlo).
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á la URL en modo binario.
zoli
1
Alguien sabe si open-uri es inteligente para llenar el búfer como explicó @Isa
gdelfino
1
@gildefino Obtendrá más respuestas si abre una nueva pregunta para eso. Es poco probable que muchas personas lo lean (y también es lo apropiado en Stack Overflow).
FWIW algunas personas piensan que open-uri es peligroso porque parchea todo el código, incluido el código de la biblioteca, que se usa opencon una nueva capacidad que el código de llamada podría no anticipar. No debe confiar en la entrada del usuario que se le pasa de opentodos modos, pero debe ser doblemente cuidadoso ahora.
La principal ventaja aquí es conciso y simple, porque openhace gran parte del trabajo pesado. Y no lee toda la respuesta en la memoria.
El openmétodo transmitirá respuestas> 1kb a a Tempfile. Podemos explotar este conocimiento para implementar este método de descarga magra a archivo. Vea la OpenURI::Bufferimplementación aquí.
¡Tenga cuidado con las aportaciones proporcionadas por el usuario!
open(name, *rest, &block)no es seguro si nameproviene de la entrada del usuario!
Esta debería ser la respuesta aceptada, ya que es conciso y simple y no carga todo el archivo en la memoria ~ + rendimiento (supongo que aquí).
Nikkolasg
Estoy de acuerdo con Nikkolasg. Intenté usarlo y funciona muy bien. Sin embargo, lo modifiqué un poco, por ejemplo, la ruta local se deducirá automáticamente de la URL dada, por ejemplo, "ruta = nula" y luego buscando nula; si es nulo, entonces uso File.basename () en la url para deducir la ruta local.
@SimonPerepelitsa jeje. Lo revisé una vez más y ahora proporciono un método conciso de descarga a archivo que no lee toda la respuesta en la memoria. Mi respuesta anterior habría sido suficiente, porque en openrealidad no lee la respuesta en la memoria, la lee en un archivo temporal para cualquier respuesta> 10240 bytes. Así que eras amable, pero no. La respuesta revisada limpia este malentendido y, con suerte, sirve como un gran ejemplo sobre el poder de Ruby :)
Overbryd
3
Si obtiene un EACCES: permission deniederror al cambiar el nombre del archivo con el mvcomando es porque primero debe cerrar el archivo. Sugerir cambiar esa parte aTempfile then io.close;
David Douglas
28
El ejemplo 3 en la documentación de red / http de Ruby muestra cómo descargar un documento a través de HTTP y generar el archivo en lugar de simplemente cargarlo en la memoria.
Los casos más complejos se muestran más abajo en el mismo documento.
Esto lee todo el archivo en la memoria antes de escribirlo en el disco, así que ... eso puede ser malo.
kgilpin
@kgilpin ambas soluciones?
KrauseFx
1
Sí, ambas soluciones.
eltiare
Dicho esto, si está de acuerdo con eso, una versión más corta (suponiendo que la url y el nombre de archivo estén en variables urly file, respectivamente), usando open-uricomo en el primero: File.write(file, open(url).read)... Muy simple, para el caso de descarga trivial.
lindes
17
Ampliando la respuesta 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}}}}
donde filenamey urlson cuerdas.
El sleepcomando es un truco que puede reducir drásticamente el uso de la CPU cuando la red es el factor limitante. Net :: HTTP no espera a que se llene el búfer (16kB en v1.9.2) antes de ceder, por lo que la CPU se ocupa de mover pequeños trozos. Dormir por un momento le da al búfer la oportunidad de llenarse entre escrituras, y el uso de la CPU es comparable a una solución curl, con una diferencia de 4-5x en mi aplicación. Una solución más sólida podría examinar el progreso f.posy ajustar el tiempo de espera para alcanzar, por ejemplo, el 95% del tamaño del búfer; de hecho, así es como obtuve el número 0.005 en mi ejemplo.
Lo siento, pero no conozco una forma más elegante de hacer que Ruby espere a que se llene el búfer.
Editar:
Esta es una versión que se ajusta automáticamente para mantener el búfer a la capacidad o por debajo. Es una solución poco elegante, pero parece ser tan rápida y usar tan poco tiempo de CPU, como se dice que se encrespa.
Funciona en tres etapas. Un breve período de aprendizaje con un tiempo de sueño deliberadamente largo establece el tamaño de un búfer completo. El período de caída reduce el tiempo de suspensión rápidamente con cada iteración, multiplicándolo por un factor mayor, hasta que encuentra un búfer insuficientemente lleno. Luego, durante el período normal, se ajusta hacia arriba y hacia abajo en un factor menor.
Mi Ruby está un poco oxidado, así que estoy seguro de que esto se puede mejorar. En primer lugar, no hay manejo de errores. Además, ¿tal vez podría separarse en un objeto, lejos de la descarga en sí, para que simplemente llame autosleep.sleep(f.pos)a su bucle? Aún mejor, Net :: HTTP podría cambiarse para esperar un búfer completo antes de producir :-)
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 me confunde. Pensé que solo salvaría parte del cuerpo de la respuesta, pero quiero guardar todo el archivo / binario. También descubrí que rio.rubyforge.org podría ser útil. Además, con mi pregunta, nadie puede decir que esa pregunta aún no fue respondida :-)http.get('...')
llamada envía una solicitud y recibe respuesta (todo el archivo). Para descargar un archivo en fragmentos y guardarlo simultáneamente, vea mi respuesta editada a continuación ;-) Reanudar no es fácil, quizás cuente los bytes que guardó y luego los omita cuandofile.write(resp.body)
vuelva a descargar el archivo ( devuelve el número de bytes escritos).Respuestas:
La forma más simple es la solución específica de la plataforma:
Probablemente estés buscando:
Editar: modificado. Gracias.
Edit2: la solución que guarda parte de un archivo durante la descarga:
fuente
a platform-specific solution
.wget
. OS X proporcionacurl
(curl http://oh.no/its/pbjellytime.flv --output secretlylove.flv
). Windows tiene un equivalente de Powershell(new-object System.Net.WebClient).DownloadFile('http://oh.no/its/pbjellytime.flv','C:\tmp\secretlylove.flv')
. También existen binarios para wget y curl para todos los sistemas operativos mediante descarga. Todavía recomiendo usar la biblioteca estándar a menos que escriba su código únicamente para su propio amor.Net::HTTP
. Y recibo la parte del archivo pero obtengo respuestaNet::HTTPOK
. ¿Hay alguna manera de garantizar que hayamos descargado el archivo por completo?Sé que esta es una vieja pregunta, pero Google me arrojó aquí y creo que encontré una respuesta más simple.
En Railscasts # 179 , Ryan Bates utilizó la clase estándar Ruby OpenURI para hacer mucho de lo que se le pidió de esta manera:
( Advertencia : código no probado. Es posible que deba cambiarlo / modificarlo).
fuente
open("http://somedomain.net/flv/sample/sample.flv", 'rb')
abrirá la URL en modo binario.HTTP
=>HTTPS
, y descubrí cómo resolverla usandoopen_uri_redirections
Gemopen
con una nueva capacidad que el código de llamada podría no anticipar. No debe confiar en la entrada del usuario que se le pasa deopen
todos modos, pero debe ser doblemente cuidadoso ahora.Aquí está mi Ruby http para presentar usando
open(name, *rest, &block)
.La principal ventaja aquí es conciso y simple, porque
open
hace gran parte del trabajo pesado. Y no lee toda la respuesta en la memoria.El
open
método transmitirá respuestas> 1kb a aTempfile
. Podemos explotar este conocimiento para implementar este método de descarga magra a archivo. Vea laOpenURI::Buffer
implementación aquí.¡Tenga cuidado con las aportaciones proporcionadas por el usuario!
open(name, *rest, &block)
no es seguro siname
proviene de la entrada del usuario!fuente
open
realidad no lee la respuesta en la memoria, la lee en un archivo temporal para cualquier respuesta> 10240 bytes. Así que eras amable, pero no. La respuesta revisada limpia este malentendido y, con suerte, sirve como un gran ejemplo sobre el poder de Ruby :)EACCES: permission denied
error al cambiar el nombre del archivo con elmv
comando es porque primero debe cerrar el archivo. Sugerir cambiar esa parte aTempfile then io.close;
El ejemplo 3 en la documentación de red / http de Ruby muestra cómo descargar un documento a través de HTTP y generar el archivo en lugar de simplemente cargarlo en la memoria.
Los casos más complejos se muestran más abajo en el mismo documento.
fuente
Puedes usar open-uri, que es un trazador de líneas
O usando net / http
fuente
url
yfile
, respectivamente), usandoopen-uri
como en el primero:File.write(file, open(url).read)
... Muy simple, para el caso de descarga trivial.Ampliando la respuesta de Dejw (edit2):
donde
filename
yurl
son cuerdas.El
sleep
comando es un truco que puede reducir drásticamente el uso de la CPU cuando la red es el factor limitante. Net :: HTTP no espera a que se llene el búfer (16kB en v1.9.2) antes de ceder, por lo que la CPU se ocupa de mover pequeños trozos. Dormir por un momento le da al búfer la oportunidad de llenarse entre escrituras, y el uso de la CPU es comparable a una solución curl, con una diferencia de 4-5x en mi aplicación. Una solución más sólida podría examinar el progresof.pos
y ajustar el tiempo de espera para alcanzar, por ejemplo, el 95% del tamaño del búfer; de hecho, así es como obtuve el número 0.005 en mi ejemplo.Lo siento, pero no conozco una forma más elegante de hacer que Ruby espere a que se llene el búfer.
Editar:
Esta es una versión que se ajusta automáticamente para mantener el búfer a la capacidad o por debajo. Es una solución poco elegante, pero parece ser tan rápida y usar tan poco tiempo de CPU, como se dice que se encrespa.
Funciona en tres etapas. Un breve período de aprendizaje con un tiempo de sueño deliberadamente largo establece el tamaño de un búfer completo. El período de caída reduce el tiempo de suspensión rápidamente con cada iteración, multiplicándolo por un factor mayor, hasta que encuentra un búfer insuficientemente lleno. Luego, durante el período normal, se ajusta hacia arriba y hacia abajo en un factor menor.
Mi Ruby está un poco oxidado, así que estoy seguro de que esto se puede mejorar. En primer lugar, no hay manejo de errores. Además, ¿tal vez podría separarse en un objeto, lejos de la descarga en sí, para que simplemente llame
autosleep.sleep(f.pos)
a su bucle? Aún mejor, Net :: HTTP podría cambiarse para esperar un búfer completo antes de producir :-)fuente
sleep
hack!Hay más bibliotecas aptas
Net::HTTP
para api que , por ejemplo, httparty :fuente
Tuve problemas si el archivo contenía Umlauts alemanes (ä, ö, ü). Podría resolver el problema usando:
fuente
si está buscando una manera de descargar un archivo temporal, hacer cosas y eliminarlo, pruebe esta gema https://github.com/equivalent/pull_tempfile
fuente