ฉันจะดาวน์โหลดไฟล์ไบนารีผ่าน HTTP ได้อย่างไร


134

ฉันจะดาวน์โหลดและบันทึกไฟล์ไบนารีผ่าน HTTP โดยใช้ Ruby ได้อย่างไร

URL คือhttp://somedomain.net/flv/sample/sample.flv.

ฉันอยู่บนแพลตฟอร์ม Windows และฉันไม่ต้องการเรียกใช้โปรแกรมภายนอกใด ๆ


วิธีแก้ปัญหาของฉันใช้snippets.dzone.com/posts/show/2469ซึ่งปรากฏขึ้นหลังจากที่ฉันพิมพ์การดาวน์โหลดไฟล์ทับทิมในแถบที่อยู่ FireFox ... คุณได้ทำการค้นคว้าทางอินเทอร์เน็ตก่อนที่จะถามคำถามนี้หรือไม่
Dawid

@Dejw: ฉันค้นคว้าและพบคำถามที่ตอบได้ที่นี่ โดยทั่วไปมีรหัสเดียวกับที่คุณให้ฉัน resp.bodyส่วนหนึ่งเป็นความสับสนฉันฉันคิดว่ามันจะบันทึกเฉพาะ 'ร่างกาย' เป็นส่วนหนึ่งของการตอบสนอง แต่ฉันต้องการที่จะประหยัดทั้ง / แฟ้มไบนารี ฉันยังพบว่าrio.rubyforge.orgอาจเป็นประโยชน์ ยิ่งไปกว่านั้นคำถามของฉันไม่มีใครสามารถพูดได้ว่าคำถามดังกล่าวยังไม่ได้รับคำตอบ :-)
เด็ค

3
ส่วนของร่างกายเป็นไฟล์ทั้งหมด การตอบสนองถูกสร้างขึ้นจากส่วนหัว (http) และเนื้อหา (ไฟล์) ดังนั้นเมื่อคุณบันทึกเนื้อหาคุณบันทึกไฟล์ ;-)
Dawid

1
อีกคำถามหนึ่ง ... สมมติว่าไฟล์มีขนาดใหญ่ 100MB และกระบวนการดาวน์โหลดถูกขัดจังหวะตรงกลาง จะมีอะไรรอดไหม? ฉันสามารถดำเนินการต่อไฟล์ได้หรือไม่?
Radek

ไม่น่าเสียดายเพราะการhttp.get('...')โทรส่งคำขอและรับการตอบกลับ (ทั้งไฟล์) หากต้องการดาวน์โหลดไฟล์เป็นชิ้น ๆ และบันทึกพร้อมกันให้ดูคำตอบที่แก้ไขของฉันด้านล่าง ;-) การดำเนินการต่อไม่ใช่เรื่องง่ายบางทีคุณอาจนับไบต์ที่คุณบันทึกไว้แล้วข้ามไปเมื่อคุณดาวน์โหลดไฟล์อีกครั้ง ( file.write(resp.body)ส่งกลับจำนวนไบต์ที่เขียน)
Dawid

คำตอบ:


145

วิธีที่ง่ายที่สุดคือโซลูชันเฉพาะแพลตฟอร์ม:

 #!/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 (new-object System.Net.WebClient).DownloadFile('http://oh.no/its/pbjellytime.flv','C:\tmp\secretlylove.flv')มีเทียบเท่า ไบนารีสำหรับ wget และ curl มีอยู่สำหรับระบบปฏิบัติการทั้งหมดผ่านการดาวน์โหลดเช่นกัน ฉันยังคงแนะนำเป็นอย่างยิ่งให้ใช้ไลบรารีมาตรฐานเว้นแต่คุณจะเขียนโค้ดเพื่อความรักของคุณเองเท่านั้น
fny

1
เริ่มต้น ... ให้แน่ใจว่า ... สิ้นสุดไม่จำเป็นหากใช้แบบฟอร์มบล็อกเปิด เปิด 'sample.flv' ทำ | f | .... ฉ. เขียนเซ็กเมนต์
lab419

1
ไฟล์ที่ไม่ใช่ข้อความได้รับความเสียหาย
พอล

1
ฉันใช้การดาวน์โหลดแบบเป็นก้อนโดยใช้ไฟล์Net::HTTP. และฉันได้รับส่วนหนึ่งของไฟล์ Net::HTTPOKแต่ได้รับการตอบ มีวิธีใดบ้างที่จะทำให้แน่ใจว่าเราดาวน์โหลดไฟล์เสร็จสมบูรณ์
Nickolay Kondratenko

120

ฉันรู้ว่านี่เป็นคำถามเก่า แต่ Google ส่งฉันมาที่นี่และฉันคิดว่าฉันพบคำตอบที่ง่ายกว่านี้

ในRailscasts # 179 Ryan Bates ใช้ Ruby standard class 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
มีใครรู้บ้างว่า open-uri ฉลาดในการเติมบัฟเฟอร์ตามที่ @Isa อธิบายหรือไม่?
gdelfino

1
@gildefino คุณจะได้รับคำตอบเพิ่มเติมหากคุณเปิดคำถามใหม่สำหรับสิ่งนั้น ไม่น่าเป็นไปได้ที่หลาย ๆ คนจะอ่านสิ่งนี้ (และเป็นสิ่งที่เหมาะกับการทำใน Stack Overflow)
kikito

2
น่ากลัว ฉันมีปัญหากับHTTP=> การHTTPSเปลี่ยนเส้นทางและพบวิธีแก้ปัญหาโดยใช้open_uri_redirectionsGem
mathielo

