ฉันจะดาวน์โหลดและบันทึกไฟล์ไบนารีผ่าน HTTP โดยใช้ Ruby ได้อย่างไร
URL คือhttp://somedomain.net/flv/sample/sample.flv
.
ฉันอยู่บนแพลตฟอร์ม Windows และฉันไม่ต้องการเรียกใช้โปรแกรมภายนอกใด ๆ
ฉันจะดาวน์โหลดและบันทึกไฟล์ไบนารีผ่าน HTTP โดยใช้ Ruby ได้อย่างไร
URL คือhttp://somedomain.net/flv/sample/sample.flv
.
ฉันอยู่บนแพลตฟอร์ม Windows และฉันไม่ต้องการเรียกใช้โปรแกรมภายนอกใด ๆ
resp.body
ส่วนหนึ่งเป็นความสับสนฉันฉันคิดว่ามันจะบันทึกเฉพาะ 'ร่างกาย' เป็นส่วนหนึ่งของการตอบสนอง แต่ฉันต้องการที่จะประหยัดทั้ง / แฟ้มไบนารี ฉันยังพบว่าrio.rubyforge.orgอาจเป็นประโยชน์ ยิ่งไปกว่านั้นคำถามของฉันไม่มีใครสามารถพูดได้ว่าคำถามดังกล่าวยังไม่ได้รับคำตอบ :-)
http.get('...')
โทรส่งคำขอและรับการตอบกลับ (ทั้งไฟล์) หากต้องการดาวน์โหลดไฟล์เป็นชิ้น ๆ และบันทึกพร้อมกันให้ดูคำตอบที่แก้ไขของฉันด้านล่าง ;-) การดำเนินการต่อไม่ใช่เรื่องง่ายบางทีคุณอาจนับไบต์ที่คุณบันทึกไว้แล้วข้ามไปเมื่อคุณดาวน์โหลดไฟล์อีกครั้ง ( file.write(resp.body)
ส่งกลับจำนวนไบต์ที่เขียน)
คำตอบ:
วิธีที่ง่ายที่สุดคือโซลูชันเฉพาะแพลตฟอร์ม:
#!/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
a platform-specific solution
นั่นคือเหตุผลที่ผมบอกว่ามันเป็น
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 มีอยู่สำหรับระบบปฏิบัติการทั้งหมดผ่านการดาวน์โหลดเช่นกัน ฉันยังคงแนะนำเป็นอย่างยิ่งให้ใช้ไลบรารีมาตรฐานเว้นแต่คุณจะเขียนโค้ดเพื่อความรักของคุณเองเท่านั้น
Net::HTTP
. และฉันได้รับส่วนหนึ่งของไฟล์ Net::HTTPOK
แต่ได้รับการตอบ มีวิธีใดบ้างที่จะทำให้แน่ใจว่าเราดาวน์โหลดไฟล์เสร็จสมบูรณ์
ฉันรู้ว่านี่เป็นคำถามเก่า แต่ 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
open("http://somedomain.net/flv/sample/sample.flv", 'rb')
จะเปิด URL ในโหมดไบนารี
open
กับความสามารถใหม่ที่รหัสการโทรอาจไม่คาดคิด คุณไม่ควรเชื่อถือการป้อนข้อมูลของผู้ใช้open
แต่ตอนนี้คุณต้องระมัดระวังเป็นสองเท่า
นี่คือ 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
มาจากการป้อนข้อมูลของผู้ใช้!
open
จริงๆแล้วไม่ได้อ่านการตอบสนองในหน่วยความจำมันอ่านเป็นไฟล์ชั่วคราวสำหรับคำตอบใด ๆ > 10240 ไบต์ คุณเป็นคนใจดี แต่ไม่ใช่ คำตอบที่ได้รับการแก้ไขจะช่วย
EACCES: permission denied
ข้อผิดพลาดเมื่อเปลี่ยนชื่อไฟล์ด้วยmv
คำสั่งเนื่องจากคุณต้องปิดไฟล์ก่อน แนะนำให้เปลี่ยนส่วนนั้นเป็นTempfile then io.close;
ตัวอย่างที่ 3 ในเอกสาร net / httpของ Rubyแสดงวิธีการดาวน์โหลดเอกสารผ่าน HTTP และการส่งออกไฟล์แทนที่จะโหลดลงในหน่วยความจำการแทนที่จะเขียนไบนารีลงในไฟล์เช่นดังที่แสดงในคำตอบของ Dejw
กรณีที่ซับซ้อนมากขึ้นจะแสดงเพิ่มเติมในเอกสารเดียวกัน
วิธีแก้ไขปัญหาต่อไปนี้จะอ่านเนื้อหาทั้งหมดลงในหน่วยความจำก่อนที่จะเขียนลงดิสก์ (สำหรับโซลูชันที่มีประสิทธิภาพ 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")))
url
และfile
ตามลำดับ) โดยใช้open-uri
ในแบบแรก: File.write(file, open(url).read)
... Dead simple สำหรับกรณีดาวน์โหลดเล็กน้อย
ขยายความเกี่ยวกับคำตอบของเดช (แก้ไข 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
สับ!
มีไลบรารีที่เป็นมิตรกับ 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
ฉันมีปัญหาหากไฟล์มี Umlauts ภาษาเยอรมัน (ä, ö, ü) ฉันสามารถแก้ปัญหาได้โดยใช้:
ec = Encoding::Converter.new('iso-8859-1', 'utf-8')
...
f << ec.convert(seg)
...
หากคุณกำลังมองหาวิธีดาวน์โหลดไฟล์ชั่วคราวทำสิ่งต่างๆและลบออกลองใช้อัญมณีนี้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