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

Rad*_*dek 128 ruby download

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

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

我在Windows平台上,我宁愿不运行任何外部程序.

Daw*_*wid 143

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

 #!/usr/bin/env ruby
`wget http://somedomain.net/flv/sample/sample.flv`
Run Code Online (Sandbox Code Playgroud)

可能您正在寻找:

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."
Run Code Online (Sandbox Code Playgroud)

编辑:已更改.谢谢.

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
Run Code Online (Sandbox Code Playgroud)

  • 是的我知道.这就是我说它是"特定于平台的解决方案"的原因. (14认同)

kik*_*ito 114

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

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
Run Code Online (Sandbox Code Playgroud)

  • `open("http://somedomain.net/flv/sample/sample.flv",'rb')`将以二进制模式打开URL. (9认同)
  • FWIW 一些人认为 open-uri 是危险的,因为它对所有代码,包括库代码,使用具有调用代码可能无法预料的新功能的“open”进行修补。无论如何,您不应该相信传递给 `open` 的用户输入,但现在您需要加倍小心。 (3认同)
  • 任何人都知道 open-uri 是否像@Isa 解释的那样在填充缓冲区方面很聪明? (2认同)
  • 真棒.我遇到了`HTTP` =>`HTTPS`重定向的问题,并使用[`open_uri_redirections` Gem]找到了[如何解决](http://stackoverflow.com/a/27411667/2752041)(https:// github.com/open-uri-redirections/open_uri_redirections) (2认同)

Ove*_*ryd 41

这是我的Ruby http to file使用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
Run Code Online (Sandbox Code Playgroud)

这里的主要优点是简洁和简单,因为open大量繁重.它并没有在内存中读取整个响应.

open方法将响应> 1kb流式传输到a Tempfile.我们可以利用这些知识来实现​​这种精益下载到文件方法.请参阅此处的OpenURI::Buffer实施.

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

  • 这应该是公认的答案,因为它简洁明了,不会将整个文件加载到内存中〜+性能(在这里猜测). (4认同)
  • @SimonPerepelitsa呵呵。我再次进行了修改,现在提供了一种简洁的“下载到文件”方法,该方法“不会读取内存中的整个响应”。我之前的回答就足够了,因为“ open”实际上并不读取内存中的响应,而是将其读取到临时文件中,以获取任何大于10240字节的响应。所以你是对的,但不是。修改后的答案消除了这种误解,并有望成为Ruby功能的一个很好的例子:) (2认同)
  • 如果在使用“ mv”命令更改文件名时收到“ EACCES:权限被拒绝”错误,因为您必须先关闭文件。建议将该部分更改为`Tempfile然后io.close;`。 (2认同)

Ark*_*kku 28

Ruby的net/http文档中的示例3 显示了如何通过HTTP下载文档,并输出文件而不是仅将其加载到内存中,将puts替换为二进制写入文件,例如Dejw的答案中所示.

更复杂的案例在同一文件中进一步展示.


Kra*_*eFx 26

你可以使用open-uri,这是一个单行

require 'open-uri'
content = open('http://example.com').read
Run Code Online (Sandbox Code Playgroud)

或者使用net/http

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

  • 这会在将整个文件写入磁盘之前将其读入内存,因此......这可能很糟糕. (10认同)

小智 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 
      }
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

在哪里filenameurl是字符串.

sleep命令是一种黑客攻击,可以在网络成为限制因素时显着降低CPU使用率.Net :: HTTP不等待缓冲区(v1.9.2中的16kB)在产生之前填充,因此CPU忙于移动小块.暂时休眠使缓冲区有机会填写写入之间,CPU使用率与卷曲解决方案相当,在我的应用程序中有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
Run Code Online (Sandbox Code Playgroud)


fgu*_*len 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
Run Code Online (Sandbox Code Playgroud)