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

最简单的方法: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}")
如何运行:
- 将上述代码保存为
.py文件(download_simple.py)。 - 在终端中运行
python download_simple.py。 - 你会发现当前目录下多了一个
python_logo.png文件。
优点:

- 代码简单:一行核心代码即可完成下载。
- 无需手动管理连接:
urlretrieve会自动处理请求、响应和连接关闭。
缺点:
- 内存消耗大:它会将整个文件内容读入内存,然后写入磁盘,如果下载一个几 GB 的大文件,你的程序可能会因为内存不足而崩溃。
- 无法显示下载进度:你无法知道下载进度,程序会一直等到下载完成。
- 功能有限:无法轻松处理复杂的请求头、认证或错误重试。
推荐的方法:流式下载(适合大文件和显示进度)
对于大文件,推荐使用“流式下载”(Streaming Download),这种方法不会一次性将整个文件加载到内存,而是分块读取和写入,极大地节省内存。
核心思路:
- 发送请求,获取响应对象。
- 以二进制模式(
'wb')打开一个本地文件。 - 循环读取响应对象的数据块(
chunk)。 - 将每个数据块写入文件,直到读取完毕。
代码示例(带进度条):

下面这个例子不仅实现了流式下载,还添加了一个简单的进度条,让你能看到下载进度。
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")
代码解析:
urllib.request.Request(url, headers=...):创建一个请求对象,可以添加请求头(如User-Agent),模拟浏览器访问,避免被一些网站拦截。with urllib.request.urlopen(req) as response::使用with语句确保连接在完成后被正确关闭。response.getheader('Content-Length'):从响应头中获取文件的总大小,用于计算进度百分比。response.read(chunk_size):每次只读取chunk_size大小的数据块,而不是整个文件。with open(filename, 'wb') as f::以二进制写入模式('wb')打开文件,确保图片、压缩包等二进制文件能被正确保存。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}")
代码解析:
HTTPPasswordMgrWithDefaultRealm:用于存储用户名和密码。add_password(...):将凭据添加到管理器,指定它们适用的 URL 范围。HTTPBasicAuthHandler:创建一个处理器,它会自动在遇到需要认证的请求时,使用password_mgr中的凭据。build_opener(...):构建一个更强大的“opener”对象,它包含了我们指定的处理器。install_opener(...):将这个 opener 安装为全局的,之后调用urllib.request.urlopen()时,会自动使用这个 opener 及其包含的处理器。
总结与对比
| 方法 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
urlretrieve |
快速下载小文件 | 代码极其简单 | 内存消耗大,无进度,功能有限 |
| 流式下载 | 推荐,尤其适合大文件,需要显示进度 | 内存效率高,可控性强,可扩展(如添加进度条) | 代码比 urlretrieve 稍复杂 |
| 高级 Opener | 需要处理认证、Cookie、自定义头等复杂请求 | 功能强大,灵活,能应对复杂的网络环境 | 代码最复杂,需要理解 opener 和 handler 机制 |
最佳实践建议:
- 对于一次性、小文件的快速下载,使用
urllib.request.urlretrieve。 - 对于任何需要认真对待的下载任务,特别是大文件,强烈推荐使用流式下载的方法,它在性能和用户体验上都更优。
- 如果你的程序需要与复杂的网站交互(如爬虫),学习如何构建和使用自定义 Opener 是必不可少的技能。
虽然 requests 库(第三方库)因其简洁的 API 在处理 HTTP 请求时更受欢迎,但 urllib 作为 Python 标准库,无需安装,并且在很多基础场景下已经足够强大,了解 urllib 是每个 Python 开发者的基本功。
