如何通过HTTP下载二进制文件?


131

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

网址为http://somedomain.net/flv/sample/sample.flv

我在Windows平台上,所以我不希望运行任何外部程序。


我的解决方案强烈地基于snippets.dzone.com/posts/show/2469,它是在我在FireFox地址栏中键入ruby文件下载后出现的...所以在问这个问题之前您是否在互联网上进行了研究?
戴维

@Dejw:我做了研究,并在这里找到了答案。基本上使用您给我的相同代码。该resp.body部分使我感到困惑,我认为它只会保存响应的“正文”部分,但我想保存整个/二进制文件。我还发现rio.rubyforge.org可能会有所帮助。此外,关于我的问题,没有人可以说还没有回答这样的问题:-)
Radek

3
主体部分完全是整个文件。响应是从标头(http)和正文(文件)创建的,因此当您保存正文时,您保存的是文件;-)
Dawid 2010年

1
还有一个问题……假设文件大小为100MB,下载过程在中间中断。有什么要保存的吗?我可以恢复文件吗?
Radek

不幸的是没有,因为http.get('...')调用发送请求并接收响应(整个文件)。要分块下载文件并同时保存,请参见下面我编辑过的答案;-)恢复并不容易,也许您要对保存的字节数进行计数,然后在重新下载文件时跳过它们(file.write(resp.body)返回写入的字节数)。
戴维

Answers:


143

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

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

可能您正在搜索:

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

15
是的我知道。这就是为什么我说是这样a platform-specific solution
戴维

1
更多特定于平台的解决方案:GNU / Linux平台提供wget。OS X提供curlcurl http://oh.no/its/pbjellytime.flv --output secretlylove.flv)。Windows具有等效的Powershell (new-object System.Net.WebClient).DownloadFile('http://oh.no/its/pbjellytime.flv','C:\tmp\secretlylove.flv')。通过下载,所有操作系统都存在wget和curl的二进制文件。我仍然强烈建议您使用标准库,除非您的编写代码完全是为了自己的爱好。
2013年

1
如果使用开放式块形式,则无需...开始...确保...结束。打开'sample.flv'做| f | .... f.write段
lab419 2014年

1
非文本文件已损坏。
保罗

1
我使用进行分块下载Net::HTTP。而且我收到了文件的一部分,但是得到了答复Net::HTTPOK。有什么方法可以确保我们完全下载文件?
Nickolay Kondratenko

118

我知道这是一个老问题,但是Google将我扔到这里,我认为我找到了一个简单的答案。

Railscasts#179中,Ryan Bates使用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年

1
有谁知道open-uri是否像@Isa解释的那样聪明地填充缓冲区?
gdelfino 2012年

1
@gildefino如果您为此打开一个新问题,您将获得更多答案。很多人不太可能会读到此书(这也是在Stack Overflow中要做的适当事情)。
kikito 2012年

2
太棒了 我在使用HTTP=> HTTPS重定向时遇到了问题,并发现了如何使用open_uri_redirectionsGem
Mathielo 2015年

1
FWIW有些人认为open-uri很危险,因为它会猴子补丁所有代码,包括库代码,这些代码open具有调用代码可能无法预期的新功能。open无论如何,您都不应该信任传递给用户的输入,但是现在您需要加倍小心。
方法

42

这是我使用Ruby http归档的文件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

它的主要优点是简洁明了,因为open它可以完成许多繁重的工作。而且它不会读取内存中的整个响应。

open方法会将大于1kb的响应流式传输到Tempfile。我们可以利用这些知识来实现​​这种精益下载到文件的方法。在此处查看OpenURI::Buffer实现

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


4
这应该是公认的答案,因为它简洁明了,并且不会将整个文件加载到内存中(+)性能(此处为猜测)。
Nikkolasg's

我同意Nikkolasg。我只是尝试使用它,所以效果很好。我对此进行了一些修改,例如,将从给定的URL自动推断出本地路径,例如“ path = nil”,然后检查nil。如果为零,那么我在URL上使用File.basename()来推断本地路径。
shevy

1
这将是最好的答案,但开放的URI DOES加载在内存中的整个文件stackoverflow.com/questions/17454956/...
西蒙Perepelitsa

2
@SimonPerepelitsa呵呵。我再次对其进行了修订,现在提供了一种简洁的“下载到文件”方法,该方法无法读取内存中的整个响应。我之前的回答就足够了,因为open实际上不读取内存中的响应,而是将其读取到临时文件中,以获取任何大于10240字节的响应。所以你是对的,但不是。修改后的答案消除了这种误解,并有望成为Ruby功能的一个很好的例子:)
Overbryd 18'Aug

3
如果EACCES: permission denied在使用mv命令命令更改文件名时遇到错误,因为您必须先关闭文件。建议将该部分更改为Tempfile then io.close;
David Douglas,


26

您可以使用open-uri,这是一个班轮

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

或使用net / http

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

10
这会将整个文件读入内存,然后再将其写入磁盘,因此...可能很糟糕。
kgilpin 2014年

@kgilpin两种解决方案?
KrauseFx 2014年

1
是的,这两种解决方案。
Eltiare 2015年

就是说,如果您还可以的话,可以使用一个较短的版本(假设url和filename分别位于变量url和中file),open-uri如在第一个File.write(file, open(url).read)示例中那样使用:...对于简单的下载案例,很简单。
林德斯2015年

17

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

其中filenameurl是字符串。

当网络成为限制因素时,该sleep命令是一种可以大大减少CPU使用率的黑客。Net :: HTTP不会等待缓冲区(在v1.9.2中为16kB)填满才让步,因此CPU忙于自己移动小块内存。睡眠片刻可以使缓冲区有机会在两次写操作之间充满,并且CPU使用率与curl解决方案相当,在我的应用程序中相差4-5倍。一个更强大的解决方案可能会检查f.pos并调整超时以达到目标,例如缓冲区大小的95%-实际上,这就是我在示例中获得的0.005值的方式。

抱歉,但是我不知道让Ruby等待缓冲区填充的更优雅的方法。

编辑:

这是一个自动调整自身以使缓冲区保持等于或低于容量的版本。这是一个不太好的解决方案,但是它看起来一样快,并且只需占用很少的CPU时间,就像它呼唤卷曲一样。

它分为三个阶段。短暂的学习时间和故意较长的睡眠时间可以确定整个缓冲区的大小。下降周期通过将其乘以一个较大的因子来迅速减少每次迭代的睡眠时间,直到找到未满的缓冲区为止。然后,在正常期间,它会以较小的系数上下调整。

我的Ruby有点生锈,所以我敢肯定它可以改善。首先,没有错误处理。另外,也许可以将其分离为一个对象,而不必进行下载本身,这样您就可以autosleep.sleep(f.pos)在循环中进行调用了吗?更好的是,可以将Net :: 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

我喜欢sleep骇客!
Radek

13

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

如果文件中包含德国Umlauts(ä,ö,ü),我遇到了问题。我可以使用以下方法解决问题:

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

0

如果您正在寻找一种下载临时文件的方法,请执行以下操作并将其删除,请尝试使用此gem 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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.