【问题标题】:How to download a file using python in a 'smarter' way?如何以“更智能”的方式使用 python 下载文件?
【发布时间】:2010-10-26 02:51:30
【问题描述】:

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

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

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

但我将不得不处理以某种方式令人讨厌的 URL,例如:http://server.com/!Run.aspx/someoddtext/somemore?id=121&m=pdf。当通过浏览器下载时,该文件有一个人类可读的名称,即。 accounts.pdf

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

【问题讨论】:

  • 服务器上的文件名是否相关?大概这些文件对你有一些意义,所以你应该能够自己命名它们。如果名称没有意义,请自己想出一个随机的唯一名称(也许是 uuid?)
  • 我希望文件名具有可读性和意义。问题是,脚本将从文本文件中下载 URL,并且这些 URL 将由非技术人员添加和删除。

标签: python http download


【解决方案1】:

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

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

如果你能抓住那个标题,你就能得到正确的文件名。

another thread 有一些代码可以提供给Content-Disposition-grabbing。

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

【讨论】:

  • 不,他们可能会重定向到纯文件。但如果它像大多数下载脚本一样,他们正在推动内容处置。一定要检查。
  • 如果它把我重定向到一个普通文件也很容易,我可以通过 remotefile.url 访问实际的 url,不是吗?
【解决方案2】:

基于cmets和@Oli的anwser,我做了一个这样的解决方案:

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

【讨论】:

  • 我发现这很有用。但是要下载更大的文件,而不是将它们的全部内容存储在内存中,我必须找出这一点,将你的 'r' 复制到 'f': import shutil shutil.copyfileobj(r, f)
  • 工作得很好,但我会用对urllib.unquote 的调用来包装urlsplit(url)[2],否则文件名将被百分比编码。这是我的做法:return basename(urllib.unquote(urlsplit(url)[2]))
【解决方案3】:

结合以上大部分内容,这是一个更 Pythonic 的解决方案:

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

【讨论】:

    【解决方案4】:

    2肯德

    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
    

    【讨论】:

    • 更好的是:local_name.strip('\'"') -- 只会从头到尾剥离,也更简洁。
    【解决方案5】:

    使用wget

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

    使用 urlretrieve:

    urllib.urlretrieve(url, custom_file_name)
    

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

    【讨论】:

      【解决方案6】:

      您需要查看 'Content-Disposition' 标头,请参阅 kender 的解决方案。

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

      发布修改后的解决方案,可以指定输出文件夹:

      from os.path import basename
      import os
      from urllib.parse import urlsplit
      import urllib.request
      
      def url2name(url):
          return basename(urlsplit(url)[2])
      
      def download(url, out_path):
          localName = url2name(url)
          req = urllib.request.Request(url)
          r = urllib.request.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)
      
          localName = os.path.join(out_path, localName)
          f = open(localName, 'wb')
          f.write(r.read())
          f.close()
      
      download("https://example.com/demofile", '/home/username/tmp')
      

      我刚刚更新了kender for python3的答案

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2023-02-17
        • 2016-02-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-10-06
        相关资源
        最近更新 更多