Làm cách nào để tải xuống tệp nhị phân qua HTTP?


131

Làm cách nào để tải xuống và lưu tệp nhị phân qua HTTP bằng Ruby?

URL là http://somedomain.net/flv/sample/sample.flv.

Tôi đang ở trên nền tảng Windows và tôi không muốn chạy bất kỳ chương trình bên ngoài nào.


Giải pháp của tôi dựa rất nhiều vào snippets.dzone.com/posts/show/2469 xuất hiện sau khi tôi nhập tệp ruby ​​tải xuống trong thanh địa chỉ FireFox ... vậy bạn đã thực hiện nghiên cứu nào trên internet trước khi hỏi câu hỏi này chưa?
Dawid

@Dejw: Tôi đã nghiên cứu và tìm thấy một câu hỏi được trả lời ở đây. Về cơ bản với cùng mã mà bạn đã cho tôi. Phần resp.bodynày làm tôi bối rối, tôi nghĩ rằng nó sẽ chỉ lưu phần 'cơ thể' của phản hồi nhưng tôi muốn lưu toàn bộ / tệp nhị phân. Tôi cũng thấy rằng rio.rubyforge.org có thể hữu ích. Ngoài ra, với câu hỏi của tôi, không ai có thể nói rằng câu hỏi đó chưa được trả lời :-)
Radek

3
Phần cơ thể là chính xác toàn bộ tập tin. Phản hồi được tạo từ các tiêu đề (http) và phần thân (tệp), vì vậy khi bạn lưu phần thân Bạn đã lưu tệp ;-)
Dawid

1
thêm một câu hỏi nữa ... giả sử tập tin lớn 100 MB và quá trình tải xuống bị gián đoạn ở giữa. Có điều gì sẽ được cứu? Tôi có thể làm sơ yếu lý lịch của tập tin?
Radek

Thật không may, vì http.get('...')cuộc gọi sẽ gửi một yêu cầu và nhận được phản hồi (toàn bộ tệp). Để tải xuống một tệp trong các khối và lưu nó đồng thời, hãy xem câu trả lời đã được chỉnh sửa của tôi bên dưới ;-) Tiếp tục không dễ dàng, có thể Bạn đếm byte Bạn đã lưu và sau đó bỏ qua chúng khi Bạn tải lại tệp ( file.write(resp.body)trả về số byte đã ghi).
Dawid

Câu trả lời:


143

Cách đơn giản nhất là giải pháp dành riêng cho nền tảng:

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

Có lẽ bạn đang tìm kiếm:

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

Chỉnh sửa: Thay đổi. Cảm ơn bạn.

Edit2: Giải pháp lưu một phần của tệp trong khi tải xuống:

# 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
Vâng tôi biết. Đó là lý do tại sao tôi nói rằng nó là a platform-specific solution.
Dawid

