Як завантажити двійковий файл через HTTP?


131

Як завантажити та зберегти бінарний файл через HTTP за допомогою Ruby?

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

Я на платформі Windows, і я вважаю за краще не запускати жодної зовнішньої програми.


Моє рішення ґрунтується на snippets.dzone.com/posts/show/2469, яке з'явилося після того, як я набрав завантаження файлу рубіну в адресний рядок FireFox ... так ви зробили якісь дослідження в Інтернеті, перш ніж задавати це питання?
Dawid

@Dejw: Я робив дослідження і знайшов тут відповідь. В основному з тим самим кодом, який ви мені дали. resp.bodyЧастина збиває з пантелику мене , я думав , що це врятувало б тільки «тіло» частина відповіді , але я хочу зберегти весь / двійковий файл. Я також виявив, що rio.rubyforge.org може бути корисним. Більше того, на моє запитання ніхто не може сказати, що на таке запитання ще не відповіли :-)
Радек

3
Частина тіла - це саме цілий файл. Відповідь створюється із заголовків (http) та body (файлу), тож коли ви зберігаєте тіло, ви зберегли файл ;-)
Dawid

1
ще одне питання ... скажімо, що файл розміром у 100 Мб, а процес завантаження переривається посередині. Чи буде щось врятовано? Чи можу я відновити файл?
Радек

На жаль, ні, тому що http.get('...')дзвінок надсилає запит і отримує відповідь (весь файл). Щоб завантажити файл фрагментами і зберегти його одночасно, дивіться мою відредаговану відповідь нижче ;-) Відновлення непросте, можливо, ви рахуєте збережені байтами, а потім пропускаєте їх при повторному завантаженні файлу ( file.write(resp.body)повертає кількість написаних байтів).
Dawid

Відповіді:


143

Найпростішим способом є рішення для платформи:

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

15
Так, я знаю. Тому я сказав, що це так a platform-specific solution.
Dawid

1
Більше рішення для певної платформи: платформи GNU / Linux надають wget. OS X забезпечує curl( curl http://oh.no/its/pbjellytime.flv --output secretlylove.flv). У Windows є еквівалент Powershell (new-object System.Net.WebClient).DownloadFile('http://oh.no/its/pbjellytime.flv','C:\tmp\secretlylove.flv'). Бінарні файли для wget і curl існують і для всієї операційної системи через завантаження. Я все-таки настійно рекомендую використовувати стандартну бібліотеку, за винятком випадків, коли ваш код написання виключно для вашого власного кохання.
fny

1
початок ... переконайтесь, що ... кінець не потрібен, якщо використовується форма відкритого блоку. відкрити 'sample.flv' do | f | .... сегмент f.write
lab419

1
Нетекстовий файл надходить зіпсованим.
Пол

1
Я використовую фрагменти завантаження, використовуючи Net::HTTP. І я отримую частину файлу, але отримую відповідь Net::HTTPOK. Чи є спосіб переконатися, що ми завантажили файл повністю?
Микола Кондратенко

118

Я знаю, що це старе питання, але 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

9
open("http://somedomain.net/flv/sample/sample.flv", 'rb')відкриє URL-адресу у двійковому режимі.
zoli

1
хтось знає, чи open-uri розумний щодо заповнення буфера, як пояснив @Isa?
gdelfino

1
@gildefino Ви отримаєте більше відповідей, якщо відкриєте для цього нове запитання. Навряд чи багато людей прочитають це (і це теж придатна річ зробити у Stack Overflow).
kikito

2
Дивовижно. У мене виникли проблеми з перенаправленням HTTP=> HTTPS, і я дізнався, як вирішити це за допомогою open_uri_redirectionsGem
mathielo

1
FWIW деякі люди вважають, що open-uri небезпечний тим, що він маніпулює всі коди, включаючи код бібліотеки, який використовує openнову здатність, яку викликовий код може не передбачити. Вам не варто довіряти введеному користувачеві вкладу open, але вам потрібно бути вдвічі обережнішим.
метод

42

Ось мій файл 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Метод буде текти відповіді> 1KB до Tempfile. Ми можемо використовувати ці знання для реалізації цього методу безоплатного завантаження у файл. Дивіться OpenURI::Bufferреалізацію тут.

Будьте уважні з наданими користувачем інформацією! open(name, *rest, &block)небезпечно, якщо nameнадходить із введення користувача!


4
Це має бути прийнятою відповіддю, оскільки це лаконічний та простий та не завантажує весь файл у пам'ять ~ + продуктивність (тут вгадайте оцінку).
Nikkolasg

Я погоджуюсь з Ніколаглагом. Я просто намагався його використовувати, і він працює дуже добре. Я трохи його змінив, хоча, наприклад, локальний шлях буде автоматично виведений з вказаної URL-адреси, наприклад, "path = nil", а потім перевірка на nil; якщо він дорівнює нулю, я використовую File.basename () у URL-адресі для виведення локального шляху.
shevy

1
Це було б найкращою відповіддю, але open-uri ЗАБЕЗПЕЧУЄ весь файл у пам'ять stackoverflow.com/questions/17454956/…
Саймон Перепелиця

2
@SimonPerepelitsa hehe. Я переглянув це ще раз, тепер забезпечуючи стислий метод завантаження у файл, який не зчитує всю відповідь у пам'яті. Моя попередня відповідь була б достатньою, оскільки openнасправді відповідь не читається в пам'яті, вона читає її у тимчасовий файл для будь-яких відповідей> 10240 байт. Таким чином, ви були добрими, але ні. Переглянута відповідь очищає це непорозуміння і, сподіваємось, слугує прекрасним прикладом сили Рубі :)
Overbryd

