Как мне скачать бинарный файл по HTTP? - PullRequest
127 голосов
/ 15 февраля 2010

Как загрузить и сохранить бинарный файл по HTTP, используя Ruby?

URL-адрес http://somedomain.net/flv/sample/sample.flv.

Я на платформе Windows и предпочел бы не запускать какие-либо внешние программы.

Ответы [ 9 ]

139 голосов
/ 15 февраля 2010

Самый простой способ - это решение для конкретной платформы:

 #!/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
114 голосов
/ 16 февраля 2012

Я знаю, что это старый вопрос, но Google бросил меня сюда, и я думаю, что нашел более простой ответ.

В Railscasts # 179 , Райан Бейтс использовал стандартный класс 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
40 голосов
/ 17 ноября 2015

Вот мой 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 будет транслировать ответы> 1 КБ Tempfile. Мы можем использовать эти знания для реализации метода бережливой загрузки в файл. См. OpenURI::Buffer реализацию здесь.

Пожалуйста, будьте осторожны с вводом данных пользователем! open(name, *rest, &block) небезопасно, если name поступает из пользовательского ввода!

28 голосов
/ 15 февраля 2010

Пример 3 в документации net / http Ruby показывает, как загрузить документ по HTTP и вывести файл, а не просто загрузить его в память, заменив записи с двоичной записью в файл, например как показано в ответе Дейу.

Более сложные случаи показаны далее в том же документе.

25 голосов
/ 07 ноября 2013

Вы можете использовать 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")))
17 голосов
/ 06 августа 2011

Расширение ответа Дейва (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 - это хак, который может значительно снизить нагрузку на процессор, когда ограничивающим фактором является сеть. Net :: HTTP не ждет, пока буфер (16 КБ в v1.9.2) заполнится, прежде чем уступить, поэтому процессор занят, перемещая небольшие куски. Спящий момент дает буферу возможность заполняться между записями, а загрузка ЦП сравнима с решением для скручивания, разница в 4-5 раз в моем приложении. Более надежное решение может проверить ход выполнения f.pos и скорректировать время ожидания, например, на 95% от размера буфера - фактически, так я получил число 0,005 в моем примере.

Извините, но я не знаю более элегантного способа заставить Руби ждать, пока буфер заполнится.

Edit:

Это версия, которая автоматически настраивается для поддержания буфера на уровне или ниже. Это не элегантное решение, но оно кажется таким же быстрым и потребляет так мало процессорного времени, как оно требует скручивания.

Работает в три этапа. Краткий период обучения с преднамеренно длительным временем сна устанавливает размер полного буфера. Период отбрасывания сокращает время ожидания с каждой итерацией, умножая его на больший коэффициент, пока не найдет недостаточно заполненный буфер. Затем в течение нормального периода он настраивается вверх и вниз с меньшим коэффициентом.

Мой Руби немного ржавый, так что я уверен, что это можно улучшить. Прежде всего, нет обработки ошибок. Кроме того, может быть, он мог бы быть разделен на объект, отдельно от самой загрузки, чтобы вы просто вызывали 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
13 голосов
/ 28 августа 2013

Существует больше библиотек, дружественных к 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 голосов
/ 17 ноября 2011

У меня были проблемы, если файл содержал немецкие умлауты (ä, ö, ü). Я мог бы решить проблему с помощью:

ec = Encoding::Converter.new('iso-8859-1', 'utf-8')
...
f << ec.convert(seg)
...
0 голосов
/ 22 марта 2016

, если вы ищете способ как загрузить временный файл, что-то сделать и удалить его, попробуйте этот камень 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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...