1
Các giải pháp dành riêng cho nền tảng hơn: Các nền tảng GNU / Linux cung cấp wget. OS X cung cấp curl( curl http://oh.no/its/pbjellytime.flv --output secretlylove.flv). Windows có tương đương Powershell (new-object System.Net.WebClient).DownloadFile('http://oh.no/its/pbjellytime.flv','C:\tmp\secretlylove.flv'). Binaries cho wget và curl tồn tại cho tất cả các hệ điều hành thông qua tải xuống là tốt. Tôi vẫn khuyên bạn nên sử dụng thư viện tiêu chuẩn trừ khi mã viết của bạn chỉ dành cho người yêu của bạn '.
fny

1
bắt đầu ... đảm bảo ... kết thúc là không cần thiết nếu hình thức khối mở được sử dụng. mở 'sample.flv' do | f | .... phân đoạn f.write
lab419

1
Các tập tin phi văn bản đến bị hỏng.
Paul

1
Tôi sử dụng chunked tải bằng cách sử dụng Net::HTTP. Và tôi nhận được một phần của tập tin nhưng nhận được phản hồi Net::HTTPOK. Có cách nào để đảm bảo chúng tôi tải xuống tệp hoàn toàn không?
Nickolay Kondratenko

118

Tôi biết rằng đây là một câu hỏi cũ, nhưng Google đã ném tôi đến đây và tôi nghĩ rằng tôi đã tìm thấy một câu trả lời đơn giản hơn.

Trong Railscasts # 179 , Ryan Bates đã sử dụng lớp OpenURI tiêu chuẩn Ruby để thực hiện phần lớn những gì được hỏi như thế này:

( Cảnh báo : mã chưa được kiểm tra. Bạn có thể cần thay đổi / chỉnh sửa mã.)

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')sẽ mở URL ở chế độ nhị phân.
zoli

1
có ai biết nếu open-uri thông minh về việc điền vào bộ đệm như @Isa đã giải thích không?
gdelfino

1
@gildefino Bạn sẽ nhận được nhiều câu trả lời hơn nếu bạn mở một câu hỏi mới cho điều đó. Nhiều khả năng nhiều người sẽ không đọc được điều này (và đó cũng là điều thích hợp để làm trong Stack Overflow).
kikito

2
Tuyệt vời. Tôi gặp vấn đề với HTTP=> HTTPSchuyển hướng và tìm ra cách giải quyết bằng open_uri_redirectionsGem
mathielo

1
FWIW một số người nghĩ rằng open-uri là nguy hiểm vì nó đánh lừa tất cả mã, bao gồm cả mã thư viện, sử dụng openmột khả năng mới mà mã gọi có thể không lường trước được. Dù sao thì bạn cũng không nên tin vào đầu vào của người dùng open, nhưng bạn cần phải cẩn thận gấp đôi.
phương pháp

42

Đây là Ruby http của tôi để tập tin sử dụng 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

Ưu điểm chính ở đây là ngắn gọn và đơn giản, vì openphần lớn việc nâng vật nặng.Và nó không đọc toàn bộ phản hồi trong bộ nhớ.

Các openphương pháp sẽ phản ứng dòng> 1kb đến một Tempfile. Chúng ta có thể khai thác kiến ​​thức này để thực hiện phương pháp tải xuống nạc này. Xem việc OpenURI::Bufferthực hiện ở đây.

Hãy cẩn thận với đầu vào do người dùng cung cấp! open(name, *rest, &block)là không an toàn nếu nameđến từ đầu vào của người dùng!


4
Đây phải là câu trả lời được chấp nhận vì nó ngắn gọn & đơn giản & không tải toàn bộ tệp trong bộ nhớ ~ + hiệu suất (dự đoán ở đây).
Nikkolasg

Tôi đồng ý với Nikkolasg. Tôi chỉ cố gắng sử dụng nó và nó hoạt động rất tốt. Tôi đã sửa đổi nó một chút, ví dụ, đường dẫn cục bộ sẽ được suy luận tự động từ URL đã cho, ví dụ: "path = nil" và sau đó kiểm tra nil; nếu không, thì tôi sử dụng File.basename () trên url để suy ra đường dẫn cục bộ.
shevy

1
Đây sẽ là câu trả lời tốt nhất, nhưng mở uri KHÔNG tải toàn bộ tập tin trong bộ nhớ stackoverflow.com/questions/17454956/...
Simon Perepelitsa

2
@SimonPerepelitsa hehe. Tôi đã sửa đổi nó một lần nữa, bây giờ cung cấp một phương pháp tải xuống tệp ngắn gọn mà không đọc toàn bộ phản hồi trong bộ nhớ. Câu trả lời trước của tôi sẽ là đủ, vì openthực sự không đọc phản hồi trong bộ nhớ, nó đọc nó thành một tệp tạm thời cho bất kỳ phản hồi nào> 10240 byte. Vì vậy, bạn đã tử tế nhưng không phải. Câu trả lời sửa đổi sẽ xóa
tan

3
Nếu bạn gặp EACCES: permission deniedlỗi khi thay đổi tên tệp bằng mvlệnh của nó vì bạn phải đóng tệp trước. Đề nghị thay đổi phần đó thànhTempfile then io.close;
David Douglas

28

Ví dụ 3 trong tài liệu net / http của Ruby cho thấy cách tải xuống tài liệu qua HTTP và để xuất tệp thay vì chỉ tải nó vào bộ nhớ, thay thế bằng cách ghi nhị phân vào tệp, ví dụ như trong câu trả lời của Dejw.

Các trường hợp phức tạp hơn được hiển thị sâu hơn trong cùng một tài liệu.


+1 để chỉ vào tài liệu hiện có và các ví dụ khác.
semperos


26

Bạn có thể sử dụng open-uri, một lớp lót

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

Hoặc bằng cách sử dụng net / http

require 'net/http'
File.write("file_name", Net::HTTP.get(URI.parse("http://url.com")))

10
Điều này đọc toàn bộ tập tin vào bộ nhớ trước khi ghi nó vào đĩa, vì vậy ... điều đó có thể là xấu.
kgilpin

@kgilpin cả hai giải pháp?
KrauseFx

1
Vâng, cả hai giải pháp.
eltiare 17/05/2015

Điều đó nói rằng, nếu bạn ổn với điều đó, một phiên bản ngắn hơn (giả sử url và tên tệp tương ứng là các biến urlfile, open-urinhư sử dụng như trong phần đầu tiên: File.write(file, open(url).read)... Chết đơn giản, cho trường hợp tải xuống tầm thường.
lindes

17

Mở rộng về câu trả lời của 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 
      }
    }
  }
}

