2012-12-20 6 views
12

J'utilise Python Demandes bibliothèque pour télécharger un gros fichier, .: par exempledemandes avec plusieurs connexions

r = requests.get("http://bigfile.com/bigfile.bin") 
content = r.content 

Les grands téléchargements de fichiers à + - 30 Ko par seconde, ce qui est un peu lent. Chaque connexion au serveur bigfile est limitée, donc je voudrais faire plusieurs connexions.

Existe-t-il un moyen de créer plusieurs connexions en même temps pour télécharger un fichier?

Répondre

17

Vous pouvez utiliser l'en-tête HTTP Range pour récupérer une partie du fichier (already covered for python here).

Il suffit de commencer plusieurs threads et chercher différentes gammes avec chacun et vous avez terminé;)

def download(url,start): 
    req = urllib2.Request('http://www.python.org/') 
    req.headers['Range'] = 'bytes=%s-%s' % (start, start+chunk_size) 
    f = urllib2.urlopen(req) 
    parts[start] = f.read() 

threads = [] 
parts = {} 

# Initialize threads 
for i in range(0,10): 
    t = threading.Thread(target=download, i*chunk_size) 
    t.start() 
    threads.append(t) 

# Join threads back (order doesn't matter, you just want them all) 
for i in threads: 
    i.join() 

# Sort parts and you're done 
result = '' 
for i in range(0,10): 
    result += parts[i*chunk_size] 

Notez également que chaque serveur prend en charge la tête Range (et en particulier les serveurs avec php scripts responsible for data fetching ne respectent souvent pas la gestion des il).

6

Voici un script Python qui enregistre url donnée à un fichier et utilise plusieurs threads pour le télécharger:

#!/usr/bin/env python 
import sys 
from functools import partial 
from itertools import count, izip 
from multiprocessing.dummy import Pool # use threads 
from urllib2 import HTTPError, Request, urlopen 

def download_chunk(url, byterange): 
    req = Request(url, headers=dict(Range='bytes=%d-%d' % byterange)) 
    try: 
     return urlopen(req).read() 
    except HTTPError as e: 
     return b'' if e.code == 416 else None # treat range error as EOF 
    except EnvironmentError: 
     return None 

def main(): 
    url, filename = sys.argv[1:] 
    pool = Pool(4) # define number of concurrent connections 
    chunksize = 1 << 16 
    ranges = izip(count(0, chunksize), count(chunksize - 1, chunksize)) 
    with open(filename, 'wb') as file: 
     for s in pool.imap(partial(download_part, url), ranges): 
      if not s: 
       break # error or EOF 
      file.write(s) 
      if len(s) != chunksize: 
       break # EOF (servers with no Range support end up here) 

if __name__ == "__main__": 
    main() 

La fin du fichier est détecté si un serveur retourne corps vide, ou 416 code http, ou si la taille de la réponse n'est pas chunksize exactement.

Il prend en charge les serveurs qui ne comprennent pas Range en-tête (tout est téléchargé dans une seule demande dans ce cas, pour soutenir les fichiers volumineux, changer download_chunk() pour enregistrer dans un fichier temporaire et retourner le nom de fichier à lire dans le fil principal au lieu du contenu du fichier lui-même).

Il permet de modifier indépendamment le nombre de connexions simultanées (taille du pool) et le nombre d'octets requis dans une seule requête http.

Pour utiliser plusieurs processus au lieu de fils, changer l'importation:

from multiprocessing.pool import Pool # use processes (other code unchanged) 
1

Cette solution nécessite l'utilitaire linux nommé « aria2c », mais il a l'avantage de reprendre facilement les téléchargements.

Il suppose également que tous les fichiers que vous souhaitez télécharger sont répertoriés dans la liste du répertoire http pour l'emplacement MY_HTTP_LOC. J'ai testé ce script sur une instance du serveur http lighttpd/1.4.26. Mais, vous pouvez facilement modifier ce script afin qu'il fonctionne pour d'autres configurations.

#!/usr/bin/python 

import os 
import urllib 
import re 
import subprocess 

MY_HTTP_LOC = "http://AAA.BBB.CCC.DDD/" 

# retrieve webpage source code 
f = urllib.urlopen(MY_HTTP_LOC) 
page = f.read() 
f.close 

# extract relevant URL segments from source code 
rgxp = '(\<td\ class="n"\>\<a\ href=")([0-9a-zA-Z\(\)\-\_\.]+)(")' 
results = re.findall(rgxp,str(page)) 
files = [] 
for match in results: 
    files.append(match[1]) 

# download (using aria2c) files 
for afile in files: 
    if os.path.exists(afile) and not os.path.exists(afile+'.aria2'): 
     print 'Skipping already-retrieved file: ' + afile 
    else: 
     print 'Downloading file: ' + afile   
     subprocess.Popen(["aria2c", "-x", "16", "-s", "20", MY_HTTP_LOC+str(afile)]).wait() 
+1

Si vous avez vraiment besoin de télécharger 1 fichier, puis juste aller à un terminal sous Linux (si vous utilisez linux) et exécutez 'aria2c -x 16 -s 20 '. J'aime ma solution parce que chaque fois que j'ai plusieurs gros fichiers (ou même seulement un gros fichier) que j'ai besoin de télécharger, je les jette dans le répertoire 'MY_HTTP_LOC', puis j'exécute mon script. – synaptik