服务器部署 Chrome Headless Shell 与 chromedp 自动化实战指南
之前的文章我们讲解了 go 使用第三方库 chromedp 实现网页自动化操作功能,具体 Go 语言 chromedp 实战:代码实现网页自动化指南
go 使用 chromedp 实现自动化网页操作的基础是需要电脑/服务器安装 google chrome 浏览器,
但是作为开发者,我们都知道,生产服务器一般都是 linux cmd 服务器,没有图形化界面,那么在生产环境,没有可视化 google chrome 浏览器的情况下,我们如何使用 go chromedp 呢?
今天我们来深入探讨一个在服务器端进行网页自动化的强大组合:Chrome Headless Shell 和 chromedp。
这篇文章将带你从零开始,完成环境部署到代码实现的全流程。
一、为什么选择 chrome-headless-shell ?
在 Linux 服务器(无图形化界面)中使用 chromedp 实现网页自动化,核心依赖 Chrome Headless Shell—— 它是 Chrome 浏览器的精简版,专为服务器端自动化设计,相比完整 Chrome 浏览器,有三大核心优势:
- 体积小:
chrome-headless-shell不包含图形界面、GPU 渲染等组件,安装包和运行时占用的磁盘空间、内存都远小于完整的 Chrome 浏览器。 - 启动快:由于组件精简,它的启动速度比完整 Chrome 快得多,非常适合需要频繁启停浏览器的自动化任务。
- 资源占用低:在执行自动化任务时,CPU 和内存的占用也更低,这意味着你的服务器可以同时处理更多的任务。
- 专为自动化设计:它就是为了在 CI/CD 流水线、服务器端自动化测试、数据爬取等场景下使用而优化的。
二、Linux 部署
以下主要讲解在 linux 服务上部署 chrome-headless-shell 的几种方式:
(一)docker 部署
Docker 部署无需配置系统依赖,一键启动,适合快速验证和中小规模场景。
# 启动容器
docker run -d -p 9222:9222 --rm --name headless-shell --shm-size 2G chromedp/headless-shell
# 默认是没有中文字体的,所以需要下载中文字体
docker exec -it headless-shell apt-get update
docker exec -it headless-shell apt-get install -y fonts-wqy-zenhei
docker exec -it headless-shell apt install xfonts-intl-chinese ttf-wqy-microhei xfonts-wqy
# 重启容器
docker restart headless-shell验证:
# 访问 9222 端口,查看 CDP 接口是否正常
curl http://localhost:9222/json/version返回包含 “Chrome/xx.xx.xxxx.xx” 的 JSON 数据,说明部署成功。
(二)k8s 部署
K8s 部署支持自动扩缩容、故障自愈,适合大规模自动化任务。以下是完整 YAML 配置(含命名空间、Deployment、Service):
chromeheadlessshell.yaml 如下:
## 创建命名空间
apiVersion: v1 # apiVersion 指定了 Kubernetes API 的版本
kind: Namespace # kind 是指创建的住院类型,这里 Namespace 是指创建命名空间
metadata:
name: chrome
--- # 分割线,表示创建多个资源,如上面是创建一个 chrome 的命名空间,下面是创建 Deployment 类型的 service-chrome-headless-shell 资源
apiVersion: apps/v1
kind: Deployment # 工作负载
metadata:
labels:
app: service-chrome-headless-shell
name: service-chrome-headless-shell
namespace: chrome #一定要写名称空间
spec:
progressDeadlineSeconds: 600 # 多久内pod没有创建成功则认为是失败的,默认600s
replicas: 1 # pod的数量
selector:
matchLabels:
app: service-chrome-headless-shell
strategy: # 部署策略
rollingUpdate: # 滚动更新策略
maxSurge: 50% # 可以同时创建的新 Pod 数量为当前 Pod 数量的 50%
maxUnavailable: 50% # 滚动更新期间 Service 最多可以从整体 Pod 数量中删除 50% 的 Pod
type: RollingUpdate # 使用滚动更新策略
template:
metadata:
labels:
app: service-chrome-headless-shell
spec:
containers:
- name: chrome-headless-shell
image: chromedp/headless-shell:latest
volumeMounts:
- name: shared-memory
mountPath: /dev/shm
- name: data-volume
mountPath: /home/DataVolume
imagePullPolicy: IfNotPresent # 镜像拉取策略,设置为 IfNotPresent,只有当本地不存在该镜像时,才会尝试拉取镜像
ports:
- containerPort: 9222 # 容器内部监听了 9222 端口
protocol: TCP
terminationMessagePath: /home/k8s/logs # 用于指定容器终止时的消息输出路径
terminationMessagePolicy: File # 用于指定容器终止时的消息输出路径,File 表示消息将以文件的形式存储。
resources: # 容器资源限制
requests:
cpu: 10m
memory: 20Mi
limits:
cpu: 100m
memory: 60Mi
# command:
# - "/bin/bash"
# - "-c"
# - |
# apt-get update
# apt-get install -y fonts-wqy-zenhei
# apt install -y xfonts-intl-chinese ttf-wqy-microhei xfonts-wqy
volumes:
- name: data-volume
hostPath:
path: /home/chromeheadlessshell/DataVolume
type: DirectoryOrCreate
- name: shared-memory
hostPath:
path: /home/chromeheadlessshell/SharedMemory
type: DirectoryOrCreate
dnsPolicy: ClusterFirst # 指定 Pod 中容器实例使用的 DNS 策略
restartPolicy: Always # 容器终止,k8s自动重启策略:1、Always,总是重启;2、Never,不重启;3、OnFailure,表示只有当容器实例以非零状态终止时(也就是失败),才应该重启该容器实例
terminationGracePeriodSeconds: 30 # 容器优雅关闭等待时间,30s 如果容器实例需要终止,Kubernetes 将会向容器实例发送终止信号,并等待 30 秒钟,以确保容器实例有足够的时间来完成正在处理的请求。如果在 30 秒钟之内容器实例还没有终止,Kubernetes 将强制终止该容器实例。
# 需要注意的是,terminationGracePeriodSeconds 参数只对支持优雅终止的容器实例有效,也就是说,只有在容器实例内部实现了优雅终止逻辑的情况下,terminationGracePeriodSeconds 参数才会生效
---
apiVersion: v1
kind: Service
metadata:
labels:
app: service-chrome-headless-shell
name: service-chrome-headless-shell
namespace: chrome
spec:
ports:
- name: http
port: 9222
protocol: TCP
targetPort: 9222
nodePort: 30894
selector:
app: service-chrome-headless-shell
sessionAffinity: None # 会话亲和性用于将同一客户端的请求路由到同一 Pod 上,以确保会话状态的一致性。如果将 sessionAffinity 设置为 None,则 Kubernetes 将采用轮询的方式将请求路由到不同的 Pod 上,不会考虑会话状态的一致性。
type: NodePort # 服务暴露类型:ClusterIP表示该 Service 不会暴露给集群外部的网络,只能在集群内部使用,NoderPort则可以暴露给外部
externalTrafficPolicy: Cluster⚠️:yaml 仅供参考,
部署命令:
# 应用配置文件
kubectl apply -f chrome-headless-shell.yaml
# 查看部署状态
kubectl get pods -n chrome-automation
# 查看服务地址(集群内部访问用)
kubectl get svc -n chrome-automation(三)直接部署
适合无 Docker/K8s 环境的场景,需手动安装依赖:
# Ubuntu/Debian 系统
apt-get update && apt-get install -y wget gnupg2 fonts-wqy-zenhei xfonts-intl-chinese
# CentOS/RHEL 系统
yum install -y wget fontconfig wqy-zenhei-fonts下载并安装 Chrome Headless Shell:
# 下载最新版本(可从 https://chromium.googlesource.com/chromium/src/+/refs/heads/main/docs/linux_headless.md 获取下载链接)
wget https://storage.googleapis.com/chromium-browser-snapshots/Linux_x64/1287428/chrome-headless-shell.zip
# 解压到 /usr/local/bin
unzip chrome-headless-shell.zip -d /usr/local/
ln -s /usr/local/chrome-headless-shell /usr/bin/chrome-headless-shell启动服务:
# 后台启动,开启远程调试端口 9222
nohup chrome-headless-shell --remote-debugging-port=9222 --disable-gpu --no-sandbox > /var/log/chrome-headless.log 2>&1 &设置开机自启(systemd):
# 创建服务文件
cat > /etc/systemd/system/chrome-headless.service << EOF
[Unit]
Description=Chrome Headless Shell Service
After=network.target
[Service]
Type=simple
ExecStart=/usr/bin/chrome-headless-shell --remote-debugging-port=9222 --disable-gpu --no-sandbox
Restart=always
RestartSec=5
User=root
LogFile=/var/log/chrome-headless.log
[Install]
WantedBy=multi-user.target
EOF
# 启用并启动服务
systemctl daemon-reload
systemctl enable chrome-headless.service
systemctl start chrome-headless.service参考资料:
examples/remote/main.go at master · chromedp/examples · GitHub
chromedp和headless-shellchromedp驱动chrome的常用方法 headless-shell - 掘金
三、chromedp 连接实战
部署完成后,通过 chromedp 连接 Chrome Headless Shell 的 9222 端口,实现自动化操作。
以下是完整代码。
package main
import (
"context"
"fmt"
"log"
"os"
"github.com/chromedp/chromedp"
)
func main() {
// --- 关键配置:连接到已启动的 chrome-headless-shell ---
// 我们将通过 Chrome DevTools Protocol 的默认端口 9222 进行连接
// 因此,我们需要在启动 chrome-headless-shell 时指定 --remote-debugging-port=9222
opts := append(chromedp.DefaultExecAllocatorOptions[:],
chromedp.NoFirstRun,
chromedp.NoDefaultBrowserCheck,
// 关键:不自动启动新的 Chrome 进程,而是连接到已存在的进程
chromedp.Flag("headless", true), // 虽然 shell 本身就是无头的,但显式设置更清晰
)
allocCtx, cancel := chromedp.NewExecAllocator(context.Background(), opts...)
defer cancel()
// 创建一个新的浏览器上下文
ctx, cancel := chromedp.NewContext(allocCtx, chromedp.WithLogf(log.Printf))
defer cancel()
// 设置一个超时时间
if err := chromedp.Run(ctx,
chromedp.Navigate(`https://www.example.com`),
chromedp.WaitVisible(`body`, chromedp.ByQuery),
); err != nil {
log.Fatalf("Failed to navigate: %v", err)
}
// 截图
var buf []byte
if err := chromedp.Run(ctx, chromedp.CaptureScreenshot(&buf)); err != nil {
log.Fatalf("Failed to capture screenshot: %v", err)
}
if err := os.WriteFile("example_screenshot.png", buf, 0644); err != nil {
log.Fatalf("Failed to save screenshot: %v", err)
}
fmt.Println("Screenshot saved as example_screenshot.png")
// 提取页面标题
var title string
if err := chromedp.Run(ctx, chromedp.Title(&title)); err != nil {
log.Fatalf("Failed to get title: %v", err)
}
fmt.Printf("Page title: %s\n", title)
}关于 go 如何使用 chromedp,请移步文章:Go 语言 chromedp 实战:代码实现网页自动化指南
四、注意事项
生产环境最佳实践(稳定性 + 安全性 + 性能):
- 进程管理:在生产环境中,不建议使用
nohup。更好的方式是使用systemd或supervisor来管理chrome-headless-shell进程,确保它在意外退出时能自动重启。 - 端口安全:如果你的服务器暴露在公网,直接开放
9222端口是不安全的。建议只允许来自特定 IP 的连接,或者通过 SSH 隧道、VPN 等方式访问。 - 资源限制:可以为
chrome-headless-shell进程设置资源限制(如 CPU、内存),防止单个自动化任务耗尽服务器资源。 - 并发控制:
chromedp支持在同一个浏览器实例中创建多个标签页(context)来并发执行任务。但也要注意控制并发数,避免给服务器带来过大压力。 - 会话隔离:对于不同的自动化任务,最好创建独立的浏览器上下文(
chromedp.NewContext),以确保 Cookie、LocalStorage 等不会相互干扰。 - 定期更新:定期更新
chrome-headless-shell和chromedp库,以获取最新的功能和安全补丁。
常见问题
Q1. 连接失败报错 “failed to connect to browser” 怎么办?
按以下步骤排查:
- 检查服务器 9222 端口是否开放:
telnet 服务器IP 9222,不通则检查防火墙(ufw allow 9222或iptables -A INPUT -p tcp --dport 9222 -j ACCEPT); - 确认 Chrome Headless Shell 已启动:
docker ps(Docker 环境)或systemctl status chrome-headless(原生环境); - 检查连接地址是否正确:代码中
chromedp.RemoteAddr需填写服务器 IP + 9222 端口,而非localhost(跨机器访问时)。
Q2. 截图中文乱码或显示方块?
未安装中文字体,解决方案:
- Docker 环境:执行 2.1 节的中文字体安装命令;
- 原生 / K8s 环境:将中文字体文件(.ttf 格式)放置到
/usr/share/fonts/custom,执行fc-cache -fv更新字体缓存。
Q3. 服务器部署后截图模糊,如何提升清晰度?
默认截图分辨率较低,可通过以下配置优化:
// 执行任务时添加设置窗口大小和设备像素比
chromedp.Run(ctx,
chromedp.SetViewport(1920, 1080), // 设置窗口大小为 1920x1080
chromedp.EmulateViewport(1920, 1080, chromedp.EmulateScale(2.0)), // 设备像素比 2.0(高清)
// 其他操作...
)⚠️:即便如此设置,画质依旧很差,似乎无解?有没有小伙伴有解决方法的,欢迎评论区分享交流!
Q4. K8s 部署后容器频繁重启?
大概率是资源不足或共享内存配置错误:
- 增加资源 limits:将 memory 调整为 1Gi 以上,cpu 调整为 500m 以上;
- 检查共享内存挂载:确保 Deployment 中配置了
emptyDir: { medium: Memory }(参考 2.2 节 YAML)。
Q5. 如何批量执行多个自动化任务?
使用 chromedp.NewPool 管理多个浏览器上下文,实现并发执行:
// 创建最多 5 个并发上下文的池
pool, err := chromedp.NewPool(5, chromedp.WithExecAllocator(opts...))
if err != nil {
log.Fatal(err)
}
defer pool.Close()
// 批量执行任务(示例:3 个截图任务)
for i, url := range []string{"url1", "url2", "url3"} {
pool.Run(ctx, func(ctx context.Context) error {
var buf []byte
if err := chromedp.Run(ctx, chromedp.Navigate(url), chromedp.CaptureScreenshot(&buf)); err != nil {
return err
}
return os.WriteFile(fmt.Sprintf("screenshot_%d.png", i), buf, 0644)
})
}总结
Chrome Headless Shell 是 Linux 服务器实现网页自动化的核心组件,结合 Docker/K8s 部署可快速落地,搭配 chromedp 能高效完成截图、爬取、表单提交等任务。
相比完整 Chrome 浏览器,它更轻量、更稳定、资源占用更低,是生产环境的最优选择。
实际部署中,需重点关注端口安全、资源限制、中文字体安装和进程管理四大关键点,再结合生产环境最佳实践,可确保自动化任务稳定运行。
本文提供的方案已在多个生产项目中验证,适用于中小规模到大规模的自动化场景。
如果大家在部署过程中遇到特殊问题(如复杂动态页面适配、高并发优化),或者有更好的实践技巧,欢迎在评论区交流~~~
版权声明
未经授权,禁止转载本文章。
如需转载请保留原文链接并注明出处。即视为默认获得授权。
未保留原文链接未注明出处或删除链接将视为侵权,必追究法律责任!
本文原文链接: https://fiveyoboy.com/articles/linux-go-chromedp/
备用原文链接: https://blog.fiveyoboy.com/articles/linux-go-chromedp/