Wie kann ich mit Go eine große Datei effizient herunterladen?

106

Gibt es eine Möglichkeit, eine große Datei mit Go herunterzuladen, bei der der Inhalt direkt in einer Datei gespeichert wird, anstatt alles im Speicher zu speichern, bevor er in eine Datei geschrieben wird? Da die Datei so groß ist, wird das Speichern des gesamten Speichers vor dem Schreiben in eine Datei den gesamten Speicher belegen.

Cory
quelle

Antworten:

214

Ich gehe davon aus, dass Sie den Download über http meinen (Fehlerprüfungen wurden der Kürze halber weggelassen):

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)

Der Körper der http.Response ist ein Reader, sodass Sie alle Funktionen eines Readers verwenden können, um beispielsweise einen Block gleichzeitig und nicht alle gleichzeitig zu lesen. In diesem speziellen Fall io.Copy()erledigt das Grunzen für Sie.

Steve M.
quelle
85
Beachten Sie, dass io.Copy32 KB (maximal) von der Eingabe gelesen und in die Ausgabe geschrieben und dann wiederholt werden. Mach dir also keine Sorgen um das Gedächtnis.
Moshe Revah
Wie kann ich den Download-Fortschritt abbrechen?
Geln Yang
Sie können dies verwenden, um den Download nach dem angegebenen Timeout abzubrechenclient := http.Client{Timeout: 10 * time.Second,} client.Get("http://example.com/")
Bharath Kumar
55

Eine aussagekräftigere Version von Steve Ms Antwort.

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
}
Pablo Jomer
quelle
1
In meinem Universum habe ich ein DSL implementiert, das zum Herunterladen einer Datei benötigt wurde. Es war praktisch, Exec () zu locken, bis ich auf einige Betriebssystemkompatibilitäts- und Chroot-Probleme stieß, die ich wirklich nicht konfigurieren wollte, weil es ein vernünftiges Sicherheitsmodell ist. Also ersetze U meine CURL durch diesen Code und bekomme eine 10-15x Leistungsverbesserung. DUH!
Richard
14

Die oben ausgewählte Antwort io.Copylautet genau das, was Sie benötigen. Wenn Sie jedoch an zusätzlichen Funktionen wie der Wiederaufnahme fehlerhafter Downloads, der automatischen Benennung von Dateien, der Überprüfung der Prüfsumme oder der Überwachung des Fortschritts mehrerer Downloads interessiert sind, lesen Sie das Grab- Paket.

Ryan Armstrong
quelle
Könnten Sie ein Code-Snippet hinzufügen, um sicherzustellen, dass die Informationen nicht verloren gehen, wenn der Link veraltet ist?
030
-6
  1. Hier ist ein Beispiel. https://github.com/thbar/golang-playground/blob/master/download-files.go

  2. Außerdem gebe ich dir einige Codes, die dir helfen könnten.

Code:

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)
        }
    }
}
TeeTracker
quelle
13
In diesem Beispiel wird der gesamte Inhalt mit dem in den Speicher eingelesen ioutil.ReadAll(). Das ist in Ordnung, solange Sie mit winzigen Dateien arbeiten.
eduncan911
13
@ eduncan911, aber es ist nicht in Ordnung für diese Frage, die explizit über große Dateien spricht und nicht alles in den Speicher saugen will.
Dave C
2
Genau richtig, deshalb habe ich das kommentiert - damit andere wissen, dass sie dies nicht für große Dateien verwenden sollen.
eduncan911
4
Dies ist keine harmlose Antwort und sollte tatsächlich entfernt werden. Die Verwendung von ReadAll in einem großen Codestapel ist ein latentes Problem, das darauf wartet, dass eine große Datei verwendet wird. Was passiert, ist, dass wenn ReadAll für große Dateien vorhanden ist, die Antwort normalerweise darin besteht, dem hohen Speicherverbrauch und den erhöhten AWS-Rechnungen zu folgen, bis etwas fehlschlägt. Zu dem Zeitpunkt, an dem das Problem entdeckt wird, sind die Rechnungen bereits hoch.
Rob