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

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

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

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

140456 次浏览

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

 #!/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."

编辑: 改变。谢谢。

编辑2: 下载时保存部分文件的解决方案:

# 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

Ruby 的 Net/http 文档中的示例3展示了如何通过 HTTP 下载文档,并且为了输出文件而不是仅仅将其加载到内存中,Alternate 使用二进制写入一个文件,例如 Dejw 的答案。

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

扩展 Dejw 的回答(编辑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 使用量的黑客技术。HTTP 不会等到缓冲区(在 v1.9.2中为16kB)填满后才生成,因此 CPU 会忙于移动小块。休眠一会儿,缓冲区就有机会在写之间填充,CPU 使用量与 curl 解决方案相当,在我的应用程序中差异为4-5倍。一个更健壮的解决方案可能会检查 f.pos的进度,并将超时时间调整为目标,比如说,缓冲区大小的95% ——事实上,这就是我在示例中获得0.005数字的方法。

对不起,我不知道还有什么更优雅的方法可以让 Ruby 等待缓冲区被填满。

编辑:

这是一个自动调整自身以保持缓冲区刚好处于或低于容量的版本。这是一个不优雅的解决方案,但它似乎同样快,而且使用的 CPU 时间也很少,因为它要求使用 curl。

有三个阶段。一个短暂的学习时间和一个有意识的长时间睡眠建立了一个完整的缓冲区的大小。下降周期通过将其乘以一个更大的因子,在每次迭代中迅速减少睡眠时间,直到找到一个填充不足的缓冲区。然后,在正常时期,它调整上下一个较小的因素。

我的 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

我有问题,如果文件包含德国 Umlauts (ä,ö,ü)。我可以解决这个问题,使用:

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

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

铁路广播 # 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

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

下面的解决方案将首先把整个内容读入内存,然后再把它写入光盘(为了获得更高效的 i/o 解决方案,请查看其他解决方案)。

您可以使用 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")))

这里是我的 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.read) }
when Tempfile then io.close; FileUtils.mv(io.path, path)
end
end

这里的主要优势是简洁和简单,因为 open做了很多重的举重

open方法将向 Tempfile传送 > 1kb 的响应。我们可以利用这些知识来实现这种精益下载到文件的方法。 请看这里的 OpenURI::Buffer实现

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

使用 OpenURI::open_uri避免从磁盘读取文件:

...
case io = OpenURI::open_uri(url)
...

如果你在寻找一种方法来下载临时文件,做东西并删除它尝试这个 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