如何使用Ruby通过HTTP下载和保存二进制文件?
网址为http://somedomain.net/flv/sample/sample.flv
。
我在Windows平台上,我不想运行任何外部程序。
答案 0 :(得分:138)
最简单的方法是特定于平台的解决方案:
#!/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
答案 1 :(得分: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
答案 2 :(得分:40)
这是我使用open(name, *rest, &block)
的Ruby http to file。
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
implementation。
请注意用户提供的输入!
如果name
来自用户输入,则open(name, *rest, &block)
不安全!
答案 3 :(得分:28)
Ruby的net/http documentation中的示例3显示了如何通过HTTP下载文档,并输出文件而不是仅将其加载到内存中,将带有二进制写入的put替换为文件,例如,如Dejw的回答所示。
更复杂的案例在同一份文件中进一步展示。
答案 4 :(得分:24)
你可以使用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")))
答案 5 :(得分: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
}
}
}
}
其中filename
和url
是字符串。
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
答案 6 :(得分: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
答案 7 :(得分:3)
我有问题,如果文件包含德国元音(ä,ö,ü)。我可以通过使用:
解决问题ec = Encoding::Converter.new('iso-8859-1', 'utf-8')
...
f << ec.convert(seg)
...
答案 8 :(得分: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