1. 为什么会有在Mac上装低版本Chrome这种需求
1.1 高版本Chrome带来的真实困扰
先说一个我亲身踩过的坑。去年接手一个老项目,前端用的是一套基于NPAPI接口的打印控件,客户那边一直跑在Chrome 87上。我本地图省事装了最新版Chrome,结果打开页面直接白屏,控制台报了一堆Uncaught TypeError,排查了半天才发现是新版Chrome彻底移除了某些老接口。这种时候你没法让客户升级控件,只能自己把浏览器降下来。
类似场景其实挺多的。做自动化测试的朋友经常需要锁定特定大版本,因为某些WebDriver和Chrome的版本是强绑定的;做前端兼容性验证的,要同时开好几个不同版本的Chrome对比渲染效果;还有一些企业内部系统,证书链或者加密算法只兼容到某个版本之前。这些需求归结起来就一句话:我需要一个指定版本的Chrome,而且要和系统里现有的Chrome共存,不能互相干扰。
Mac上这件事比Windows麻烦一点。Windows下你下载一个离线安装包,装到不同目录就行。Mac的Chrome默认装在/Applications/Google Chrome.app,直接覆盖安装会把原来的版本干掉,而且macOS的Gatekeeper对来源不明的应用有额外限制。所以核心思路就变成了:把指定版本的Chrome装到一个独立目录,用命令行直接启动它,完全绕开系统默认的Chrome。
1.2 本文适合哪些人看
这篇内容主要面向三类人。第一类是开发和测试工程师,需要多版本Chrome共存做兼容性验证;第二类是运维或者技术支持,要帮别人复现某个特定版本下的问题;第三类就是单纯好奇、想折腾一下的Mac用户。不管你之前有没有用过命令行,只要跟着步骤走,都能搞定。我会把每一步为什么这么做讲清楚,避免你照抄命令却不知道出了问题时该往哪查。
需要提前说明的是,本文所有操作都基于官方公开的Chrome历史版本分发渠道,不涉及任何非官方修改版。整个方案的核心工具就是**终端(Terminal)**加几个基础命令,不需要装额外的包管理器,也不需要动系统配置。
2. 整体方案设计与思路拆解
2.1 为什么不用Homebrew装Chrome
很多人第一反应是brew install google-chrome,但这条路走不通。Homebrew的cask里只有最新稳定版的Chrome,它不提供历史版本。你就算指定版本号,cask也会告诉你找不到对应的artifact。而且Homebrew装完的Chrome还是在/Applications下,和手动装的没区别,一样会覆盖旧版本。
那有人会想,我能不能用brew install --cask google-chrome@87这种写法?不行,Chrome从来没有官方维护过带版本号的cask formula。所以Homebrew这条路直接放弃,别在这上面浪费时间。我见过有教程教你改cask文件里的版本号和下载地址,理论上可行,但每次Chrome更新你都得手动改,维护成本太高,不推荐。
2.2 核心思路:独立目录加命令行启动
真正靠谱的方案分三步走。第一步,从官方渠道拿到指定版本的离线安装包,注意是.dmg格式的;第二步,把dmg挂载后,把里面的Google Chrome.app整个拷贝到一个自定义目录,比如~/ChromeVersions/87.0.4280.88/;第三步,用命令行直接调用这个目录下的可执行文件启动浏览器。
这个方案的好处非常明显。首先,它完全不碰系统默认的Chrome,你原来的浏览器该咋用还咋用。其次,每个版本放在独立目录里,想装多少个版本都行,互不干扰。最后,命令行启动可以带各种参数,比如指定用户数据目录、禁用自动更新、开启远程调试端口,这些在兼容性测试里非常有用。
注意:拷贝出来的Chrome.app首次启动时,macOS可能会提示"无法验证开发者"。这是因为应用被移动过,签名校验路径变了。解决办法是在"系统设置-隐私与安全性"里点"仍要打开",或者用命令行加
--no-sandbox参数临时绕过。但后者有安全风险,仅建议在测试环境使用。
2.3 版本号怎么选才不踩坑
Chrome的版本号格式是主版本.次版本.构建号.补丁号,比如87.0.4280.88。你选版本的时候,重点看主版本号就行,因为大的接口变动基本都发生在主版本升级时。比如Chrome 88移除了Flash支持,Chrome 92改了SameSite Cookie的默认行为,Chrome 109是最后一个支持Windows 7的版本。如果你不确定该选哪个,先去查一下你要用的功能是在哪个版本被废弃的,然后选它前一个主版本。
另外要注意,Chrome的离线安装包分稳定版、测试版、开发版和金丝雀版。做兼容性测试一般用稳定版就够了。下载地址的规律是https://www.slimjet.com/chrome/google-chrome-old-version.php这类第三方归档站,但更稳妥的方式是直接查Chrome的版本历史页面,找到对应版本的官方下载链接。不过官方链接有时候会失效,所以建议下载后把dmg文件备份一份,免得以后找不到。
3. 核心细节解析与实操要点
3.1 下载指定版本dmg的完整流程
打开浏览器,访问Chrome的版本历史查询页面。这个页面会列出所有历史版本号,你找到自己想要的那个版本,点进去会看到各个平台的下载链接。Mac版要选macOS对应的x64或arm64架构,M1及以后的芯片选arm64,Intel芯片选x64。选错了也能跑,但会通过Rosetta转译,性能差一截。
下载下来的文件名一般长这样:googlechrome87.0.4280.88.dmg。文件大小在150MB到200MB之间,如果明显偏小,说明下载不完整,重新下。下载完成后,双击挂载,你会看到一个虚拟磁盘,里面就一个Google Chrome.app。先别急着拖到Applications里,那样就覆盖了。
提示:如果你找不到官方历史版本页面,可以搜"Chrome旧版本下载"找一些归档站点。但一定要核对文件的SHA256校验值,确保没被篡改。校验命令是
shasum -a 256 文件名.dmg,和官方公布的对比。
3.2 创建独立目录并拷贝应用
打开终端,先建一个专门放多版本Chrome的目录。我习惯放在用户目录下,这样不需要sudo权限:
mkdir -p ~/ChromeVersions/87.0.4280.88然后把挂载的dmg里的应用拷进去。假设dmg挂载在/Volumes/Google Chrome,执行:
cp -R "/Volumes/Google Chrome/Google Chrome.app" ~/ChromeVersions/87.0.4280.88/拷贝完成后,先卸载dmg:
hdiutil detach "/Volumes/Google Chrome"这时候你去~/ChromeVersions/87.0.4280.88/下面看,应该有一个完整的Google Chrome.app。右键点它选"显示包内容",进入Contents/MacOS/,里面有个叫Google Chrome的可执行文件,这就是我们后面要调用的。
3.3 命令行启动与常用参数
启动这个独立版本的Chrome,直接用完整路径调用可执行文件:
~/ChromeVersions/87.0.4280.88/Google\ Chrome.app/Contents/MacOS/Google\ Chrome注意路径里有空格,所以要用反斜杠转义,或者给整个路径加引号。第一次启动可能会弹安全提示,去系统设置里放行就行。
实际使用中,我强烈建议加上--user-data-dir参数,给每个版本指定独立的用户数据目录。这样不同版本的Chrome不会共用书签、Cookie和扩展,避免互相污染:
~/ChromeVersions/87.0.4280.88/Google\ Chrome.app/Contents/MacOS/Google\ Chrome \ --user-data-dir="$HOME/ChromeVersions/87.0.4280.88/userdata"如果你要做自动化测试,再加一个--remote-debugging-port=9222,这样就能用CDP协议操控这个浏览器了。注意端口别和系统里其他服务冲突,9222是常用端口,如果被占了就换9223。
3.4 防止Chrome自动更新
低版本Chrome最怕的就是它自己偷偷更新到最新版。虽然我们拷贝出来的副本不会自动更新,但保险起见,还是把更新相关的组件禁掉。进入Google Chrome.app/Contents/Frameworks/,找到GoogleSoftwareUpdate.framework或者类似的更新框架,把它重命名或者删掉。更简单的办法是在启动参数里加--disable-background-networking,这个参数会阻止Chrome在后台做各种网络请求,包括更新检查。
另外,如果你发现启动后版本号还是变了,检查一下是不是系统里默认的Chrome在捣乱。用--version参数确认当前启动的版本:
~/ChromeVersions/87.0.4280.88/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --version输出应该显示Google Chrome 87.0.4280.88,如果显示的是别的版本,说明你调用的路径不对。
4. 实操过程与核心环节实现
4.1 完整操作步骤复盘
我把整个流程从头到尾走一遍,你照着做就行。假设我要装Chrome 109.0.5414.119这个版本,这是最后一个支持某些老系统的稳定版。
第一步,下载dmg。找到对应版本的下载链接,下载到~/Downloads/目录。下载完成后,在终端里校验文件完整性:
cd ~/Downloads shasum -a 256 googlechrome109.0.5414.119.dmg把输出的哈希值和官方公布的对比,一致就继续。
第二步,挂载dmg:
hdiutil attach googlechrome109.0.5414.119.dmg挂载成功后,终端会显示挂载点,通常是/Volumes/Google Chrome。
第三步,创建目标目录并拷贝:
mkdir -p ~/ChromeVersions/109.0.5414.119 cp -R "/Volumes/Google Chrome/Google Chrome.app" ~/ChromeVersions/109.0.5414.119/第四步,卸载dmg:
hdiutil detach "/Volumes/Google Chrome"第五步,创建启动脚本。每次都敲一长串路径太麻烦,我习惯在~/ChromeVersions/下建一个launch-chrome.sh脚本:
#!/bin/bash VERSION="109.0.5414.119" APP_PATH="$HOME/ChromeVersions/$VERSION/Google Chrome.app/Contents/MacOS/Google Chrome" USER_DATA="$HOME/ChromeVersions/$VERSION/userdata" "$APP_PATH" --user-data-dir="$USER_DATA" --disable-background-networking "$@"给脚本加执行权限:
chmod +x ~/ChromeVersions/launch-chrome.sh以后要启动这个版本,直接运行~/ChromeVersions/launch-chrome.sh就行。想传额外参数,比如打开某个网址,直接在后面加:~/ChromeVersions/launch-chrome.sh https://example.com。
4.2 多版本共存的管理技巧
当你装了三个以上版本时,手动记路径就有点乱了。我的做法是在~/ChromeVersions/下建一个versions.txt,每行记录一个版本号和对应的用途备注:
87.0.4280.88 - 老打印控件测试 109.0.5414.119 - 兼容性基线 120.0.6099.109 - 当前主力测试版然后写一个通用的启动脚本,接受版本号作为参数:
#!/bin/bash VERSION="$1" shift APP_PATH="$HOME/ChromeVersions/$VERSION/Google Chrome.app/Contents/MacOS/Google Chrome" USER_DATA="$HOME/ChromeVersions/$VERSION/userdata" if [ ! -f "$APP_PATH" ]; then echo "版本 $VERSION 未安装" exit 1 fi "$APP_PATH" --user-data-dir="$USER_DATA" --disable-background-networking "$@"保存为~/ChromeVersions/run.sh,以后启动就变成:
~/ChromeVersions/run.sh 87.0.4280.88这样管理起来清爽多了。你还可以把这个脚本加到shell的alias里,比如在~/.zshrc里加一行alias chrome87='~/ChromeVersions/run.sh 87.0.4280.88',以后直接敲chrome87就能启动。
4.3 用户数据目录的隔离与清理
每个版本用独立的--user-data-dir有个副作用,就是磁盘占用会越来越大。Chrome的用户数据目录里缓存、历史记录、扩展数据加起来,用久了能到几个GB。定期清理是必要的。
清理的时候不要直接删整个userdata目录,那样书签和密码也没了。正确的做法是进到userdata/Default/下面,删掉Cache、Code Cache、GPUCache这几个缓存目录就行。命令如下:
rm -rf ~/ChromeVersions/87.0.4280.88/userdata/Default/Cache rm -rf ~/ChromeVersions/87.0.4280.88/userdata/Default/Code\ Cache rm -rf ~/ChromeVersions/87.0.4280.88/userdata/Default/GPUCache如果你确定这个版本只是临时测试用,不需要保留任何数据,那直接删整个版本目录更省事:
rm -rf ~/ChromeVersions/87.0.4280.88注意:删除前确认没有正在运行的该版本Chrome进程,否则可能删不干净。用
ps aux | grep "ChromeVersions"查一下,有的话先kill掉。
5. 常见问题与排查技巧实录
5.1 启动报错"应用已损坏"怎么办
这是macOS Gatekeeper的拦截。因为拷贝出来的应用签名路径变了,系统认为它可能被篡改。解决办法有两个。一是右键点应用选"打开",然后在弹窗里点"打开",这样系统会记住你的选择。二是用命令行移除隔离属性:
xattr -dr com.apple.quarantine ~/ChromeVersions/87.0.4280.88/Google\ Chrome.app执行完再启动就不会弹提示了。但要注意,这个命令会移除所有扩展属性,包括一些安全标记,所以只对你信任的来源使用。
5.2 版本号显示不对或启动的是系统Chrome
这种情况通常是因为你调用的路径不对,或者系统里有多个Chrome可执行文件。先用which google-chrome查一下系统PATH里有没有别的Chrome。然后用绝对路径启动,并在启动后访问chrome://version/页面确认版本号。如果页面显示的版本和你预期的不一样,说明启动的不是你拷贝的那个副本。
还有一种可能是LaunchServices缓存了旧的应用路径。可以重建缓存:
/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister -kill -r -domain local -domain system -domain user这个命令会重置应用的注册信息,执行后可能需要重新登录。
5.3 扩展程序无法安装或加载
低版本Chrome对扩展的签名要求和新版不一样。如果你从Chrome应用商店装扩展,可能会提示"无法从该网站添加应用、扩展程序和用户脚本"。这是因为旧版本的安全策略和新版商店不兼容。解决办法是下载扩展的.crx文件,然后拖到chrome://extensions/页面里安装。但要注意,Chrome 75之后对.crx的安装限制更严了,可能需要开启开发者模式。
如果扩展还是装不上,检查一下启动参数里有没有--disable-extensions,有的话去掉。另外,--user-data-dir指定的目录如果权限不对,扩展也装不进去。确保目录属主是你自己:
chown -R $(whoami) ~/ChromeVersions/87.0.4280.88/userdata5.4 常见问题速查表
| 问题现象 | 可能原因 | 解决方法 |
|---|---|---|
| 启动提示"应用已损坏" | Gatekeeper拦截 | 右键打开或执行xattr -dr com.apple.quarantine |
| 版本号显示不对 | 调用了系统Chrome | 用绝对路径启动,检查chrome://version/ |
| 扩展无法安装 | 旧版商店不兼容 | 下载crx文件拖入安装,开启开发者模式 |
| 启动后闪退 | 用户数据目录权限问题 | 执行chown -R $(whoami)修复权限 |
| 自动更新到新版 | 更新组件未禁用 | 加--disable-background-networking参数 |
| 磁盘占用过大 | 缓存未清理 | 删除Default/Cache等缓存目录 |
5.5 几个我踩过的坑
第一个坑是路径里的空格。Google Chrome.app这个名字里有空格,在命令行里必须转义或者加引号,否则shell会把Google和Chrome.app当成两个参数。我一开始没注意,报了一堆"command not found",排查了半天。
第二个坑是dmg挂载后没卸载就直接删文件。macOS会认为文件还在使用中,删不干净。一定要先hdiutil detach再操作。
第三个坑是多个版本共用同一个--user-data-dir。我图省事让87和109共用一个数据目录,结果87的书签被109升级了格式,再切回87就打不开了。所以每个版本必须独立数据目录,这是铁律。
第四个坑是忘了关自动更新。有一次测试到一半,Chrome自己更新了,版本号变了,测试结果全废。后来我在启动脚本里强制加了--disable-background-networking,再也没出过这个问题。
6. 进阶用法与自动化集成
6.1 用脚本批量管理多版本
当你需要频繁切换版本时,可以写一个更完善的bash脚本,支持列出已安装版本、启动指定版本、清理缓存等操作。下面是我自己在用的版本:
#!/bin/bash BASE_DIR="$HOME/ChromeVersions" case "$1" in list) echo "已安装版本:" ls -1 "$BASE_DIR" | grep -E '^[0-9]' ;; run) VERSION="$2" shift 2 APP="$BASE_DIR/$VERSION/Google Chrome.app/Contents/MacOS/Google Chrome" if [ ! -f "$APP" ]; then echo "版本 $VERSION 未安装" exit 1 fi "$APP" --user-data-dir="$BASE_DIR/$VERSION/userdata" \ --disable-background-networking "$@" ;; clean) VERSION="$2" rm -rf "$BASE_DIR/$VERSION/userdata/Default/Cache" rm -rf "$BASE_DIR/$VERSION/userdata/Default/Code Cache" echo "版本 $VERSION 缓存已清理" ;; *) echo "用法: $0 {list|run|clean} [版本号]" ;; esac保存为~/ChromeVersions/manage.sh,加执行权限。以后管理就三条命令:./manage.sh list看有哪些版本,./manage.sh run 87.0.4280.88启动指定版本,./manage.sh clean 87.0.4280.88清理缓存。
6.2 结合自动化测试工具使用
如果你用Selenium或者Puppeteer做自动化测试,可以直接指定这个独立Chrome的可执行文件路径。以Python的Selenium为例:
from selenium import webdriver from selenium.webdriver.chrome.service import Service from selenium.webdriver.chrome.options import Options chrome_path = "/Users/你的用户名/ChromeVersions/87.0.4280.88/Google Chrome.app/Contents/MacOS/Google Chrome" options = Options() options.binary_location = chrome_path options.add_argument("--user-data-dir=/Users/你的用户名/ChromeVersions/87.0.4280.88/userdata") options.add_argument("--disable-background-networking") driver = webdriver.Chrome(options=options) driver.get("https://example.com")注意,ChromeDriver的版本必须和Chrome主版本号匹配。Chrome 87就要用ChromeDriver 87,去ChromeDriver的官方归档页面下载对应版本。版本不匹配会报session not created错误。
6.3 把启动命令做成macOS应用快捷方式
如果你不想每次都开终端,可以用Automator把启动脚本包装成一个应用。打开Automator,新建"应用程序",添加"运行Shell脚本"动作,把启动命令粘进去,保存为Chrome 87.app。以后双击这个应用就能启动指定版本的Chrome,跟正常应用一样。
不过要注意,Automator包装的应用启动时,工作目录可能不是你预期的位置,所以脚本里所有路径都要用绝对路径,不能有相对路径。
7. 一些个人体会和后续扩展思路
这套方案我用了快两年,从Chrome 70多一直管到120多,中间换过M1的Mac,也换过Intel的,整体很稳定。最大的感受就是,独立目录加独立用户数据目录这个组合是核心,只要这两点做到位,多版本共存基本不会出问题。剩下的就是些细节,比如路径转义、权限修复、缓存清理,踩过一次坑就记住了。
后续如果你需要更自动化的管理,可以考虑用Docker跑Chrome。Linux版的Chrome在容器里跑,通过VNC或者远程调试端口访问,这样连macOS的Gatekeeper都绕过了。不过那个方案复杂度更高,适合团队共享测试环境用,个人本地测试还是本文这套轻量方案更顺手。
另外,如果你只是偶尔需要某个低版本,不想长期维护,可以用完就删。把dmg文件留好,需要的时候重新拷一份出来,几分钟的事。没必要为了一个临时需求搞一套复杂的版本管理系统。工具是为人服务的,怎么省事怎么来。