Moje rozwiązanie jest silnie oparte na snippets.dzone.com/posts/show/2469, które pojawiło się po wpisaniu pobierania pliku ruby w pasku adresu FireFox ... więc czy przeprowadziłeś jakieś badania w Internecie, zanim zadałeś to pytanie?
Dawid
@Dejw: Poszukałem informacji i znalazłem tutaj odpowiedź na pytanie. Zasadniczo za pomocą tego samego kodu, który mi dałeś. resp.bodyCzęść 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ł :-)
Radek
3
Część ciała to dokładnie cały plik. Odpowiedź jest tworzona z nagłówków (http) i treści (pliku), więc zapisując treść zapisałeś plik ;-)
Dawid
1
jeszcze jedno pytanie ... powiedzmy, że plik ma 100 MB i proces pobierania zostaje przerwany w środku. Czy będzie coś uratowanego? Czy mogę wznowić plik?
Radek
Niestety nie, ponieważ 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).
Dawid
Odpowiedzi:
145
Najprostszym sposobem jest rozwiązanie specyficzne dla platformy:
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)
endend
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)
endendensure
f.close()
end
Tak, wiem. Dlatego powiedziałem, że tak a platform-specific solution.
Dawid
1
Więcej rozwiązań specyficznych dla platformy: platformy GNU / Linux zapewniają 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.
fny
1
początek ... upewnij się ... koniec nie jest konieczny, jeśli używana jest forma bloku otwartego. otwórz „sample.flv” do | f | .... f.write segment
lab419
1
Plik nietekstowy jest uszkodzony.
Paul
1
Używam pobierania fragmentów za pomocą 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?
Nickolay Kondratenko
120
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)
endend
open("http://somedomain.net/flv/sample/sample.flv", 'rb')otworzy adres URL w trybie binarnym.
zoli
1
ktoś wie, czy open-uri inteligentnie zapełnia bufor, jak wyjaśnił @Isa?
gdelfino
1
@gildefino Otrzymasz więcej odpowiedzi, jeśli otworzysz w związku z tym nowe pytanie. Jest mało prawdopodobne, aby wiele osób to przeczytało (i jest to również właściwa rzecz w przypadku przepełnienia stosu).
FWIW Niektórzy uważają, że open-uri jest niebezpieczny, ponieważ monkeypatkuje cały kod, w tym kod biblioteki, który używa openz 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.
require"open-uri"require"fileutils"defdownload(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)
endend
Główną zaletą jest to, że jest zwięzłe i proste, ponieważ openwykonuje większość ciężkich prac. I nie odczytuje w pamięci całej odpowiedzi.
openMetoda strumień odpowiedzi> 1KB do A Tempfile. Możemy wykorzystać tę wiedzę do wdrożenia tej szczupłej metody pobierania do pliku. Zobacz OpenURI::Bufferimplementację tutaj.
Zachowaj ostrożność podczas wprowadzania danych przez użytkownika!
open(name, *rest, &block)jest niebezpieczne, jeśli namepochodzi z danych wejściowych użytkownika!
To powinna być akceptowana odpowiedź, ponieważ jest zwięzła i prosta i nie ładuje całego pliku do pamięci ~ + wydajność (oszacowanie tutaj).
Nikkolasg
Zgadzam się z Nikkolasgiem. Właśnie próbowałem go użyć i działa bardzo dobrze. Jednak trochę go zmodyfikowałem, na przykład lokalna ścieżka zostanie wydedukowana automatycznie z podanego adresu URL, więc np. „Path = nil”, a następnie sprawdzanie, czy nie ma nil; jeśli jest zero, używam File.basename () na adresie URL, aby wydedukować lokalną ścieżkę.
@SimonPerepelitsa hehe. Poprawiłem go jeszcze raz, udostępniając teraz zwięzłą metodę pobierania do pliku, która nie odczytuje całej odpowiedzi w pamięci. Moja poprzednia odpowiedź byłaby wystarczająca, ponieważ w openrzeczywistoś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ę,
posłuży
3
Jeśli pojawi się EACCES: permission deniedbłąd podczas zmiany nazwy pliku za pomocą mvpolecenia, to dlatego, że musisz najpierw zamknąć plik. Zaproponuj zmianę tej części naTempfile then io.close;
David Douglas
28
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).
To wczytuje cały plik do pamięci przed zapisaniem go na dysku, więc ... to może być złe.
kgilpin
@kgilpin oba rozwiązania?
KrauseFx
1
Tak, oba rozwiązania.
eltiare
To powiedziawszy, jeśli nie masz nic przeciwko , krótsza wersja (zakładając, że adres URL i nazwa pliku są odpowiednio w zmiennych urli file), używając open-urijak w pierwszym: File.write(file, open(url).read)... Dead simple, dla trywialnego przypadku pobierania.
Lindes
17
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 filenamei urlsą struny.
sleepKomenda 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.posi 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 :-)
defhttp_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 endif learn <= 0then
learn -= 1elsif delta == max_delta thenif drop_period then
pause /= opt[:drop_factor]
else
pause /= opt[:adjust]
endelsif delta < max_delta then
drop_period = false
pause *= opt[:adjust]
end
sleep(pause)
}
}
}
}
end
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
źródło
a platform-specific solution
.wget
. OS X zapewniacurl
(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
źródło
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
Gemopen
z nową zdolnością, której kod wywołujący może nie przewidzieć. I tak nie powinieneś ufać przekazaniu danych przez użytkownikaopen
, 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 ATempfile
. Możemy wykorzystać tę wiedzę do wdrożenia tej szczupłej metody pobierania do pliku. ZobaczOpenURI::Buffer
implementację tutaj.Zachowaj ostrożność podczas wprowadzania danych przez użytkownika!
open(name, *rest, &block)
jest niebezpieczne, jeśliname
pochodzi z danych wejściowych użytkownika!źródło
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.
źródło
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")))
źródło
url
ifile
), używającopen-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
iurl
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ępf.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
źródło
sleep
hack!Bibliotek przyjaznych dla API jest więcej niż
Net::HTTP
np. 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
źródło
Miałem problemy, jeśli plik zawierał niemieckie umlauty (ä, ö, ü). Mogłem rozwiązać problem za pomocą:
ec = Encoding::Converter.new('iso-8859-1', 'utf-8') ... f << ec.convert(seg) ...
źródło
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
źródło