Jak pobrać plik binarny przez HTTP?

Jak pobrać i zapisać plik binarny przez HTTP używając Ruby?

Adres URL to http://somedomain.net/flv/sample/sample.flv.

Jestem na platformie Windows i wolałbym nie uruchamiać żadnego zewnętrznego programu.

 134
Author: the Tin Man, 2010-02-15

9 answers

Najprostszym sposobem jest rozwiązanie specyficzne dla platformy:

 #!/usr/bin/env ruby
`wget http://somedomain.net/flv/sample/sample.flv`

Prawdopodobnie szukasz:

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."

Edit: Zmieniony. Dziękuję.

Edit2: rozwiązanie, które zapisuje część pliku podczas pobierania:

# 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
 146
Author: Dawid,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-01-15 02:27:56

Wiem, że to stare pytanie, ale Google wrzuciło mnie tutaj i myślę, że znalazłem prostszą odpowiedź.

W Railscast #179 , Ryan Bates użył klasy standardu Ruby OpenURI , aby zrobić wiele z tego, o co poproszono w ten sposób:

(Warning : untested code. Być może będziesz musiał go zmienić / dostosować.)

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
 120
Author: kikito,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-08-09 20:44:17

Oto Mój Ruby http do pliku za pomocą 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

Główną zaletą jest tutaj zwięzły i prosty, ponieważ open robi wiele ciężkich podnoszenia. i nie odczytuje całej odpowiedzi w pamięci.

Metoda open będzie streamować odpowiedzi > 1KB do Tempfile. Możemy wykorzystać tę wiedzę do wdrożenia metody lean download to file. Zobacz też OpenURI::Buffer implementacja tutaj.

Należy uważać na podane przez użytkownika wejście! open(name, *rest, &block) jest niebezpieczne, jeśli name pochodzi z danych wejściowych użytkownika!

 44
Author: Overbryd,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-11-01 20:59:06

Przykład 3 w dokumentacji Ruby NET/http pokazuje jak pobrać Dokument przez HTTP i wypisać plik zamiast tylko ładować go do pamięci, substitute puts z binarnym zapisem do pliku, np. jak pokazano w odpowiedzi Dejw.

Bardziej złożone przypadki są przedstawione dalej w tym samym dokumencie.

 29
Author: Arkku,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-12-15 08:53:53

Poniższe rozwiązania najpierw odczytają całą zawartość do pamięci przed zapisaniem jej na dysk (aby uzyskać bardziej wydajne rozwiązania we/wy, spójrz na inne odpowiedzi).

Możesz użyć open-uri, czyli jednego linera

require 'open-uri'
content = open('http://example.com').read

Lub za pomocą net / http

require 'net/http'
File.write("file_name", Net::HTTP.get(URI.parse("http://url.com")))
 26
Author: KrauseFx,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-10-19 09:53:21

Rozwiń odpowiedź 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 
      }
    }
  }
}

Gdzie filename i url są łańcuchami.

Polecenie sleepjest hack, który może drastycznie zmniejszyć zużycie procesora, gdy sieć jest czynnikiem ograniczającym. Net:: HTTP nie czeka na zapełnienie bufora (16KB w v1.9.2), więc procesor sam się porusza małymi kawałkami. Uśpienie przez chwilę daje buforowi szansę na wypełnienie między zapisami, a użycie procesora jest porównywalne z rozwiązaniem curl, różnica 4-5x w mojej aplikacji. Bardziej solidne rozwiązanie może zbadać postęp f.pos i dostosować timeout do celu, powiedzmy, 95% wielkości bufora -- w rzeczywistości tak dostałem numer 0.005 w moim przykładzie.

Przepraszam, ale nie znam bardziej eleganckiego sposobu, aby Ruby czekał na wypełnienie bufora.

Edit:

Jest to wersja, która automatycznie dostosowuje się, aby utrzymać bufor tylko na pojemności lub poniżej. To nieeleganckie rozwiązanie, ale wydaje się być równie szybkie, a do wykorzystania jako mały czas procesora, ponieważ wzywa do zwinięcia.

Działa w trzech etapach. Krótki okres nauki z celowo długim czasem snu określa wielkość pełnego bufora. Okres upuszczania skraca czas snu szybko z każdą iteracją, mnożąc go przez większy współczynnik, aż znajdzie wypełniony bufor. Następnie, w normalnym okresie, dostosowuje się w górę iw dół o mniejszy czynnik. Mój Rubin jest trochę zardzewiały, więc jestem pewien, że można to poprawić. Przede wszystkim, nie ma obsługi błędów. Poza tym, może to może być rozdzielone na obiekt, z dala od samego pobierania, tak, że po prostu wywołać autosleep.sleep(f.pos) w swojej pętli? Jeszcze lepiej, Net:: HTTP można zmienić, aby czekać na pełny bufor przed uzyskaniem: -)
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
 17
Author: Isa,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-05-10 05:00:35

Istnieje więcej bibliotek przyjaznych dla api niż Net::HTTP, na przykład 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
 13
Author: fguillen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-08-28 09:24:05

Miałem problemy, jeśli plik zawierał Niemieckie Umlauty (ä, ö, ü). Mogę rozwiązać problem używając:

ec = Encoding::Converter.new('iso-8859-1', 'utf-8')
...
f << ec.convert(seg)
...
 3
Author: Rolf,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-11-17 16:02:12

Jeśli szukasz sposobu na pobranie pliku tymczasowego, zrób coś i usuń go wypróbuj ten klejnot 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
 0
Author: equivalent8,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-03-22 11:03:25