如何在Rails中从URL下载文件并保存?

230

我有一个指向图片的 URL,想要将它保存到本地,这样我就可以使用 Paperclip 来为我的应用程序生成缩略图。下载和保存图片的最佳方式是什么?(我研究了 Ruby 文件处理,但没有找到任何相关内容。)

我该如何在本地下载并保存这张图片呢?我想用 Paperclip 为我的应用生成缩略图。
8个回答

336

试一下这个:

require 'open-uri'
open('image.png', 'wb') do |file|
  file << open('http://example.com/image.png').read
end

1
这个有效。谢谢。我在一张图片上尝试了这个。但是图片没有被正确地写入文件中。就好像它已经损坏了或者出了什么问题。另外,你能指导我如何在Rails应用程序中实现相同的功能吗?我可以在Rails应用程序中使用这个吗? - Alok Swain
96
这会将整个文件读入内存。最佳方法是 IO.copy_stream(open('http://example.com/image.png'), 'destination.png') - Janko
这对我不起作用。我已经创建了一个关于此的问题:http://stackoverflow.com/questions/37071931/how-to-save-image-from-url-to-local-ubuntu-folder-from-rails-console - user2950593
4
2016年的Rails 5版本中,我不需要 require 'open-uri' 这个库,只需使用其下方的代码即可。 - Mirror318
2
这个答案对我没有用。正确的答案在这里:https://dev59.com/b3E85IYBdhLWcg3w_Ixr#29743394 - Blair Anderson
显示剩余3条评论

186

更简短的版本:

require 'open-uri'
download = open('http://example.com/image.png')
IO.copy_stream(download, '~/image.png')
保持相同的文件名:
IO.copy_stream(download, "~/#{download.base_uri.to_s.split('/')[-1]}")

4
输出不以字符串形式存储在内存中,因此更加高效。+1 - Itay Grudev
3
虽然显示两个答案实际上很有用,这样像我这样的人就可以看到两者之间的差异,然后有意识地选择正确的答案。 - Maxwell Evans
9
当我使用open时,Rubocop会发出安全警告。我将其替换为URI.parse(url).open。在此处了解有关该警告的更多信息:https://www.rubydoc.info/gems/rubocop/RuboCop/Cop/Security/Open - Joshua Pinter
2
纯粹的 open 对我不起作用,必须使用 URI.open - xeruf

32
如果您正在使用PaperClip,从URL下载现在将自动处理。
假设您有类似以下内容的东西:
class MyModel < ActiveRecord::Base
  has_attached_file :image, ...
end

在您的模型中,只需将图像指定为URL,类似于以下内容(以故意的长手写):
@my_model = MyModel.new
image_url = params[:image_url]
@my_model.image = URI.parse(image_url)

您可能希望将此代码放在模型的方法中。这也可以在Heroku的临时文件系统上正常工作。

接下来,Paperclip将处理它。

来源:Paperclip文档


5
对于未来的搜索者,Paperclip不再默认允许此功能:https://medium.com/in-the-weeds/all-about-paperclips-cve-2017-0889-server-side-request-forgery-ssrf-vulnerability-8cb2b1c96fe8 - Alex Dunae

19

我认为这是最清晰的方式:

require 'open-uri'

File.write 'image.png', open('http://example.com/image.png').read

我是Rails的新手,这段代码应该放在模型、控制器还是视图中? - adarsh
1
这要看情况,但通常你会在模型或控制器方法中执行类似的操作。在Rails环境中,最好使用框架提供的工具,如ActiveStorage,而不是直接写入磁盘。 - Sage Ross

13

可能是最简单的方法:

require 'open-uri'
image_url = "https://i.imgur.com/ZWnhY9T.png"
IO.copy_stream(URI.open(image_url), 'destination.png')

1
在我的情况下,我尝试了上面所有的代码。只有这个代码可行。区别在于copy_stream()的第一个参数**URI.open(img_url)**,如果我不用URI.open()包装img_url,就会出现以下错误:No such file or directory @ rb_sysopen - lee mcMcartney
1
有没有办法获取下载进度? - undefined
@Pioz 抱歉,我不知道有什么方法,不过这个问题可以提问一下,因为我猜其他人也会对此感兴趣。如果你提问了,请在这里留下链接。 - undefined
1
我能在这个要点上完成它:https://gist.github.com/pioz/504f32c58465d56b12e01a2c4de05ab6#file-one_piece_downloader-rb-L21 - undefined

11

请查看标准库中的Net::HTTP,该文档提供了多个使用HTTP下载文档的示例。


2
这个答案需要更多的关注,因为Kernel#open不仅可以通过在管道符号前缀(例如open("| ls"))来启用文件访问,还可以启用进程调用。因此,通过将变量输入到Kernel#open的参数中可能会导致严重的安全风险。 - Conor

3
使用 Ruby 3 及以上版本,使用已接受的答案会得到以下错误:
``` No such file or directory @ rb_sysopen - http://example.com/image.png (Errno::ENOENT) ```
解决方案是使用 `URI.open` 替代 `Kernel.open`。例如:
require "uri"

download = URI.open('http://example.com/image.png')
File.write('~/image.png', download)

0
以上所有示例都非常好。 在我的情况下,我只想从URL中的图像创建一个下载链接。
如果您想将其下载到您的下载文件夹中,您可以在控制器中使用以下代码:
require 'open-uri'
file_type = url.to_s.split(".")[-1]

send_data open(url).read, filename: "some_name.#{file_type}", type: "image/#{file_type}", disposition: "attachment"

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接