ฉันจะดาวน์โหลดไฟล์ขนาดใหญ่อย่างมีประสิทธิภาพโดยใช้ Go ได้อย่างไร


110

มีวิธีดาวน์โหลดไฟล์ขนาดใหญ่โดยใช้ Go ซึ่งจะจัดเก็บเนื้อหาลงในไฟล์โดยตรงแทนที่จะเก็บไว้ในหน่วยความจำก่อนเขียนลงไฟล์หรือไม่? เนื่องจากไฟล์มีขนาดใหญ่มากการจัดเก็บทั้งหมดในหน่วยความจำก่อนที่จะเขียนลงไฟล์จะใช้หน่วยความจำทั้งหมด

คำตอบ:


216

ฉันจะถือว่าคุณหมายถึงการดาวน์โหลดผ่าน http (การตรวจสอบข้อผิดพลาดถูกละไว้เพื่อความกะทัดรัด):

import ("net/http"; "io"; "os")
...
out, err := os.Create("output.txt")
defer out.Close()
...
resp, err := http.Get("http://example.com/")
defer resp.Body.Close()
...
n, err := io.Copy(out, resp.Body)

http.Response's Body เป็น Reader ดังนั้นคุณสามารถใช้ฟังก์ชันใด ๆ ที่ใช้ Reader เช่นอ่านทีละชิ้นแทนที่จะอ่านทั้งหมดในครั้งเดียว ในกรณีเฉพาะนี้io.Copy()จะทำให้คุณพอใจ


89
โปรดทราบว่าio.Copyอ่าน 32kb (สูงสุด) จากอินพุตและเขียนลงในเอาต์พุตจากนั้นทำซ้ำ ดังนั้นอย่ากังวลเรื่องความจำ
Moshe Revah

1
จะยกเลิกการดาวน์โหลดได้อย่างไร?
Geln Yang

1
คุณสามารถใช้สิ่งนี้เพื่อยกเลิกการดาวน์โหลดหลังจากหมดเวลาที่กำหนดclient := http.Client{Timeout: 10 * time.Second,} client.Get("http://example.com/")
Bharath Kumar

56

คำตอบของ Steve M ในรูปแบบที่อธิบายได้ชัดเจนยิ่งขึ้น

import (
    "os"
    "net/http"
    "io"
)

func downloadFile(filepath string, url string) (err error) {

  // Create the file
  out, err := os.Create(filepath)
  if err != nil  {
    return err
  }
  defer out.Close()

  // Get the data
  resp, err := http.Get(url)
  if err != nil {
    return err
  }
  defer resp.Body.Close()

  // Check server response
  if resp.StatusCode != http.StatusOK {
    return fmt.Errorf("bad status: %s", resp.Status)
  }

  // Writer the body to file
  _, err = io.Copy(out, resp.Body)
  if err != nil  {
    return err
  }

  return nil
}

1
ในจักรวาลของฉันฉันใช้ DSL ที่จำเป็นในการดาวน์โหลดไฟล์ ... มันสะดวกที่จะ Exec () curl จนกระทั่งฉันตกอยู่ในปัญหาเกี่ยวกับ OS compat และ chroot ซึ่งฉันไม่ต้องการกำหนดค่าเพราะเป็นรูปแบบความปลอดภัยที่สมเหตุสมผล ดังนั้นคุณจึงแทนที่ CURL ของฉันด้วยรหัสนี้และได้รับการปรับปรุงประสิทธิภาพ 10-15 เท่า ดุ!
Richard

15

คำตอบที่เลือกข้างต้นio.Copyคือสิ่งที่คุณต้องการ แต่หากคุณสนใจในคุณสมบัติเพิ่มเติมเช่นการกลับมาดาวน์โหลดที่เสียการตั้งชื่อไฟล์อัตโนมัติการตรวจสอบความถูกต้องหรือการตรวจสอบความคืบหน้าของการดาวน์โหลดหลายครั้งให้ชำระเงินในแพ็กเกจคว้า


คุณสามารถเพิ่มข้อมูลโค้ดเพื่อให้แน่ใจว่าข้อมูลจะไม่สูญหายหากลิงก์ถูกเลิกใช้งาน
030

-6
  1. นี่คือตัวอย่าง https://github.com/thbar/golang-playground/blob/master/download-files.go

  2. นอกจากนี้ฉันให้รหัสบางอย่างอาจช่วยคุณได้

รหัส:

func HTTPDownload(uri string) ([]byte, error) {
    fmt.Printf("HTTPDownload From: %s.\n", uri)
    res, err := http.Get(uri)
    if err != nil {
        log.Fatal(err)
    }
    defer res.Body.Close()
    d, err := ioutil.ReadAll(res.Body)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("ReadFile: Size of download: %d\n", len(d))
    return d, err
}

func WriteFile(dst string, d []byte) error {
    fmt.Printf("WriteFile: Size of download: %d\n", len(d))
    err := ioutil.WriteFile(dst, d, 0444)
    if err != nil {
        log.Fatal(err)
    }
    return err
}

func DownloadToFile(uri string, dst string) {
    fmt.Printf("DownloadToFile From: %s.\n", uri)
    if d, err := HTTPDownload(uri); err == nil {
        fmt.Printf("downloaded %s.\n", uri)
        if WriteFile(dst, d) == nil {
            fmt.Printf("saved %s as %s\n", uri, dst)
        }
    }
}

13
ตัวอย่างนี้อ่านเนื้อหาทั้งหมดลงในหน่วยความจำด้วยไฟล์ioutil.ReadAll(). ไม่เป็นไรตราบใดที่คุณจัดการกับไฟล์ขนาดเล็ก
eduncan911

13
@ eduncan911 แต่มันไม่ดีสำหรับคำถามนี้ซึ่งพูดถึงไฟล์ขนาดใหญ่อย่างชัดเจนและไม่ต้องการดูดทั้งหมดไปไว้ในหน่วยความจำ
Dave C

2
ถูกต้องนั่นคือเหตุผลที่ฉันแสดงความคิดเห็นเช่นนั้น - เพื่อให้คนอื่นรู้และอย่าใช้สิ่งนี้กับไฟล์ขนาดใหญ่
eduncan911

4
นี่ไม่ใช่คำตอบที่ไม่เป็นพิษเป็นภัยและควรลบออกไป การใช้ ReadAll ท่ามกลางโค้ดจำนวนมากเป็นปัญหาแฝงที่รอจนกว่าจะใช้ไฟล์ขนาดใหญ่ สิ่งที่เกิดขึ้นคือหากมี ReadAll ในไฟล์ขนาดใหญ่การตอบสนองมักจะเป็นไปพร้อมกับการใช้หน่วยความจำที่สูงและค่า AWS ที่เพิ่มขึ้นจนกว่าจะมีบางอย่างล้มเหลว เมื่อถึงเวลาที่พบปัญหาบิลก็สูงอยู่แล้ว
Rob
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.