如何以“更智能”的方式使用python下载文件?


68

我需要在Python中通过http下载多个文件。

最明显的方法就是使用urllib2:

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

但我不得不面对以某种方式是讨厌的网址,这样说:http://server.com/!Run.aspx/someoddtext/somemore?id=121&m=pdf。通过浏览器下载时,文件具有人类可读的名称,即。accounts.pdf

有什么办法可以在python中处理它,所以我不需要知道文件名并将其硬编码到脚本中?


3
服务器上的文件名是否相关?大概这些文件对您有一定的意义,因此您应该可以自己命名。如果名称没有意义,请自己想出一个随机的唯一名称(也许是uuid?)
Dominic Rodger,2009年

我希望文件名可读且有意义。问题是,该脚本将采用从文本文件下载的URL,并且这些URL将由非技术人员添加和删除。
kender

Answers:


41

像这样的下载脚本往往会推送一个标题,告诉用户代理该文件的名称:

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

如果可以获取该标头,则可以获取正确的文件名。

还有另一个线程可以提供一些代码来进行Content-Disposition抓取。

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

5
不,他们可能会重定向到纯文件。但是,就像大多数下载脚本一样,它们正在推动内容配置。一定要检查。
奥利(Oli)

如果将我重定向到一个普通文件也很容易,我可以通过remotefile.url访问实际的URL,不是吗?
kender

35

根据评论和@Oli的答案,我提出了这样的解决方案:

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

它从Content-Disposition获取文件名;如果不存在,则使用URL中的文件名(如果发生重定向,则将最终URL考虑在内)。


9
我发现这很有用。但是要下载更大的文件,而又不将它们的全部内容存储在内存中,我必须找出答案,将“ r”复制到“ f”:import shutil shutil.copyfileobj(r,f)
u0b34a0f6ae

4
工作得很好,但是我将urlsplit(url)[2]用调用包装urllib.unquote,否则文件名将被百分比编码。这是我的工作方式:return basename(urllib.unquote(urlsplit(url)[2]))
fjsj 2012年

23

结合上面的大部分内容,这是一个更加Python化的解决方案:

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]

这是不安全的-Web服务器可能会以[“ file.ext]或[file.ext']的形式传递错误的格式名称,甚至为空,并且localName [0]会引发异常。正确的代码如下所示:

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

2
更好的是:local_name.strip('\'"')-这只会从头到尾消失,也更加简洁。
koniiiik 2014年

0

使用wget

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

使用urlretrieve:

urllib.urlretrieve(url, custom_file_name)

如果不存在,urlretrieve也会创建目录结构。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.