2010-02-15 89 views
117

如何使用Ruby下载和保存二进制文件通过HTTP?如何通过HTTP下载二进制文件?

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

我在Windows平台上,我不想运行任何外部程序。

+0

我的解决方案是强烈基于http://snippets.dzone.com/posts/show/2469在FireFox地址栏中输入__ruby文件download__之后出现...在你问这个问题之前,你是否在互联网上做过任何研究? – 2010-02-15 01:17:15

+0

@Dejw:我做了研究,在这里找到了一个回答的问题。基本上用你给我的相同的代码。 'resp.body'部分令我困惑,我认为它只会保存响应的'body'部分,但我想保存整个/二进制文件。我还发现http://rio.rubyforge.org/可能会有所帮助。此外,我的问题没有人可以说这样的问题还没有回答:-) – Radek 2010-02-15 01:23:28

+2

正文部分正好是整个文件。响应是从标题(http)和正文(文件)创建的,所以当您保存正文时您保存了该文件;-) – 2010-02-15 01:54:57

回答

126

最简单的方法是特定于平台的解决方案:

​​

也许你正在寻找:

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

编辑:改变。谢谢。

EDIT2:

# 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 
+0

第一个“简单”解决方法将无法在Windows机器上工作 – srcspider 2013-01-17 15:52:02

+14

是的,我知道。这就是为什么我说它是'特定于平台的解决方案'。 – 2013-01-17 21:28:00

+1

更多平台特定的解决方案:GNU/Linux平台提供'wget'。 OS X提供'curl'('curl http://oh.no/its/pbjellytime.flv --output secretlylove.flv')。 Windows有一个Powershell等效的'(新对象System.Net.WebClient).DownloadFile('http://oh.no/its/pbjellytime.flv','C:\tmp\secretlylove.flv')''。对于所有操作系统,wget和curl都可以通过下载进行二进制文件。我仍然强烈建议使用标准库,除非您的代码完全是为了您自己的喜好。 – fny 2013-01-23 12:51:34

27

例3在Ruby的net/http documentation展示了如何通过HTTP下载文件,并输出文件,而不是只加载到:它保存文件的一部分,同时下载解决方案内存,替代放入二进制写入文件,例如如Dejw的答案所示。

更复杂的情况在相同的文档中进一步显示。

+0

+1用于指向现有文档和其他示例。 – semperos 2010-12-29 18:04:55

+1

具体链接如下:http://ruby-doc.org/stdlib-2.1.4/libdoc/net/http/rdoc/Net/HTTP.html#class-Net::HTTP-label-Streaming+Response+Bodies – kgilpin 2014-10-29 20:11:34

16

扩展在Dejw的答案(EDIT 2):

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

其中filenameurl都是字符串。

sleep命令是一个黑客,可以戏剧性当网络是限制因素时减少CPU使用率。 Net :: HTTP不会等待缓冲区(v1.9.2中的16kB)在生成之前填充,因此CPU忙于移动小块。沉睡片刻让缓冲区有机会在写入之间填充,CPU使用率与curl解决方案相当,在我的应用程序中有4-5倍的差异。一个更强大的解决方案可能会检查f.pos的进度并将超时调整为目标,例如缓冲区大小的95% - 事实上,在我的示例中,这就是我得到的0.005数字。

对不起,但我不知道更优雅的方式让Ruby等待缓冲区填充。

编辑:

这是自动调整,以保持缓冲在低于或等于容量的版本。这是一个不雅的解决方案,但它看起来速度一样快,并且使用尽可能少的CPU时间,因为它正在调用curl。

它分三个阶段工作。有意识的长时间睡眠时间的简短学习时间确定了完整缓冲区的大小。丢弃期通过每次迭代快速减少睡眠时间,将其乘以更大的因子,直到找到欠填充的缓冲区。然后,在正常时期,它会上下调整一个较小的因子。

我的Ruby有点生锈,所以我相信这可以改进。首先,没有错误处理。此外,也许它可以分离成一个对象,远离下载本身,所以你只需要在你的循环中调用autosleep.sleep(f.pos)?更妙的是,网:: HTTP可以改变等待全缓冲产生:-)

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 
+0

我喜欢'睡眠'黑客! – Radek 2011-08-06 04:02:12

3

我有问题,如果该文件包含德国的变音之前(ä,ö,ü)。我可以通过使用解决的问题:

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

我知道这是一个老问题,但谷歌把我在这里,我想我找到了一个简单的答案。

Railscasts #179,瑞恩·贝茨使用Ruby的标准类OpenURI做很多东西,有人问这样的:

警告:未经测试的代码,您可能需要更改/调整它。)

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。 – zoli 2012-09-25 19:21:50

+0

@ zoli:真棒。更新我的答案,谢谢! – kikito 2012-09-26 10:10:30

+1

任何人都知道,如果open-uri在@Isa解释的时候是否聪明地填充缓冲区? – gdelfino 2012-10-26 21:28:37

13

还有比Net::HTTP更多的API友好的库,例如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 
23

您可以使用开放式的URI,这是一个内衬

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

或通过网/ HTTP

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

在将文件写入磁盘之前,将整个文件读入内存,所以......可能很糟糕。 – kgilpin 2014-10-29 20:07:07

+0

@kgilpin两种解决方案? – KrauseFx 2014-10-29 20:34:53

+0

是的,两种解决方案。 – eltiare 2015-05-17 20:12:36

26

这里是我的Ruby HTTP到文件使用IO::copy_stream(src, dst)

require "open-uri" 

def download(url, path) 
    File.open(path, "w") do |f| 
    IO.copy_stream(open(url), f) 
    end 
end 

这里的主要优点是它读取和写入块,因此不会读取内存中的整个响应。

我使用open(name, *rest, &block)作为本演示的目的。 IO::copy_stream(src, dst)的第一个参数可以是任何响应读取的IO对象。

请注意用户提供的输入! open(name, *rest, &block)是不安全的,如果name来自用户输入!

+4

这应该是公认的答案,因为它简洁明了,并且不会加载整个文件在内存中〜+性能(在这里猜测)。 – Nikkolasg 2016-09-12 13:56:41

+0

我同意Nikkolasg。我只是试图使用它,它工作得很好。我修改了一下,例如,本地路径将从给定的URL自动推断出来,所以e。 G。 “path = nil”然后检查是否为零;如果它是零,那么我使用URL上的File.basename()来推断本地路径。 – shevy 2017-07-03 11:41:12

+0

我不知道为什么它可以正确使用''w“'。它会在Windows上工作还是更好地使用''wb“'而不是? – sekrett 2017-12-04 10:59:11

0

,如果你正在寻找一种方式如何下载的临时文件,做的东西,并删除它试试这个宝石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 
相关问题