HTTP를 통해 바이너리 파일을 다운로드하려면 어떻게합니까?


131

Ruby를 사용하여 HTTP를 통해 바이너리 파일을 다운로드하고 저장하려면 어떻게합니까?

URL은 http://somedomain.net/flv/sample/sample.flv입니다.

저는 Windows 플랫폼을 사용하고 있으며 외부 프로그램을 실행하지 않는 것을 선호합니다.


내 솔루션은 FireFox 주소 표시 줄에 루비 파일 다운로드 를 입력 한 후 나타나는 snippets.dzone.com/posts/show/2469 를 기반으로 합니다 ...이 질문을하기 전에 인터넷에 대한 조사를 했습니까?
Dawid

@ Dejw : 연구를하고 여기에서 답변이있는 질문을 찾았습니다. 기본적으로 당신이 나에게 준 것과 같은 코드로. resp.body부분은 내가 응답의 '몸'부분을 절약 할 것이라고 생각하지만 전체 / 바이너리 파일을 저장할 저를 혼동한다. 또한 rio.rubyforge.org 가 도움이 될 수 있다는 것을 알았습니다 . 또한 내 질문으로 아무도 그런 질문에 아직 대답하지 않았다고 말할 수 없습니다 :-)
Radek

3
본문 부분은 정확히 전체 파일입니다. 응답은 헤더 (http) 및 본문 (파일)에서 작성되므로 본문을 저장할 때 파일을 저장했습니다. ;-)
Dawid

1
질문 하나 더 ... 파일의 크기가 100MB이고 다운로드 프로세스가 중간에 중단되었다고 가정 해 봅시다. 저장된 것이 있습니까? 파일을 다시 시작할 수 있습니까?
Radek

불행히도, 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."

편집 : 변경되었습니다. 감사합니다.

편집 2 : 다운로드하는 동안 파일의 일부를 저장하는 솔루션 :

# 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
열린 블록 양식을 사용하는 경우 begin ... ensure ... end가 필요하지 않습니다. 'sample.flv'를 엽니 다. | f | .... f. 쓰기 세그먼트
lab419

1
텍스트가 아닌 파일이 손상된 상태로 도착합니다.
Paul

1
를 사용하여 청크 다운로드를 사용 Net::HTTP합니다. 그리고 나는 파일의 일부를 받지만 응답을 얻습니다 Net::HTTPOK. 파일을 완전히 다운로드 할 수있는 방법이 있습니까?
Nickolay Kondratenko

118

나는 이것이 오래된 질문이라는 것을 알고 있지만 Google에서 나를 던졌고 더 간단한 대답을 찾았습니다.

에서 Railscasts # 179 , 라이언 베이츠는 루비 표준 클래스를 사용 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
@Isa가 설명했듯이 open-uri가 버퍼를 채우는 것에 대해 지능적인지 아는 사람이 있습니까?
gdelfino

1
@gildefino 새 질문을 열면 더 많은 답변을 얻을 수 있습니다. 많은 사람들이 이것을 읽지 않을 것입니다 (그리고 스택 오버플로에서 수행하는 것이 좋습니다).
kikito

2
대박. HTTP=> HTTPS리디렉션에 문제가 있었고 Gem 사용하여 문제를 해결하는 방법을 찾았습니다open_uri_redirections
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 이상의 응답을 a로 스트리밍합니다 Tempfile. 이 지식을 활용하여이 린 다운로드 파일 방법을 구현할 수 있습니다. 여기 에서 OpenURI::Buffer구현을 참조 하십시오.

사용자가 제공 한 입력에주의하십시오! 사용자 입력에서 오는 open(name, *rest, &block)경우 안전하지 않습니다 name!


4
이것은 간결하고 간단하며 전체 파일을 메모리 ~ + 성능 (여기서는 추측)에로드하지 않으므로 허용되는 대답이어야합니다.
Nikkolasg

Nikkolasg에 동의합니다. 방금 사용하려고했는데 매우 잘 작동합니다. 예를 들어 로컬 경로는 주어진 URL에서 자동으로 추론됩니다. 예를 들어 "path = nil"그리고 nil 확인; 그것이 nil이라면, URL에서 File.basename ()을 사용하여 로컬 경로를 추론합니다.
shevy

1
이것이 가장 좋은 대답이지만 open-uri 전체 파일을 메모리 로드합니다. stackoverflow.com/questions/17454956/…
Simon Perepelitsa

2
@SimonPerepelitsa hehe. 나는 그것을 다시 수정 하여 메모리 에서 전체 응답읽지 못하는 간결한 파일로 다운로드 방법을 제공합니다 . open실제로 이전 응답은 메모리에서 응답을 읽지 못 하기 때문에 10240 바이트 이상의 응답에 대해 임시 파일로 읽습니다. 그래서 당신은 친절했지만 그렇지 않았습니다. 수정 된 답변은이 오해를 정리하고 루비의 힘에 대한 훌륭한 모범이되기를 바랍니다.)
Overbryd

3
당신은 얻을 경우 EACCES: permission denied와 파일 이름을 변경할 때 오류를 mv먼저 파일을 닫습니다하기 때문에 명령의를. 다음 부분으로 변경 제안Tempfile then io.close;
David Douglas

28

Ruby의 net / http 문서 에있는 예제 3은 HTTP를 통해 문서를 다운로드하고 파일을 메모리에로드하는 대신 파일을 출력하는 방법을 보여줍니다 (예 : Dejw의 답변에 표시된대로 파일에 2 진 쓰기로 대체).

더 복잡한 사례는 동일한 문서에서 더 아래에 표시됩니다.


기존 문서 및 추가 예제를 가리키는 +1
semperos


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)... 사소한 다운로드 사례의 경우 간단합니다.
lindes

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명령은 네트워크가 제한 요인 일 때 CPU 사용량을 크게 줄일 수있는 해킹입니다 . Net :: HTTP는 버퍼 (v1.9.2의 16kB)가 가득 찰 때까지 기다리지 않으므로 CPU는 자체적으로 작은 덩어리를 움직입니다. 잠을 자면 버퍼가 쓰기 사이를 채울 수 있으며 CPU 사용량은 응용 프로그램의 4-5 배 차이 인 컬 솔루션과 비슷합니다. 더 강력한 솔루션은 진행률을 검사 f.pos하고 버퍼 크기의 95 %를 목표로 시간 초과를 조정할 수 있습니다 . 실제로 예제에서 0.005 숫자를 얻었습니다.

죄송하지만 Ruby가 버퍼가 가득 찰 때까지 기다리는 더 우아한 방법을 모르겠습니다.

편집하다:

이것은 버퍼를 용량 이하로 유지하기 위해 자동으로 조정되는 버전입니다. 우아하지 않은 솔루션이지만 컬링을 부르는 것처럼 빠르며 CPU 시간을 적게 사용하는 것 같습니다.

세 단계로 작동합니다. 의도적으로 긴 수면 시간을 갖는 짧은 학습 기간은 전체 버퍼의 크기를 설정합니다. 드롭 기간은 언더필 버퍼를 찾을 때까지 더 큰 계수를 곱하여 각 반복마다 슬립 시간을 빠르게 줄입니다. 그런 다음 정상 기간 동안 더 작은 계수만큼 위아래로 조정됩니다.

내 루비는 조금 녹슬 었으므로 개선 될 수 있다고 확신합니다. 우선, 오류 처리가 없습니다. 또한 다운로드 자체와는 별도로 객체로 분리되어 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핵을 좋아한다 !
Radek

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

파일에 German 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
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.