杰瑞科技汇

Python3 urllib如何实现文件下载?

下面我将从最简单的方法开始,逐步介绍更健壮、更完整的下载方案,并解释关键点。

Python3 urllib如何实现文件下载?-图1
(图片来源网络,侵删)

最简单的方法:urllib.request.urlretrieve

这是最直接、最简单的方式,适合下载小文件,它将整个文件下载到本地,非常方便。

代码示例:

import urllib.request
# 目标文件的 URL
url = "https://www.python.org/static/img/python-logo.png"
# 本地保存路径和文件名
local_filename = "python_logo.png"
try:
    # urlretrieve(url, filename) 会下载文件并保存到指定路径
    urllib.request.urlretrieve(url, local_filename)
    print(f"文件已成功下载并保存为: {local_filename}")
except Exception as e:
    print(f"下载失败: {e}")

如何运行:

  1. 将上述代码保存为 .py 文件(download_simple.py)。
  2. 在终端中运行 python download_simple.py
  3. 你会发现当前目录下多了一个 python_logo.png 文件。

优点:

Python3 urllib如何实现文件下载?-图2
(图片来源网络,侵删)
  • 代码简单:一行核心代码即可完成下载。
  • 无需手动管理连接urlretrieve 会自动处理请求、响应和连接关闭。

缺点:

  • 内存消耗大:它会将整个文件内容读入内存,然后写入磁盘,如果下载一个几 GB 的大文件,你的程序可能会因为内存不足而崩溃。
  • 无法显示下载进度:你无法知道下载进度,程序会一直等到下载完成。
  • 功能有限:无法轻松处理复杂的请求头、认证或错误重试。

推荐的方法:流式下载(适合大文件和显示进度)

对于大文件,推荐使用“流式下载”(Streaming Download),这种方法不会一次性将整个文件加载到内存,而是分块读取和写入,极大地节省内存。

核心思路:

  1. 发送请求,获取响应对象。
  2. 以二进制模式('wb')打开一个本地文件。
  3. 循环读取响应对象的数据块(chunk)。
  4. 将每个数据块写入文件,直到读取完毕。

代码示例(带进度条):

Python3 urllib如何实现文件下载?-图3
(图片来源网络,侵删)

下面这个例子不仅实现了流式下载,还添加了一个简单的进度条,让你能看到下载进度。

import urllib.request
import os
def download_file_with_progress(url, filename):
    """
    带进度条的流式下载文件
    """
    try:
        # 发送请求,获取响应对象
        # 我们添加了 User-Agent,因为有些网站会拒绝没有 User-Agent 的请求
        req = urllib.request.Request(url, headers={'User-Agent': 'Mozilla/5.0'})
        with urllib.request.urlopen(req) as response:
            # 获取文件总大小(单位:字节)
            file_size = int(response.getheader('Content-Length', 0))
            # 获取文件名(如果未提供)
            if not filename:
                filename = os.path.basename(url)
            print(f"正在下载 {filename} (大小: {file_size / (1024*1024):.2f} MB)...")
            # 定义每次读取的块大小(8KB)
            chunk_size = 8192
            downloaded_size = 0
            # 以二进制写入模式打开文件
            with open(filename, 'wb') as f:
                # 循环读取数据块
                while True:
                    chunk = response.read(chunk_size)
                    if not chunk:  # 如果读取到空数据,表示下载完成
                        break
                    f.write(chunk)
                    downloaded_size += len(chunk)
                    # 打印进度
                    if file_size > 0:
                        percent = (downloaded_size / file_size) * 100
                        print(f"\r进度: {percent:.2f}%", end='', flush=True)
            # 下载完成后换行
            print(f"\n下载完成!文件已保存为: {filename}")
    except urllib.error.URLError as e:
        print(f"\nURL 错误: {e.reason}")
    except Exception as e:
        print(f"\n发生错误: {e}")
# --- 使用示例 ---
# 一个较大的测试文件
large_file_url = "https://www.python.org/ftp/python/3.11.0/Python-3.11.0.tgz"
download_file_with_progress(large_file_url, "Python-3.11.0.tgz")
# 一个较小的图片文件
image_url = "https://www.example.com/favicon.ico"
download_file_with_progress(image_url, "favicon.ico")

