2009-05-14 8 views
66

J'ai besoin de télécharger plusieurs fichiers via http en Python.Comment télécharger un fichier en utilisant python d'une manière «plus intelligente»?

La plus évidente façon de le faire est juste en utilisant urllib2:

import urllib2 
u = urllib2.urlopen('http://server.com/file.html') 
localFile = open('file.html', 'w') 
localFile.write(u.read()) 
localFile.close() 

Mais je vais devoir traiter les URL qui sont mauvais en quelque sorte, disent comme ceci: http://server.com/!Run.aspx/someoddtext/somemore?id=121&m=pdf. Lorsque téléchargé via le navigateur, le fichier a un nom lisible par l'homme, à savoir. accounts.pdf.

Y a-t-il un moyen de gérer cela en python, donc je n'ai pas besoin de connaître les noms de fichiers et de les coder en dur dans mon script?

+3

Le nom de fichier sur le serveur est-il pertinent? Vraisemblablement, ces fichiers ont un sens pour vous, donc vous devriez être capable de les nommer vous-même. Si les noms n'ont pas de sens, trouvez un nom unique au hasard (peut-être?) –

+0

J'aimerais avoir des noms de fichiers lisibles et significatifs. Le problème est que le script prendra des URL à télécharger à partir d'un fichier texte, et les URL seront ajoutées et supprimées par une personne non technique. – kender

Répondre

40

scripts de téléchargement comme qui ont tendance à pousser un en-tête indiquant à l'utilisateur agent quoi le nom du fichier:

Content-Disposition: attachment; filename="the filename.ext" 

Si vous pouvez saisir cet en-tête, vous pouvez obtenir le nom de fichier approprié.

Il ya another thread qui a un peu de code à offrir pour Content-Disposition -grabbing.

remotefile = urllib2.urlopen('http://example.com/somefile.zip') 
remotefile.info()['Content-Disposition'] 
+0

Mais ils pourraient ne pas. –

+5

Non, ils redirigent peut-être vers un fichier ordinaire. Mais si c'est comme la plupart des scripts de téléchargement, ils poussent la disposition du contenu. Bien sûr, vérifiez. – Oli

+0

Si elle me redirige vers un fichier simple c'est aussi facile, je peux accéder à l'URL réelle via remotefile.url, je ne peux pas? – kender

35

Sur la base des commentaires et @ la anwser Oli, je fait une solution comme ceci:

from os.path import basename 
from urlparse import urlsplit 

def url2name(url): 
    return basename(urlsplit(url)[2]) 

def download(url, localFileName = None): 
    localName = url2name(url) 
    req = urllib2.Request(url) 
    r = urllib2.urlopen(req) 
    if r.info().has_key('Content-Disposition'): 
     # If the response has Content-Disposition, we take file name from it 
     localName = r.info()['Content-Disposition'].split('filename=')[1] 
     if localName[0] == '"' or localName[0] == "'": 
      localName = localName[1:-1] 
    elif r.url != url: 
     # if we were redirected, the real file name we take from the final URL 
     localName = url2name(r.url) 
    if localFileName: 
     # we can force to save the file as specified name 
     localName = localFileName 
    f = open(localName, 'wb') 
    f.write(r.read()) 
    f.close() 

Il prend le nom de fichier de Content-Disposition; si ce n'est pas le cas, utilise le nom de fichier de l'URL (si la redirection a eu lieu, l'URL finale est prise en compte).

+9

Je l'ai trouvé utile. Mais pour télécharger des fichiers plus gros, sans les stocker en mémoire, je devais trouver ceci, en copiant votre 'r' à 'f': importer shutil shutil.copyfileobj (r, f) – u0b34a0f6ae

+0

@ kaizer.se génial, Merci de l'avoir signalé – kender

+4

Fonctionne très bien, mais j'emballe 'urlsplit (url) [2]' avec un appel à 'urllib.unquote', sinon les noms de fichiers seraient codés en pourcentage. Voici comment je suis en train de faire: 'return basename (urllib.unquote (urlsplit (url) [2]))' – fjsj

23

La combinaison beaucoup plus de ce qui précède, voici une solution plus pythonique:

import urllib2 
import shutil 
import urlparse 
import os 

def download(url, fileName=None): 
    def getFileName(url,openUrl): 
     if 'Content-Disposition' in openUrl.info(): 
      # If the response has Content-Disposition, try to get filename from it 
      cd = dict(map(
       lambda x: x.strip().split('=') if '=' in x else (x.strip(),''), 
       openUrl.info()['Content-Disposition'].split(';'))) 
      if 'filename' in cd: 
       filename = cd['filename'].strip("\"'") 
       if filename: return filename 
     # if no filename was found above, parse it out of the final URL. 
     return os.path.basename(urlparse.urlsplit(openUrl.url)[2]) 

    r = urllib2.urlopen(urllib2.Request(url)) 
    try: 
     fileName = fileName or getFileName(url,r) 
     with open(fileName, 'wb') as f: 
      shutil.copyfileobj(r,f) 
    finally: 
     r.close() 
1

2 Kender:

if localName[0] == '"' or localName[0] == "'": 
    localName = localName[1:-1] 

il est pas en sécurité - serveur web peut passer un mauvais nom formaté comme ["fichier.ext] ou [fichier.ext '] ou même être vide et nom local [0] va déclencher une exception Le code correct peut ressembler à ceci:

localName = localName.replace('"', '').replace("'", "") 
if localName == '': 
    localName = SOME_DEFAULT_FILE_NAME 
+2

Encore mieux: 'nom_local.strip ('\'" ') '- qui ne se déshabille que du début et fin et est également plus succinct. – koniiiik

0

En utilisant wget:

custom_file_name = "/custom/path/custom_name.ext" 
wget.download(url, custom_file_name) 

En utilisant urlretrieve:

urllib.urlretrieve(url, custom_file_name) 

urlretrieve crée aussi la structure de répertoire si existe pas.

Questions connexes