2
FWIW บางคนคิดว่า open-uri เป็นอันตรายเพราะมันจับคู่รหัสทั้งหมดรวมถึงรหัสไลบรารีที่ใช้openกับความสามารถใหม่ที่รหัสการโทรอาจไม่คาดคิด คุณไม่ควรเชื่อถือการป้อนข้อมูลของผู้ใช้openแต่ตอนนี้คุณต้องระมัดระวังเป็นสองเท่า
วิธี

44

นี่คือ 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.read) }
  when Tempfile then io.close; FileUtils.mv(io.path, path)
  end
end

ข้อได้เปรียบหลักคือกระชับและเรียบง่ายเนื่องจากopenมีการยกของหนักมาก และไม่อ่านคำตอบทั้งหมดในหน่วยความจำ

openวิธีการตอบสนองจะสตรีม> 1kb Tempfileไป เราสามารถใช้ประโยชน์จากความรู้นี้เพื่อใช้วิธีการดาวน์โหลดแบบลีนเป็นไฟล์ ดูการOpenURI::Bufferใช้งานที่นี่

โปรดใช้ความระมัดระวังกับข้อมูลที่ผู้ใช้ให้มา! open(name, *rest, &block)ไม่ปลอดภัยหากnameมาจากการป้อนข้อมูลของผู้ใช้!


4
นี่ควรเป็นคำตอบที่ได้รับการยอมรับเนื่องจากมีความกระชับและเรียบง่าย & ไม่โหลดทั้งไฟล์ในหน่วยความจำ ~ + ประสิทธิภาพ (คาดเดาได้ที่นี่)
Nikkolasg

ฉันเห็นด้วยกับ Nikkolasg เพิ่งลองใช้แล้วได้ผลดีมาก ฉันแก้ไขมันเล็กน้อยตัวอย่างเช่น local path จะถูกอนุมานโดยอัตโนมัติจาก URL ที่กำหนดดังนั้นเช่น "path = nil" จากนั้นตรวจหา nil; ถ้าเป็นศูนย์ฉันจะใช้ File.basename () บน url เพื่ออนุมานเส้นทางภายในเครื่อง
shevy

1
นี้จะเป็นคำตอบที่ดีที่สุด แต่เปิด URI ไมโหลดไฟล์ทั้งหมดในหน่วยความจำstackoverflow.com/questions/17454956/...
ไซมอน Perepelitsa

2
@SimonPerepelitsa ฮิฮิ. ฉันแก้ไขอีกครั้งตอนนี้ให้วิธีการดาวน์โหลดเป็นไฟล์ที่กระชับซึ่งไม่ได้อ่านคำตอบทั้งหมดในหน่วยความจำ คำตอบก่อนหน้าของฉันน่าจะเพียงพอแล้วเพราะopenจริงๆแล้วไม่ได้อ่านการตอบสนองในหน่วยความจำมันอ่านเป็นไฟล์ชั่วคราวสำหรับคำตอบใด ๆ > 10240 ไบต์ คุณเป็นคนใจดี แต่ไม่ใช่ คำตอบที่ได้รับการแก้ไขจะช่วย
ขจัด

3
หากคุณได้รับEACCES: permission deniedข้อผิดพลาดเมื่อเปลี่ยนชื่อไฟล์ด้วยmvคำสั่งเนื่องจากคุณต้องปิดไฟล์ก่อน แนะนำให้เปลี่ยนส่วนนั้นเป็นTempfile then io.close;
David Douglas

28

ตัวอย่างที่ 3 ในเอกสาร net / httpของ Rubyแสดงวิธีการดาวน์โหลดเอกสารผ่าน HTTP และการส่งออกไฟล์แทนที่จะโหลดลงในหน่วยความจำการแทนที่จะเขียนไบนารีลงในไฟล์เช่นดังที่แสดงในคำตอบของ Dejw

กรณีที่ซับซ้อนมากขึ้นจะแสดงเพิ่มเติมในเอกสารเดียวกัน


+1 สำหรับชี้ไปที่เอกสารที่มีอยู่และตัวอย่างเพิ่มเติม
semperos

1
นี่คือลิงค์เฉพาะ: ruby-doc.org/stdlib-2.1.4/libdoc/net/http/rdoc/Net/…
kgilpin

26

วิธีแก้ไขปัญหาต่อไปนี้จะอ่านเนื้อหาทั้งหมดลงในหน่วยความจำก่อนที่จะเขียนลงดิสก์ (สำหรับโซลูชันที่มีประสิทธิภาพ i / o เพิ่มเติมโปรดดูคำตอบอื่น ๆ )

คุณสามารถใช้ 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)... Dead simple สำหรับกรณีดาวน์โหลดเล็กน้อย
lindes

17

ขยายความเกี่ยวกับคำตอบของเดช (แก้ไข 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 
      }
    }
  }
}

ที่ไหนfilenameและurlเป็นสตริง

sleepคำสั่งสับที่ที่สามารถอย่างรวดเร็วลดการใช้งาน CPU เมื่อเครือข่ายเป็นปัจจัยที่ จำกัด Net :: HTTP ไม่รอให้บัฟเฟอร์ (16kB ใน v1.9.2) เติมเต็มก่อนที่จะให้ผลดังนั้น CPU จึงเคลื่อนย้ายชิ้นส่วนเล็ก ๆ ไปรอบ ๆ การนอนพักสักครู่ทำให้บัฟเฟอร์มีโอกาสเติมระหว่างการเขียนและการใช้งาน CPU เปรียบได้กับโซลูชัน curl ซึ่งแตกต่างกัน 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สับ!
เด็

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

ฉันมีปัญหาหากไฟล์มี 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.