代码解析:

  1. urllib.request.Request(url, headers=...):创建一个请求对象,可以添加请求头(如 User-Agent),模拟浏览器访问,避免被一些网站拦截。
  2. with urllib.request.urlopen(req) as response::使用 with 语句确保连接在完成后被正确关闭。
  3. response.getheader('Content-Length'):从响应头中获取文件的总大小,用于计算进度百分比。
  4. response.read(chunk_size):每次只读取 chunk_size 大小的数据块,而不是整个文件。
  5. with open(filename, 'wb') as f::以二进制写入模式('wb')打开文件,确保图片、压缩包等二进制文件能被正确保存。
  6. print(f"\r进度: {percent:.2f}%", end='', flush=True)\r 使光标回到行首,end='' 阻止 print 换行,flush=True 强制立即输出,实现动态更新的进度条。

更高级的方法:处理重定向、认证和 Cookie

如果目标网站有重定向、需要登录(Basic/Digest 认证)或依赖 Cookie,你需要更精细地控制请求。

代码示例(处理重定向和 Basic 认证):

import urllib.request
import base64
# 需要认证的 URL (这里用 httpbin.org 作为示例)
auth_url = "https://httpbin.org/basic-auth/user/passwd"
# 你的用户名和密码
username = "user"
password = "passwd"
# 1. 创建密码管理器,处理 HTTP 认证
password_mgr = urllib.request.HTTPPasswordMgrWithDefaultRealm()
# 添加用户名和密码,以及它们适用的 URL
top_level_url = "https://httpbin.org/"
password_mgr.add_password(None, top_level_url, username, password)
# 2. 创建认证处理器
auth_handler = urllib.request.HTTPBasicAuthHandler(password_mgr)
# 3. 创建一个 opener,它会使用我们的认证处理器
opener = urllib.request.build_opener(auth_handler)
# 4. 安装 opener,之后所有的 urllib.request 调用都会使用它
urllib.request.install_opener(opener)
# 现在可以直接使用 urlopen 了,它会自动处理认证
try:
    with urllib.request.urlopen(auth_url) as response:
        # 读取并打印响应内容
        print("响应状态码:", response.status)
        print("响应头:", response.getheaders())
        content = response.read().decode('utf-8')
        print("响应内容:", content)
except urllib.error.HTTPError as e:
    print(f"HTTP 错误: {e.code} {e.reason}")
except Exception as e:
    print(f"发生错误: {e}")

代码解析:

  1. HTTPPasswordMgrWithDefaultRealm:用于存储用户名和密码。
  2. add_password(...):将凭据添加到管理器,指定它们适用的 URL 范围。
  3. HTTPBasicAuthHandler:创建一个处理器,它会自动在遇到需要认证的请求时,使用 password_mgr 中的凭据。
  4. build_opener(...):构建一个更强大的“opener”对象,它包含了我们指定的处理器。
  5. install_opener(...):将这个 opener 安装为全局的,之后调用 urllib.request.urlopen() 时,会自动使用这个 opener 及其包含的处理器。

总结与对比

方法 适用场景 优点 缺点
urlretrieve 快速下载小文件 代码极其简单 内存消耗大,无进度,功能有限
流式下载 推荐,尤其适合大文件,需要显示进度 内存效率高,可控性强,可扩展(如添加进度条) 代码比 urlretrieve 稍复杂
高级 Opener 需要处理认证、Cookie、自定义头等复杂请求 功能强大,灵活,能应对复杂的网络环境 代码最复杂,需要理解 openerhandler 机制

最佳实践建议:

  • 对于一次性、小文件的快速下载,使用 urllib.request.urlretrieve
  • 对于任何需要认真对待的下载任务,特别是大文件,强烈推荐使用流式下载的方法,它在性能和用户体验上都更优。
  • 如果你的程序需要与复杂的网站交互(如爬虫),学习如何构建和使用自定义 Opener 是必不可少的技能。

虽然 requests 库(第三方库)因其简洁的 API 在处理 HTTP 请求时更受欢迎,但 urllib 作为 Python 标准库,无需安装,并且在很多基础场景下已经足够强大,了解 urllib 是每个 Python 开发者的基本功。

分享:
扫描分享到社交APP
上一篇
下一篇