Jak pobrać i zapisać plik binarny przez HTTP przy użyciu Rubiego?
Adres URL to http://somedomain.net/flv/sample/sample.flv
.
Pracuję na platformie Windows i wolałbym nie uruchamiać żadnego zewnętrznego programu.
Jak pobrać i zapisać plik binarny przez HTTP przy użyciu Rubiego?
Adres URL to http://somedomain.net/flv/sample/sample.flv
.
Pracuję na platformie Windows i wolałbym nie uruchamiać żadnego zewnętrznego programu.
resp.body
Część jest mylące mnie Myślałem, że to zapisać tylko „jednostka” część odpowiedzi, ale chcę, aby zapisać cały plik / binarny. Zauważyłem też, że pomocny może być rio.rubyforge.org . Zresztą moim pytaniem nikt nie może powiedzieć, że na takie pytanie jeszcze nie odpowiedział :-)
http.get('...')
call wysyła żądanie i otrzymuje odpowiedź (cały plik). Aby pobrać plik w kawałkach i zapisać go jednocześnie, zobacz moją edytowaną odpowiedź poniżej ;-) Wznowienie nie jest łatwe, być może liczysz zapisane bajty, a następnie pomijasz je, gdy ponownie pobierasz plik ( file.write(resp.body)
zwraca liczbę zapisanych bajtów).
Odpowiedzi:
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."
Edycja: zmieniona. Dziękuję Ci.
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
a platform-specific solution
.
wget
. OS X zapewnia curl
( curl http://oh.no/its/pbjellytime.flv --output secretlylove.flv
). Windows ma odpowiednik Powershell (new-object System.Net.WebClient).DownloadFile('http://oh.no/its/pbjellytime.flv','C:\tmp\secretlylove.flv')
. Pliki binarne dla wget i curl istnieją również dla wszystkich systemów operacyjnych do pobrania. Nadal bardzo polecam używanie biblioteki standardowej, chyba że piszesz kod wyłącznie dla własnej miłości.
Net::HTTP
. Otrzymuję część pliku, ale otrzymuję odpowiedź Net::HTTPOK
. Czy jest jakiś sposób, aby upewnić się, że plik został pobrany w całości?
Wiem, że to stare pytanie, ale Google rzucił mnie tutaj i myślę, że znalazłem prostszą odpowiedź.
W Railscasts # 179 Ryan Bates użył standardowej klasy Ruby OpenURI, aby zrobić wiele z tego, o co proszono:
( Ostrzeżenie : nieprzetestowany kod. Być może trzeba będzie go zmienić / poprawić).
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
open("http://somedomain.net/flv/sample/sample.flv", 'rb')
otworzy adres URL w trybie binarnym.
HTTP
=> HTTPS
i dowiedziałem się, jak rozwiązać ten problem za pomocą open_uri_redirections
Gem
open
z nową zdolnością, której kod wywołujący może nie przewidzieć. I tak nie powinieneś ufać przekazaniu danych przez użytkownika open
, ale musisz teraz być podwójnie ostrożny.
Oto mój plik http w Ruby do pliku przy użyciu 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 to, że jest zwięzłe i proste, ponieważ open
wykonuje większość ciężkich prac. I nie odczytuje w pamięci całej odpowiedzi.
open
Metoda strumień odpowiedzi> 1KB do A Tempfile
. Możemy wykorzystać tę wiedzę do wdrożenia tej szczupłej metody pobierania do pliku. Zobacz OpenURI::Buffer
implementację tutaj.
Zachowaj ostrożność podczas wprowadzania danych przez użytkownika!
open(name, *rest, &block)
jest niebezpieczne, jeśli name
pochodzi z danych wejściowych użytkownika!
open
rzeczywistości nie czyta odpowiedzi w pamięci, wczytuje ją do pliku tymczasowego dla odpowiedzi> 10240 bajtów. Więc miałeś rację, ale nie. Poprawiona odpowiedź wyjaśnia to nieporozumienie i, miejmy nadzieję,
EACCES: permission denied
błąd podczas zmiany nazwy pliku za pomocą mv
polecenia, to dlatego, że musisz najpierw zamknąć plik. Zaproponuj zmianę tej części naTempfile then io.close;
Przykład 3 w dokumentacji net / http Rubiego pokazuje, jak pobrać dokument przez HTTP i aby wyprowadzić plik zamiast po prostu ładować go do pamięci, należy zastąpić put zapisem binarnym do pliku, np. Jak pokazano w odpowiedzi Dejw.
Bardziej złożone przypadki przedstawiono w dalszej części tego samego dokumentu.
Poniższe rozwiązania najpierw odczytują całą zawartość z pamięci przed zapisaniem jej na dysku (aby uzyskać bardziej wydajne rozwiązania we / wy, spójrz na inne odpowiedzi).
Możesz użyć open-uri, czyli jednej wkładki
require 'open-uri'
content = open('http://example.com').read
Lub używając net / http
require 'net/http'
File.write("file_name", Net::HTTP.get(URI.parse("http://url.com")))
url
i file
), używając open-uri
jak w pierwszym: File.write(file, open(url).read)
... Dead simple, dla trywialnego przypadku pobierania.
Poszerzenie odpowiedzi Dejw (edycja 2):
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ą struny.
sleep
Komenda jest hack, które mogą znacznie zmniejszyć zużycie procesora, gdy sieć jest czynnikiem ograniczającym. Net :: HTTP nie czeka na zapełnienie bufora (16kB w wersji 1.9.2) przed uzyskaniem wyniku, więc procesor zajmuje się przenoszeniem małych fragmentów. Spanie przez chwilę daje szansę na zapełnienie bufora między zapisami, a użycie procesora jest porównywalne z rozwiązaniem curl, 4-5x różnica w mojej aplikacji. Bardziej niezawodne rozwiązanie mogłoby zbadać postęp f.pos
i dostosować limit czasu do docelowej, powiedzmy, 95% rozmiaru bufora - w rzeczywistości w ten sposób otrzymałem liczbę 0,005 w moim przykładzie.
Przepraszam, ale nie znam bardziej eleganckiego sposobu na to, by Ruby czekał na zapełnienie bufora.
Edytować:
Jest to wersja, która automatycznie dostosowuje się, aby utrzymać bufor tylko na poziomie lub poniżej pojemności. To nieeleganckie rozwiązanie, ale wydaje się być równie szybkie i zużywa tak mało czasu procesora, jak woła do zwijania.
Działa w trzech etapach. Krótki okres uczenia się z celowo długim czasem snu określa wielkość pełnego bufora. Okres upuszczania szybko skraca czas uśpienia z każdą iteracją, mnożąc go przez większy współczynnik, aż znajdzie niedopełniony bufor. Następnie, w normalnym okresie, dostosowuje się w górę iw dół o mniejszy współczynnik.
Mój Ruby jest trochę zardzewiały, więc jestem pewien, że można to poprawić. Przede wszystkim nie ma obsługi błędów. Może też można go podzielić na obiekt, z dala od samego pobierania, aby po prostu wywołać autosleep.sleep(f.pos)
swoją pętlę? Co więcej, można zmienić Net :: HTTP, 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
sleep
hack!
jeśli szukasz sposobu, jak pobrać plik tymczasowy, zrób rzeczy 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