web-dev-qa-db-fra.com

Télécharger un fichier volumineux dans python avec des requêtes

Requests est une très belle bibliothèque. J'aimerais l'utiliser pour télécharger de gros fichiers (> 1 Go). Le problème est qu'il n'est pas possible de garder tout le fichier en mémoire, je dois le lire en morceaux. Et c'est un problème avec le code suivant

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 

Pour une raison quelconque, cela ne fonctionne pas de cette façon. Il charge toujours la réponse en mémoire avant de l'enregistrer dans un fichier.

UPDATE

Si vous avez besoin d’un petit client (Python 2.x /3.x) capable de télécharger de gros fichiers à partir de FTP, vous pouvez le trouver ici . Il prend en charge le multithreading et les reconnexions (il surveille les connexions) et il ajuste également les paramètres de socket pour la tâche de téléchargement.

324
Roman Podlinov

Avec le code de transmission en continu suivant, l'utilisation de la mémoire Python est restreinte quelle que soit la taille du fichier téléchargé:

def download_file(url):
    local_filename = url.split('/')[-1]
    # NOTE the stream=True parameter below
    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): 
                if chunk: # filter out keep-alive new chunks
                    f.write(chunk)
                    # f.flush()
    return local_filename

Notez que le nombre d'octets renvoyés à l'aide de iter_content n'est pas exactement le chunk_size; on s'attend à ce qu'il s'agisse d'un nombre aléatoire souvent beaucoup plus grand et différent à chaque itération.

Voir http://docs.python-requests.org/en/latest/user/advanced/#body-content-workflow pour plus de détails.

556
Roman Podlinov

C'est beaucoup plus facile si vous utilisez Response.raw et shutil.copyfileobj() :

_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
_

Cela diffuse le fichier sur le disque sans utiliser trop de mémoire et le code est simple.

193
John Zwinck

Ce n'est pas exactement ce que demandait OP, mais ... c'est ridiculement facile de le faire avec urllib:

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)

Ou de cette façon, si vous voulez l'enregistrer dans un fichier temporaire:

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)

J'ai regardé le processus:

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

Et j'ai vu le fichier grandir, mais l'utilisation de la mémoire est restée à 17 Mo. Est-ce que je manque quelque chose?

41
x-yuri

La taille de votre bloc est peut-être trop importante. Avez-vous essayé de le supprimer - peut-être 1024 octets à la fois? (vous pouvez aussi utiliser with pour ranger la syntaxe)

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 out keep-alive new chunks
                f.write(chunk)
    return 

Incidemment, comment en déduisez-vous que la réponse a été chargée en mémoire?

On dirait que python ne vide pas les données dans un fichier, mais d’autres questions SO , vous pouvez essayer f.flush() et os.fsync() pour forcer le fichier à écrire et libérer de la mémoire;

    with open(local_filename, 'wb') as f:
        for chunk in r.iter_content(chunk_size=1024): 
            if chunk: # filter out keep-alive new chunks
                f.write(chunk)
                f.flush()
                os.fsync(f.fileno())
40
danodonovan