urllib.request.urlretrieve 不是安全省心的默认选择,因其静默失败风险高、不支持超时参数、无法自定义请求头和认证、重定向与证书处理不透明,且 reporthook 中 total_size 常为-1导致进度难计算。

urllib.request.urlretrieve 确实能一行下载文件,但它不是“安全又省心”的默认选择——尤其在需要错误处理、进度反馈、重试或大文件场景下,容易静默失败或卡死。
urlretrieve 会静默吞掉哪些关键错误?
它默认把 HTTPError 和 URLError 都抛给上层,但如果你没包 try/except,程序直接崩;更麻烦的是,它对 302 重定向后的认证头、Cookie、甚至部分 HTTPS 证书验证行为不透明,有时下载到的是登录页 HTML 而非目标文件,却无提示。
- 常见现象:
urlretrieve返回的文件只有几 KB,打开是 “401 Unauthorized” 或 “Please log in” 的 HTML - 根本原因:它不复用你手动构造的
Request对象,也不继承已设置的OpenerDirector - 解决思路:真要可控,优先用
urlopen()+ 手动写文件,或换requests.get(..., stream=True)
如何用 urlretrieve 实现带超时和简单重试?
它的 reporthook 参数能传进度函数,但超时必须靠外层控制——因为 urlretrieve 本身没有 timeout 参数(Python 3.8+ 仍不支持)。
安全 Python 代码沙箱:在隔离的沙箱中执行任意 Python 代码,预装 requests、NumPy、pandas 库。返回 stdout、stderr。
- 必须用
socket.setdefaulttimeout(15)全局设超时,且需注意这会影响后续所有 socket 操作 - 重试只能靠循环 +
try/except,不能依赖内置机制 - 示例片段:
import socket
import urllib.request
import time
<p>socket.setdefaulttimeout(10)
for i in range(3):
try:
urllib.request.urlretrieve('<a href="https://www.php.cn/link/de829cc41d27f07c17771b5027167353">https://www.php.cn/link/de829cc41d27f07c17771b5027167353</a>', 'file.zip')
break
except (urllib.error.URLError, socket.timeout) as e:
if i == 2:
raise e
time.sleep(1)
reporthook 怎么写才真正反映下载进度?
它的三个参数是 (block_count, block_size, total_size),但 total_size 经常为 -1(服务器没发 Content-Length),此时无法算百分比。
立即学习“Python免费学习笔记(深入)”;
- 不要假设
total_size > 0,先判断再计算 -
block_count * block_size是当前已写入字节数,但可能因缓存/OS 行为滞后于真实网络接收量 - 轻量提示可这样写:
def show_progress(block_count, block_size, total_size):
if total_size < 0:
print(f'Downloaded {block_count * block_size} bytes...')
else:
percent = min(100, (block_count * block_size) / total_size * 100)
print(f'\rProgress: {percent:.1f}% ({block_count * block_size}/{total_size})', end='')
<p>urllib.request.urlretrieve(url, filename, reporthook=show_progress)
真正难处理的从来不是“怎么调用”,而是当 URL 带 query 参数签名、需要 bearer token、或响应是分块传输(chunked)时,urlretrieve 完全无法注入 header 或接管流——这种时候,别硬扛,换 requests。

















