服务器上部署Chrome无头模式:安装、配置与命令行实战

发布时间:2026/10/11 11:29:26
服务器上部署Chrome无头模式:安装、配置与命令行实战 把Chrome装到一台没有图形界面的云服务器上再通过命令行把它跑起来这事听起来有点“杀鸡用牛刀”但实际用过的都知道这把牛刀在自动化场景里简直太趁手了。我最近在一台Alibaba Cloud Linux 3实例上完整跑通了Chrome的安装、配置和命令行调用过程中踩了不少坑尤其是依赖库、中文乱码、沙箱权限这几关每一个都能卡住人半小时起步。这篇文章就把整个流程和排查经验原原本本写下来打算在服务器上跑网页截图、做自动化测试或者搞数据采集的同学可以直接照着抄。1. 场景与需求拆解为什么非要在服务器上跑一个浏览器先聊一个很多人第一反应会问的问题服务器不是用来跑后端服务的吗为什么要装浏览器这个需求其实非常普遍。最常见的是网页自动化测试团队用Selenium或者Playwright做端到端用例需要一个真实的浏览器内核去执行JS、点击按钮、渲染页面。其次是数据采集现在大量网站的关键内容都是前端异步加载的用curl或者requests拿到的HTML里只有空壳div真正的数据都是JS跑完之后才填进去的这时候就必须有一个无头浏览器替你执行脚本、输出最终DOM。另外还有一类刚需是生成网页截图和PDF比如自动巡检页面视觉效果、批量生成报告封面、给客户演示线上页面缩略图都属于这个范畴。那为什么选Chrome而不是其他方案我在实际对比中感受很深。老牌的phantomjs早就停止维护了跑稍微现代一点的页面经常白屏wkhtmltopdf对CSS支持差页面稍微复杂点就崩Firefox headless虽然也能用但生态和文档远不如Chrome丰富。Chrome本身是浏览器市场份额最大的那一款你的目标用户在用Chrome打开网站那么用Chrome去渲染、截图、做自动化得到的结果就是最接近真实用户体验的。再加上Chromium内核被Playwright、Puppeteer这些自动化框架原生支持选它几乎不用纠结。还有个认知要扭转过来命令行运行Chrome不等于没有浏览器界面。无头模式只是不把窗口画出来引擎本身还是完整的HTML解析、CSS计算、JS执行、渲染管线统统在跑只是把最后画屏幕那一步跳过了。所以你在服务器上通过命令行调用Chrome和在桌面双击图标上网背后是同一套渲染逻辑这也是它能用于自动化测试的根本原因。这里顺便说一下我选择Alibaba Cloud Linux的原因。它是阿里云主推的服务器操作系统兼容RHEL/CentOS的软件包管理方式用dnf、yum装东西非常顺手而且针对云上场景做了很多内核层面的优化跑Chrome这类相对吃资源的应用稳定性表现比我在某些精简版容器镜像里折腾要省心得多。接下来的安装和调用步骤在Alibaba Cloud Linux 2、3上都能直接复用CentOS系的其他发行版差异也不大。2. 安装前的依赖准备这一步偷懒后面全是坑很多教程一上来就让你下载rpm包安装好像装完就万事大吉。实际上Chrome是一个GUI应用它在启动时会动态链接一大批图形相关的系统库。服务器上如果没有图形桌面这些库默认是不存在的装完Chrome之后一执行命令就报error while loading shared libraries那种感觉真的很崩溃。我在踩过好几次之后养成了一个习惯装Chrome之前先把依赖一次性铺满。2.1 确认系统版本与源配置先看清楚你手上的系统是什么版本防止装错东西。执行下面的命令cat /etc/os-release如果输出里写着Alibaba Cloud Linux (Aliyun Linux) 3或者类似字样说明是3代版本包管理走的是dnf。如果是2代版本yum和dnf都可以用但推荐统一用dnf因为它在依赖解析上比老yum更可靠。另外看一下软件源里有没有epel-release。Chrome本身依赖的若干库在默认源里可能不全提前把EPEL加上能省去很多“找不到包”的麻烦dnf install -y epel-release安装过程中如果提示没有这个包那就先执行dnf install -y dnf-plugins-core再试一次。有些云镜像默认裁剪了EPEL手动装一下就好。2.2 一次性装齐图形与GTK依赖这是我最想强调的一个环节。网上很多解决方案只会让你装一个libXss但实际跑起来你就会发现缺的库一个接一个libXcomposite、libXdamage、libgbm、libxshmfence……与其报一个装一个浪费时间不如一开始就揍一整组包。我自己在Alibaba Cloud Linux 3上验证过下面这组命令可以覆盖Chrome运行所需的绝大部分动态库dnf install -y \ libX11 libXcomposite libXdamage libXext libXrandr libXtst \ libXScrnSaver atk at-spi2-atk gtk3 pango gdk-pixbuf2 \ nss nspr alsa-lib libgbm libxshmfence逐个解释一下这些包是干什么的libX11那一串是X11图形协议的基础库Chrome虽然是自绘界面的但底层依然要通过这些库跟X Server交互gtk3、pango、gdk-pixbuf2是GUI工具包和文本渲染库Chrome的Linux版和GTK绑定得很深即使无头模式也要加载它们nss和nspr是网络安全服务库负责TLS/SSL连接缺少会导致访问HTTPS网站时直接崩libgbm是图形缓冲区管理库缺少它经常会在启动时报libgbm.so.1: cannot open shared object file。为什么不装Xvfb这个看需求。如果你只是用--headless模式那不需要虚拟显示器上面这些库就够了。但如果你的自动化脚本需要跑有头模式比如某些遮挡检测、特殊渲染场景必须真实出图那还得装一个xorg-x11-server-Xvfb用虚拟framebuffer充当显示器。我这里先不展开后面讲有头模式的时候再说。2.3 中文字体补丁不装必踩的隐藏坑接着装中文字体。很多人忽略这一步最后发现自己截图或者--dump-dom出来的页面里所有中文全是方块。Chrome本身只是一个浏览器它不附带任何字体页面里的中文要用系统里的中文字体渲染。最小化的云服务器镜像一般只带一些西文字体中文自然就是豆腐块。dnf install -y wqy-zenhei-fonts fc-cache -fvwqy-zenhei-fonts是文泉驿正黑中文字体里比较常用的一个。如果源里没有这个名字可以换fonts-noto-cjk试试或者dk-install其他中文字体包。装完一定要执行fc-cache -fv刷新字体缓存否则Chrome可能还是认不出来。这点特别提醒做自动化截图的朋友截图里中文是不是方框在验收截图脚本时往往是第一个被发现的问题提前装好字体后面会非常省心。3. 获取Chrome浏览器两种安装方式实测对比依赖准备好之后装Chrome就变得很轻松了。我在不同网络环境下试过两种方式各有适用场景都给你写清楚。3.1 方式A配置官方源后用dnf一键安装Chrome官方提供了Linux仓库配置好之后就能用dnf安装和后续更新体验非常顺滑。在/etc/yum.repos.d/下新建一个仓库文件vim /etc/yum.repos.d/google-chrome.repo写入如下内容[google-chrome] namegoogle-chrome baseurlhttps://dl.google.com/linux/chrome/rpm/stable/x86_64 enabled1 gpgcheck1 gpgkeyhttps://dl.google.com/linux/linux_signing_key.pub保存后执行安装dnf install -y google-chrome-stable装完先验证版本确保二进制和依赖都正常google-chrome --version能看到类似Google Chrome 126.0.6478.126的输出就说明安装成功了。这个方案的好处是后续升级方便dnf update google-chrome-stable就能更新到最新版。但有个我实际遇到的麻烦是某些云环境的网络到dl.google.com并不稳定dnf拉包可能超时。如果网络条件不好用方式B更靠谱。3.2 方式B下载rpm包后本地安装方式B是先把Chrome的rpm包下载到服务器上再本地安装。适合网络不稳定的场景也适合那种有内网yum仓库、想离线分发软件的团队。wget https://dl.google.com/linux/direct/google-chrome-stable_current_x86_64.rpm官网的rpm包是直链一般比仓库会更容易下载成功。如果下载慢也可以先在自己电脑上下载好再通过scp传到服务器scp google-chrome-stable_current_x86_64.rpm root你的服务器IP:/root/拿到包之后用dnf本地安装注意别用rpm -ivh硬装因为你还是要依赖网络源去解决依赖问题dnf install -y ./google-chrome-stable_current_x86_64.rpmdnf会先解析rpm包里声明的依赖再自动去已配置的源里把缺的包拉下来这样一次性就能把Chrome和它的依赖全部装好。两种方式选哪种我的建议很简单能连上官方源且不赶时间用方式A以后升级省事网络不稳、或者想在多台机器上复用安装包用方式B包文件可以存一份内网分发。3.3 版本锁定自动化任务里的隐形炸弹装好Chrome之后有一件事值得认真考虑要不要锁版本。Chrome为了安全会频繁自动更新这在桌面环境是加分项但在自动化环境里是个隐患。我遇到过真实案例脚本今天跑得好好的明天Chrome一升级某个--headless的渲染行为变了截图尺寸对不上、JS执行顺序有差异测试直接挂掉。排查半天才发现是浏览器偷偷升级了。如果你只是自己临时用不用管版本。但如果是需要稳定复现的自动化任务建议给Chrome加版本锁dnf install -y dnf-plugin-versionlock dnf versionlock add google-chrome-stable这样Chrome就会固定在当前版本不会被dnf update带跑。等哪天确认新版本行为没问题了再执行dnf versionlock delete google-chrome-stable解锁更新。4. 命令行运行Chrome的核心参数参数给对了事半功倍Chrome的参数非常多我刚开始也是一脸懵。用多了之后发现日常高频使用的其实就那么十几个把这几个组合搞明白就能覆盖绝大多数需求。4.1 最小可用命令先跑通再谈别的安装完成后如果你直接执行google-chrome大概率会看到这样的报错Missing X server or $DISPLAY这很正常服务器上根本没有图形界面。要验证Chrome能不能跑最直接的是用--headlessgoogle-chrome --headlessnew --no-sandbox --disable-gpu --dump-dom about:blankabout:blank是Chrome内置空白页输出几行空HTML或者提示信息就说明Chrome内核已经完全能运行了。这里先记住三个必带参数--headlessnew是启用新版无头模式--no-sandbox是在root用户下运行时的必需品--disable-gpu是关掉GPU硬件加速服务器上根本没有GPU不开反而能规避一堆兼容性问题。关于--headlessnew多说一句。Chrome的无头模式有过一次大重构老的--headless是独立实现用的是一套精简渲染路径新的--headlessnew则直接复用有头模式的完整渲染代码对JS、CSS、WebGL的支持更好。从Chrome 112左右开始新无头模式已经是默认但我建议还是显式写--headlessnew方便排查问题时一眼看出用的是什么模式也避免老版本Chrome行为不一致。4.2 三种高频输出方式DOM、截图、PDF无头模式最常用的三个能力是抓取渲染后HTML、截网页图片、生成PDF。逐个给你示范。抓取动态渲染后的DOM输出到标准输出google-chrome --headlessnew --no-sandbox --disable-gpu \ --dump-dom https://example.com直接执行会刷出一大屏HTML通常配合重定向存到文件里google-chrome --headlessnew --no-sandbox --disable-gpu \ --dump-dom https://example.com /tmp/page.html截图google-chrome --headlessnew --no-sandbox --disable-gpu \ --screenshot/root/snapshot.png --window-size1440,900 https://example.com--screenshot指定截图保存路径--window-size指定视口宽高。注意这里设置的是视口尺寸不是截图图片尺寸1440x900是模拟一个常见笔记本屏幕分辨率做页面预览图很合适。如果想要整页长截图需要自己调整视口高度Chrome官方headless不支持自动截完整页面这是一个已知的局限。生成PDFgoogle-chrome --headlessnew --no-sandbox --disable-gpu \ --print-to-pdf/root/page.pdf --no-pdf-header-footer https://example.com--no-pdf-header-footer是去掉PDF顶部标题和底部页码这个好像不起眼但实际交付给客户的时候带页眉页脚的PDF显得很不专业我第一次生成PDF就被这个细节坑过。4.3 稳定性与性能参数自动化任务不崩的关键除了前面三个必备参数还有几个参数在跑批量任务或者放进容器里时几乎必加。--disable-dev-shm-usage。这个参数对容器用户简直是救命稻草。Chrome在渲染时会用/dev/shm做共享内存很多容器默认/dev/shm只有64MBChrome一多开就直接崩溃报错信息还极其隐晦。加上这个参数强制Chrome改用/tmp目录就能避开这个小得可怜的共享内存限制。跑Docker容器、K8s Pod的同学这条必记。--user-data-dir/tmp/chrome-profile。Chrome默认会把用户数据写到$HOME/.config/google-chrome如果多个进程同时启动会因为配置文件锁而报process already running之类的错。给每个进程指定独立的--user-data-dir不仅隔离干净还能避免并发抢锁问题。批量任务里这个参数尤其重要。--virtual-time-budget10000。这个参数很有意思它告诉Chrome“你可以把虚拟时钟快进到10秒后”然后在这段虚拟时间内让页面里的JS尽情执行定时器、异步回调时间到了之后再把结果输出。它比--timeout强的地方在于Chrome会智能地跳过真实等待时间而不是傻等10秒。对那种“页面加载后3秒才发起请求渲染数据”的场景加这个参数比固定sleep靠谱得多。--blink-settingsimagesEnabledfalse。如果只是抓取DOM文本、做HTML结构分析不关心图片可以禁用图片加载减少带宽和内存消耗让任务跑得更快。像爬虫场景就特别适合。5. 实战无头模式抓取动态页面与批量截图参数说了一大堆不如来一个真实案例串一下。假设现在有一个页面它的核心数据是通过JavaScript异步请求接口后渲染出来的用curl抓HTML只能看到一个loading空壳。我们的目标是拿到它渲染后的完整DOM再顺手截一张图存档。5.1 使用virtual-time-budget等待异步渲染先拿curl验证一下问题curl -s https://example.com/api-driven-page | grep -i data-value | head -5如果这个页面恰好是异步渲染的大概率什么都搜不到。这时候换Chrome上场google-chrome --headlessnew --no-sandbox --disable-gpu \ --virtual-time-budget10000 \ --dump-dom https://example.com/api-driven-page /tmp/page_render.html执行完看一下文件大小和内容wc -c /tmp/page_render.html grep -o data-value[^]* /tmp/page_render.html | head -5这次就会看到数据了。--virtual-time-budget10000的意思是给页面10秒的虚拟执行时间Chrome会在这段时间里把setTimeout、setInterval、Promise这些异步任务尽量往前赶等虚拟时间耗尽后再输出DOM。这个特性实在太好用了它比固定sleep 5要高效也比容易误判的--timeout要准确很多。5.2 一个脚本完成批量截图继续延伸。如果我要巡检一批页面每个页面截图存档用Shell写个循环就非常方便。我实际跑过一个类似的需求监控公司对外宣传页面的视觉效果变化每天定时截一遍图扔到目录里。#!/bin/bash OUTPUT_DIR/root/page_shots mkdir -p $OUTPUT_DIR urls( https://example.com/home https://example.com/products https://example.com/about ) for url in ${urls[]}; do name$(echo $url | sed s#https://##; s#/#_#g) echo [$(date %F %T)] capturing $url google-chrome --headlessnew --no-sandbox --disable-gpu \ --disable-dev-shm-usage \ --user-data-dir/tmp/chrome_profile_${name} \ --screenshot${OUTPUT_DIR}/${name}.png \ --window-size1440,900 \ --virtual-time-budget8000 \ $url /dev/null 21 echo saved to ${OUTPUT_DIR}/${name}.png done这段脚本里三个细节值得注意一是--user-data-dir用了带页面名的独立目录避免多个Chrome实例并发时抢同一个配置文件锁。二是--virtual-time-budget给了8秒虚拟时间保证异步渲染基本都跑完。三是每次截图后没有急着删--user-data-dir目录这样Chrome的缓存还能留着同一页面下次再截会快不少。如果实在在乎磁盘占用可以在循环最后加一句rm -rf /tmp/chrome_profile_${name}。有同学问为什么不直接用Python Playwright或者Selenium我的答案是如果只是简单截图、抓DOM这种轻量任务命令行Chrome零依赖、零框架一个Shell脚本就能跑完没必要为此额外维护一套自动化框架。真正需要点击、填表单、断言元素这种复杂交互的时候再上Selenium或Playwright让它们内部调用同一个Chrome二进制这是更工程化的取舍。5.3 配合Python做更复杂的处理轻量任务用Shell复杂处理还是要交给Python。比如截图之后要做图像比对或者把DOM里的结构化数据解析出来Shell会写得很痛苦。此时用subprocess调用Chrome是成本最低的过渡方案import subprocess import sys url sys.argv[1] out_png sys.argv[2] cmd [ google-chrome, --headlessnew, --no-sandbox, --disable-gpu, --disable-dev-shm-usage, f--virtual-time-budget10000, f--screenshot{out_png}, --window-size1440,900, url, ] result subprocess.run(cmd, capture_outputTrue, textTrue, timeout60) if result.returncode ! 0: print(chrome failed:, result.stderr) else: print(screenshot saved:, out_png)这样就把Chrome当成了一个可编程的“渲染子进程”后面想接图像识别、数据解析都灵活得多。6. 常见问题与排查技巧实录写到这里我把实际操作中频率最高的几个问题整理成速查表方便你以后对号入座。6.1 root用户运行报沙箱错误报错信息长这样Running as root without --no-sandbox is not supported. See https://crbug.com/638180.原因很简单Chrome的沙箱依赖内核的一些安全机制在root用户下这些机制无法正常起作用所以Chrome直接拒绝运行。两条路图省事加--no-sandbox参数关掉沙箱。自动化环境里大家都在这么干注意只跑你信任的页面就行。更规范的做法创建专用普通用户来跑Chrome沙箱还是开着的安全性好很多useradd chrome-runner mkdir -p /home/chrome-runner chown chrome-runner:chrome-runner /home/chrome-runner su - chrome-runner -c google-chrome --headlessnew --dump-dom https://example.com6.2 运行时报缺少动态库典型报错google-chrome: error while loading shared libraries: libXss.so.1: cannot open shared object file排查方法很简单用ldd查看Chrome依赖哪些库缺失ldd /usr/bin/google-chrome | grep not found它会列出所有找不到的libxxxx.so然后逐个用dnf provides反查是哪个包提供的dnf provides \*/libXss.so.1查到包名后安装即可。不过偷懒一点的办法就是看我在第2节里那组依赖命令一次性把常见的GUI库全装了基本不会有漏网之鱼。6.3 截图中文全是方块这个问题我在第2节已经预警过这里再给一次排查思路。先确认系统里有没有中文字体fc-list :langzh如果输出为空说明系统里没有中文字体装一个再刷缓存dnf install -y wqy-zenhei-fonts fc-cache -fv如果输出里有字体文件但还是方块检查一下Chrome的渲染进程是否因为沙箱问题读不到字体很多时候加了--no-sandbox就莫名好了。这里逻辑是沙箱会限制进程访问某些系统资源根因不在这里但实际遇到的比例不低。6.4 Chrome进程挂死命令一直不退出无头模式偶尔会抽风比如某些页面有永不停止的动画、有WebSocket长连接Chrome就会一直挂着不退出。在Shell里还好控制给命令套一个timeout就行timeout 60 google-chrome --headlessnew --no-sandbox --dump-dom https://example.com /tmp/page.html60代表最多执行60秒超时直接杀掉。在Python的subprocess.run里也有timeout60参数一样的效果。另外配合--virtual-time-budget能从根上减少这种挂死因为它让Chrome的虚拟时钟到了就主动交差。6.5 定时任务里跑Chrome就是不行这个问题极其经典手动执行脚本一切正常放到crontab里就不工作日志也没有。原因大都是cron环境变量太精简PATH里只有/usr/bin:/bin而你的脚本里写的是google-chrome没写绝对路径导致cron找不到命令。解法在脚本开头显式声明export PATH/usr/bin:/bin CHROME_BIN/usr/bin/google-chrome或者在crontab里把环境变量写全。更重要的是Chrome的用户目录依赖$HOME而cron环境下$HOME可能是空的导致它无法读取配置。建议在脚本里指定--user-data-dir并且把输出日志写到一个固定文件里*/30 * * * * /root/scripts/chrome_capture.sh /root/scripts/chrome_capture.log 21“输出都进日志”这是我排查cron问题的最强心法。只要命令在cron里跑不灵先把stderr和stdout全部落盘问题马上就水落石出。6.6 容器里跑Chrome频繁崩溃如果你的应用本来就是容器化部署把Chrome放进容器之前务必检查这两个参数google-chrome --headlessnew --no-sandbox --disable-dev-shm-usage ...--no-sandbox是因为多数容器以root运行或者容器的seccomp配置不满足沙箱要求--disable-dev-shm-usage则是因为容器默认的/dev/shm太小。这两个参数我几乎在所有容器场景里都加上了实测下来稳定性能提升一大截。7. 收尾一些小体会这套东西我已经在自动生成页面巡检截图的脚本里跑了一个多月最深的感受是Chrome在服务器上并不娇气只要依赖装齐、参数给对、超时兜底做好稳定性完全不输桌面环境。反而最常出问题的永远是环境和版本这两个变量机器换了一个镜像、Chrome悄悄升了一版都可能让脚本突然翻车。所以我现在任何新机器上跑Chrome都会先固化一份依赖清单和参数模板全程复用基本没有意外。最后再分享一个小技巧把常用参数写进Shell函数省得每次长串命令行敲到怀疑人生。比如在~/.bashrc里加一个简短函数以后一条命令就能截图chrome_shot() { google-chrome --headlessnew --no-sandbox --disable-gpu \ --disable-dev-shm-usage --virtual-time-budget10000 \ --screenshot$2 --window-size${3:-1440,900} $1 }如果你也正准备在Alibaba Cloud Linux上跑Chrome先把开头那组依赖装全再把这几个关键参数抄下来真的能少走很多弯路。剩下的事情交给时间和踩坑就行。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询