目录

服务器部署 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 浏览器,有三大核心优势:

  1. 体积小chrome-headless-shell 不包含图形界面、GPU 渲染等组件,安装包和运行时占用的磁盘空间、内存都远小于完整的 Chrome 浏览器。
  2. 启动快:由于组件精简,它的启动速度比完整 Chrome 快得多,非常适合需要频繁启停浏览器的自动化任务。
  3. 资源占用低:在执行自动化任务时,CPU 和内存的占用也更低,这意味着你的服务器可以同时处理更多的任务。
  4. 专为自动化设计:它就是为了在 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

参考资料:

GitHub - chromedp/docker-headless-shell: Minimal container for Chrome’s headless shell, useful for automating / driving the web

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 实战:代码实现网页自动化指南

四、注意事项

生产环境最佳实践(稳定性 + 安全性 + 性能):

  1. 进程管理:在生产环境中,不建议使用 nohup。更好的方式是使用 systemd 或 supervisor 来管理 chrome-headless-shell 进程,确保它在意外退出时能自动重启。
  2. 端口安全:如果你的服务器暴露在公网,直接开放 9222 端口是不安全的。建议只允许来自特定 IP 的连接,或者通过 SSH 隧道、VPN 等方式访问。
  3. 资源限制:可以为 chrome-headless-shell 进程设置资源限制(如 CPU、内存),防止单个自动化任务耗尽服务器资源。
  4. 并发控制chromedp 支持在同一个浏览器实例中创建多个标签页(context)来并发执行任务。但也要注意控制并发数,避免给服务器带来过大压力。
  5. 会话隔离:对于不同的自动化任务,最好创建独立的浏览器上下文(chromedp.NewContext),以确保 Cookie、LocalStorage 等不会相互干扰。
  6. 定期更新:定期更新 chrome-headless-shell 和 chromedp 库,以获取最新的功能和安全补丁。

常见问题

Q1. 连接失败报错 “failed to connect to browser” 怎么办?

按以下步骤排查:

  1. 检查服务器 9222 端口是否开放:telnet 服务器IP 9222,不通则检查防火墙(ufw allow 9222 或 iptables -A INPUT -p tcp --dport 9222 -j ACCEPT);
  2. 确认 Chrome Headless Shell 已启动:docker ps(Docker 环境)或 systemctl status chrome-headless(原生环境);
  3. 检查连接地址是否正确:代码中 chromedp.RemoteAddr 需填写服务器 IP + 9222 端口,而非 localhost(跨机器访问时)。

Q2. 截图中文乱码或显示方块?

未安装中文字体,解决方案:

  1. Docker 环境:执行 2.1 节的中文字体安装命令;
  2. 原生 / 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 部署后容器频繁重启?

大概率是资源不足或共享内存配置错误:

  1. 增加资源 limits:将 memory 调整为 1Gi 以上,cpu 调整为 500m 以上;
  2. 检查共享内存挂载:确保 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/