无头浏览器在 ARM Linux 上的部署排障:Playwright 可执行文件定位指南
无头浏览器在 ARM Linux 上的部署排障:Playwright 可执行文件定位指南
引子
把网页自动化脚本迁移到一台 aarch64 的 Linux 服务器上,第一次运行就报错:
playwright._impl._errors.Error: BrowserType.launch: Executable doesn't exist at
/root/.cache/ms-playwright/chromium_headless_shell-1243/chrome-headless-shell-linux-arm64/chrome-headless-shell
Playwright 明明装了,浏览器也明明下载了,为什么启动还是找不到?这篇文章复盘这次排障:问题不在网络、不在权限,而在版本目录与可执行文件的对应关系。
一、先看清 Playwright 到底装了哪些浏览器
报错信息很容易让人误以为"没下载",但先看缓存目录:
ls /root/.cache/ms-playwright/
# chromium-1243
# chromium_headless_shell-1234
两个目录都在,说明浏览器已经下载完成。真正的问题藏在目录名里的版本号:
- 完整版 Chromium 是
chromium-1243; - 无头专用外壳是
chromium_headless_shell-1234; - 而当前 Playwright 期望的是
chromium_headless_shell-1243。
期望路径与实装路径差了一个版本号,文件系统层面当然找不到。这类问题在环境被镜像重置、或 playwright install 与 Python 包版本不一致时非常常见。
二、两种修法,选哪个?
方案 A:补装缺失版本
python3 -m playwright install chromium
最"正"的做法,但在内网/受限网络下常常卡在下载,而且在 ARM 平台上部分版本并没有提供 headless_shell 的 arm64 构建,装完可能还是缺。
方案 B:显式指定已有的可执行文件(推荐)
既然完整版 Chromium 就在本地,直接告诉 Playwright 用哪个:
from playwright.sync_api import sync_playwright
CHROME = "/root/.cache/ms-playwright/chromium-1243/chrome-linux/chrome"
with sync_playwright() as p:
browser = p.chromium.launch(
headless=True,
executable_path=CHROME,
args=["--no-sandbox"],
)
验证一下这个文件确实可执行:
/root/.cache/ms-playwright/chromium-1243/chrome-linux/chrome --version
# Chromium 153.0.8010.12
只要版本打印出来,launch 就不会再去找那个不存在的 headless_shell-1243。
三、完整版和 headless_shell 到底差在哪?
这是很多人的盲区。headless_shell 是 Chromium 提供的一个"只跑无头"的精简可执行文件,体积小、启动快,Playwright 新版默认优先用它做 headless 模式。而完整版 chrome 带全套 UI 组件,用 headless=True 时内部仍走无头渲染路径。
对自动化脚本而言,二者的行为差异主要在:
- 字体和图形库依赖:headless_shell 更少;
- 某些扩展、GPU 特性:完整版支持更全;
- 启动耗时:headless_shell 略快;
- 版本目录独立:两者不一定同步更新,这正是本次踩坑的根源。
结论:生产脚本不要依赖默认路径,把 executable_path 写死并纳入配置,比"相信自动发现"可靠得多。
四、把路径变成可配置项
写死绝对路径的代价是换机器就要改代码。更好的做法是走环境变量 + 兜底探测:
import os, glob
def find_chrome():
env = os.environ.get("CHROME_PATH")
if env and os.path.exists(env):
return env
base = os.path.expanduser("~/.cache/ms-playwright")
for pat in ("chromium-*/chrome-linux/chrome",
"chromium_headless_shell-*/chrome-linux/headless_shell"):
hits = sorted(glob.glob(os.path.join(base, pat)))
if hits:
return hits[-1]
return None
这样在 CI 里用 CHROME_PATH 注入,本地则自动找最新版本,既可控又不失弹性。
五、同类报错的通用排查顺序
遇到 "Executable doesn't exist",按这个顺序查,基本能一次定位:
- 看缓存目录:
ls ~/.cache/ms-playwright/,确认是否真的下载了; - 比对版本号:报错路径里的版本号 vs 实际目录版本号;
- 检查平台子目录:ARM 是
chrome-linux还是chrome-headless-shell-linux-arm64; - 验证可执行权限:
--version能否跑通; - 显式指定:
executable_path兜底。
其中第 2 步和第 3 步最容易漏。很多人只看了"目录存在",没看目录里到底有没有对应架构的文件。
六、小结
这次排障的根因只有一句话:Playwright 期望的 headless_shell 版本与本地实际安装的版本不一致。但背后暴露的是自动化工程里一个共性问题——依赖"自动发现"的组件,在跨平台、跨镜像时会变得脆弱。
可落地的三条经验:
- 浏览器可执行文件路径显式配置,不要留给框架猜;
- 环境初始化脚本里加一步
chrome --version自检; - ARM 环境优先复用完整版 Chromium,避开 headless_shell 的构建缺失。
把这些写进部署清单,下次换机器就不会再被同一个报错拦住。
- 点赞
- 收藏
- 关注作者
评论(0)