Playwright无头模式默认禁用下载,需显式设置accept_downloads=True;监听download事件前须注册,推荐用expect_download()捕获并调用save_as()落盘重命名,避免path()临时路径失效。

Playwright 无头模式下文件下载默认被禁用
Playwright 在 headless=True 模式下默认不启用下载功能——不是“下载失败”,而是根本不会触发下载事件,page.on("download") 根本收不到任何回调。这是很多用户卡住的第一步:明明写了监听,却始终没进回调函数。
必须显式启用下载支持,并确保浏览器上下文允许下载:
- 创建
browser_context时传入accept_downloads=True - 不能直接在
browser.new_page()后设置,必须从上下文创建页面 - 无头模式(
headless=True)本身不影响重命名逻辑,但会掩盖下载未启用的问题
监听 download 事件后如何安全获取并重命名文件
Playwright 的 download 事件对象提供 download.path(),但它返回的是临时路径,且仅在下载完成后的短时间内有效(尤其在无头模式下,临时目录可能被快速清理)。直接 os.rename() 容易报错 FileNotFoundError。
正确做法是先调用 download.save_as(),它会阻塞等待下载完成,并把文件写入指定路径:
让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript 系统要求 macOS 14+ (Sonoma) Python 3.9+ 需要开启:辅助功能 + 屏幕录制权限
立即学习“Python免费学习笔记(深入)”;
async def handle_download(download):
# 确保下载完成,再保存到目标路径(含新文件名)
await download.save_as("/tmp/my_report.pdf")
print("已保存为新文件名")
<h1>绑定监听(注意:必须在点击触发下载前注册)</h1><p>page.on("download", handle_download)
await page.click("#export-btn")
-
download.save_as()是唯一推荐的落盘方式,download.path()仅作调试参考 - 监听必须在触发下载动作(如
click)之前注册,否则会错过事件 - 如果页面有多个下载按钮,建议用
page.expect_download()配合timeout更可靠
使用 expect_download() 更稳妥地捕获单次下载并重命名
当下载由明确操作(如点击按钮)触发时,page.expect_download() 比全局 on("download") 更精准,避免竞态和误匹配:
# 等待下一次下载开始(自动超时 30s)
async with page.expect_download(timeout=60000) as download_info:
await page.click("#export-csv")
<p>download = await download_info.value
await download.save_as("./data/export_20241105.csv")
-
expect_download()内部已处理下载就绪等待,无需手动轮询或 sleep -
timeout建议显式设大些(如 60000),避免网络慢时误抛TimeoutError - 文件名可动态拼接,比如基于当前时间、页面标题或 URL 参数,但注意路径合法性(避免
/、..注入)
无头模式下常见重命名失败原因
即使逻辑写对,仍可能因环境配置失败。最常忽略的三点:
- 启动浏览器时未加
--disable-features=DownloadRestrictions(某些 Chromium 版本在 headless 下限制下载) - 目标目录不存在,
save_as()不会自动创建父目录,需提前os.makedirs(os.path.dirname(path), exist_ok=True) - 并发下载场景下,多个
save_as()写同一路径会覆盖——重命名必须包含唯一标识(如uuid.uuid4()或毫秒时间戳)
真正麻烦的从来不是“怎么改名字”,而是“怎么确保那个文件真的落到了磁盘上、且没被删掉”。无头模式下临时文件生命周期极短,依赖 save_as() 是硬性要求,绕不开。

















