638 Stimmen

Laden Sie eine große Datei in Python mit Anfragen herunter

Requests ist eine wirklich tolle Bibliothek. Ich würde gerne verwenden, um große Dateien (>1GB) herunterzuladen. Das Problem ist, dass es nicht möglich ist, die gesamte Datei im Speicher zu behalten; ich muss sie in Abschnitten lesen. Und das ist ein Problem mit dem folgenden Code:

import requests

def DownloadFile(url)
    local_filename = url.split('/')[-1]
    r = requests.get(url)
    f = open(local_filename, 'wb')
    for chunk in r.iter_content(chunk_size=512 * 1024): 
        if chunk: # filter out keep-alive new chunks
            f.write(chunk)
    f.close()
    return 

Aus irgendeinem Grund funktioniert es auf diese Weise nicht; es lädt die Antwort immer noch in den Speicher, bevor sie in eine Datei gespeichert wird.

1000voto

Roman Podlinov Punkte 21233

Mit dem folgenden Streaming-Code wird der Python-Speicherverbrauch unabhängig von der Größe der heruntergeladenen Datei eingeschränkt:

def download_file(url):
    local_filename = url.split('/')[-1]
    # BEACHTEN Sie den Parameter stream=True unten
    with requests.get(url, stream=True) as r:
        r.raise_for_status()
        with open(local_filename, 'wb') as f:
            for chunk in r.iter_content(chunk_size=8192): 
                # Wenn Sie eine chunk-codierte Antwort haben, kommentieren Sie den Abschnitt aus und setzen Sie den chunk_size-Parameter auf None.
                #if chunk: 
                f.write(chunk)
    return local_filename

Beachten Sie, dass die Anzahl der über iter_content zurückgegebenen Bytes nicht genau der chunk_size entspricht; es wird erwartet, dass es eine zufällige Zahl ist, die oft viel größer ist und in jeder Iteration unterschiedlich sein soll.

Siehe body-content-workflow und Response.iter_content für weitere Informationen.

560voto

John Zwinck Punkte 221200

Es ist viel einfacher, wenn Sie Response.raw und shutil.copyfileobj() verwenden:

import requests
import shutil

def download_file(url):
    local_filename = url.split('/')[-1]
    with requests.get(url, stream=True) as r:
        with open(local_filename, 'wb') as f:
            shutil.copyfileobj(r.raw, f)

    return local_filename

Dies streamt die Datei auf die Festplatte, ohne übermäßig viel Speicher zu verbrauchen, und der Code ist einfach.

Hinweis: Laut der Dokumentation decodiert Response.raw nicht die Übertragungscodierungen gzip und deflate, daher müssen Sie dies manuell tun.

117voto

x-yuri Punkte 13561

Nicht genau das, was der OP gefragt hat, aber... es ist lächerlich einfach, das mit urllib zu tun:

from urllib.request import urlretrieve

url = 'http://mirror.pnl.gov/releases/16.04.2/ubuntu-16.04.2-desktop-amd64.iso'
dst = 'ubuntu-16.04.2-desktop-amd64.iso'
urlretrieve(url, dst)

Oder auf diese Weise, wenn Sie es in einer temporären Datei speichern möchten:

from urllib.request import urlopen
from shutil import copyfileobj
from tempfile import NamedTemporaryFile

url = 'http://mirror.pnl.gov/releases/16.04.2/ubuntu-16.04.2-desktop-amd64.iso'
with urlopen(url) as fsrc, NamedTemporaryFile(delete=False) as fdst:
    copyfileobj(fsrc, fdst)

Ich habe den Prozess beobachtet:

watch 'ps -p 18647 -o pid,ppid,pmem,rsz,vsz,comm,args; ls -al *.iso'

Und ich sah, wie die Datei wuchs, aber der Speicherverbrauch blieb bei 17 MB. Habe ich etwas übersehen?

48voto

danodonovan Punkte 18292

Die Größe Ihres Chunks könnte zu groß sein. Haben Sie versucht, sie zu reduzieren - vielleicht um 1024 Bytes auf einmal? (Außerdem könnten Sie with verwenden, um die Syntax aufzuräumen)

def DownloadFile(url):
    local_filename = url.split('/')[-1]
    r = requests.get(url)
    with open(local_filename, 'wb') as f:
        for chunk in r.iter_content(chunk_size=1024): 
            if chunk: # Filter neue Chunks heraus
                f.write(chunk)
    return 

Zufälligerweise, wie leiten Sie ab, dass die Antwort im Speicher geladen wurde?

Es scheint, als ob Python die Daten nicht in die Datei schreibt. Basierend auf anderen SO-Fragen könnten Sie f.flush() und os.fsync() versuchen, um den Schreibvorgang in die Datei zu erzwingen und den Speicher freizugeben;

    with open(local_filename, 'wb') as f:
        for chunk in r.iter_content(chunk_size=1024): 
            if chunk: # Filter neue Chunks heraus
                f.write(chunk)
                f.flush()
                os.fsync(f.fileno())

12voto

Verwenden Sie stattdessen das wget Modul von Python. Hier ist ein Auszug

import wget
wget.download(url)

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X