3
Якщо ви отримаєте EACCES: permission deniedпомилку під час зміни імені файлу mvкомандою його, тому що ви повинні спочатку закрити файл. Запропонуйте змінити цю частину наTempfile then io.close;
Девід Дуглас

28

Приклад 3 в net / http документації Ruby показує, як завантажити документ через HTTP, а для виведення файлу замість того, щоб просто завантажувати його в пам'ять, замінник ставить з двійковим записом у файл, наприклад, як показано у відповіді Dejw.

Більш складні випадки наведені далі в тому ж документі.


+1 для вказівки на існуючу документацію та подальші приклади.
semperos

1
Ось посилання конкретно: ruby-doc.org/stdlib-2.1.4/libdoc/net/http/rdoc/Net/…
kgilpin

26

Можна використовувати 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")))

10
Це читає весь файл в пам'ять, перш ніж записати його на диск, так що ... це може бути погано.
kgilpin

@kgilpin обидва рішення?
KrauseFx

1
Так, обидва рішення.
eltiare

Це означає, що якщо ви з цим все гаразд, більш коротка версія (якщо припустити, що URL-адреса та ім’я файлу знаходяться у змінних urlі file, відповідно), використовуючи open-uriяк у першому: File.write(file, open(url).read)... Dead просто, для тривіального випадку завантаження.
Лінди

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

Вибачте, але я не знаю більш елегантного способу змусити Рубі чекати, коли буфер заповниться.

Редагувати:

Це версія, яка автоматично налаштовує себе на те, щоб утримувати буфер трохи нижче та нижче. Це неелегантне рішення, але воно, здається, настільки ж швидко, і використовувати якнайменше часу процесора, оскільки це вимагає згортання.

Він працює в три етапи. Короткий період навчання з навмисно довгим часом сну встановлює розмір повного буфера. Період краплі швидко скорочує час сну з кожною ітерацією, помножуючи його на більший коефіцієнт, доки він не знайде недостатньо заповнений буфер. Потім, у звичайний період, вона регулюється вгору і вниз меншим фактором.

Мій Рубі трохи іржавий, тому я впевнений, що це можна покращити. Перш за все, немає помилок в обробці. Також, можливо, його можна було б відокремити в об'єкт, подалі від самого завантаження, щоб ви просто зателефонували 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

Мені подобається sleepхак!
Радек

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

3

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

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

0

якщо ви шукаєте спосіб, як завантажити тимчасовий файл, виконайте завдання та видаліть його, спробуйте цей дорогоцінний камінь 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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.