ở đâu filenameurllà chuỗi.

Các sleeplệnh là một hack mà có thể đột ngột giảm sử dụng CPU khi mạng là yếu tố hạn chế. Net :: HTTP không đợi bộ đệm (16kB trong v1.9.2) lấp đầy trước khi mang lại, do đó, CPU tự di chuyển các khối nhỏ xung quanh. Ngủ một lát sẽ cho bộ đệm một cơ hội để điền vào giữa ghi và việc sử dụng CPU có thể so sánh với giải pháp cuộn tròn, chênh lệch 4-5 lần trong ứng dụng của tôi. Một giải pháp mạnh mẽ hơn có thể kiểm tra tiến trình f.posvà điều chỉnh thời gian chờ để nhắm mục tiêu, giả sử, 95% kích thước bộ đệm - thực tế đó là cách tôi lấy số 0,005 trong ví dụ của mình.

Xin lỗi, nhưng tôi không biết một cách thanh lịch hơn khi Ruby đợi bộ đệm lấp đầy.

Biên tập:

Đây là phiên bản tự động điều chỉnh chính nó để giữ bộ đệm ở mức hoặc dưới công suất. Đó là một giải pháp không phù hợp, nhưng dường như nó cũng nhanh và sử dụng ít thời gian CPU, vì nó đang kêu gọi cuộn tròn.

Nó hoạt động trong ba giai đoạn. Một thời gian học ngắn với thời gian ngủ dài có chủ ý thiết lập kích thước của một bộ đệm đầy đủ. Thời gian thả làm giảm thời gian ngủ nhanh chóng với mỗi lần lặp, bằng cách nhân nó với một yếu tố lớn hơn, cho đến khi nó tìm thấy một bộ đệm đầy. Sau đó, trong thời gian bình thường, nó điều chỉnh lên xuống theo một yếu tố nhỏ hơn.

Ruby của tôi hơi rỉ sét, vì vậy tôi chắc chắn điều này có thể được cải thiện. Trước hết, không có xử lý lỗi. Ngoài ra, có lẽ nó có thể được tách thành một đối tượng, cách xa bản tải xuống, để bạn chỉ cần gọi autosleep.sleep(f.pos)trong vòng lặp của mình? Thậm chí tốt hơn, Net :: HTTP có thể được thay đổi để chờ bộ đệm đầy đủ trước khi mang lại :-)

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

Tôi thích sleephack!
Radek

13

Có nhiều thư viện thân thiện với api hơn Net::HTTP, ví dụ như 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

Tôi gặp vấn đề, nếu tập tin chứa tiếng Đức Umlauts (ä, ö, ü). Tôi có thể giải quyết vấn đề bằng cách sử dụng:

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

0

nếu bạn đang tìm cách tải tập tin tạm thời, hãy làm công cụ và xóa nó, hãy thử viên ngọc này 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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.