How to download a file using python in a 'smarter' way?

68

I need to download several files via http in Python.

The most obvious way to do it is just using urllib2:

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

But I'll have to deal with the URLs that are nasty in some way, say like this: http://server.com/!Run.aspx/someoddtext/somemore?id=121&m=pdf. When downloaded via the browser, the file has a human-readable name, ie. accounts.pdf.

Is there any way to handle that in python, so I don't need to know the file names and hardcode them into my script?

kender
quelle
3
Is the filename on the server relevant? Presumably these files have some meaning to you, so you ought to be able to name them yourself. If the names don't have meaning, come up with a random unique name yourself (uuids perhaps?)
Dominic Rodger
I'd love to have file names readable and meaningful. The issue is, the script will take URLs to download from from a text file, and the URLs will be added and removed by a non-technical person.
kender

Antworten:

41

Download scripts like that tend to push a header telling the user-agent what to name the file:

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

If you can grab that header, you can get the proper filename.

There's another thread that has a little bit of code to offer up for Content-Disposition-grabbing.

remotefile = urllib2.urlopen('http://example.com/somefile.zip')
remotefile.info()['Content-Disposition']
Oli
quelle
5
Nein, sie werden möglicherweise in eine einfache Datei umgeleitet. Aber wenn es wie bei den meisten Download-Skripten ist, fördern sie die inhaltliche Disposition. Auf jeden Fall überprüfen.
Oli
Wenn es mich zu einer einfachen Datei umleitet, ist es auch einfach, ich kann über remotefile.url auf die tatsächliche URL zugreifen, nicht wahr?
Kender
35

Basierend auf Kommentaren und @ Olis Antwort habe ich eine Lösung wie diese gefunden:

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()

Es übernimmt den Dateinamen von Content-Disposition; Wenn es nicht vorhanden ist, wird der Dateiname aus der URL verwendet (wenn eine Umleitung erfolgt ist, wird die endgültige URL berücksichtigt).

Kender
quelle
9
Ich fand das nützlich. Aber um größere Dateien herunterzuladen, ohne sie vollständig im Speicher zu speichern, musste ich dies herausfinden und Ihr 'r' nach 'f' kopieren: import shutil shutil.copyfileobj (r, f)
u0b34a0f6ae
4
Hat sehr gut funktioniert, aber ich würde urlsplit(url)[2]mit einem Aufruf an urllib.unquoteabschließen, sonst würden die Dateinamen prozentual codiert. Hier ist, wie ich es mache:return basename(urllib.unquote(urlsplit(url)[2]))
fjsj
23

Hier finden Sie eine pythonischere Lösung:

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()
lostlogic
quelle
1

2 Kender :

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

Es ist nicht sicher - der Webserver kann einen falsch formatierten Namen als ["file.ext] oder [file.ext '] übergeben oder sogar leer sein und localName [0] löst eine Ausnahme aus. Der richtige Code kann folgendermaßen aussehen:

localName = localName.replace('"', '').replace("'", "")
if localName == '':
    localName = SOME_DEFAULT_FILE_NAME
Denis Barmenkov
quelle
2
Noch besser: local_name.strip('\'"')- das wird sich nur von Anfang bis Ende ablösen und ist auch prägnanter.
Koniiiik
0

Verwenden von wget:

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

Verwenden von urlretrieve:

urllib.urlretrieve(url, custom_file_name)

urlretrieve erstellt auch die Verzeichnisstruktur, falls nicht vorhanden.

Jaydev
quelle