目录

服务器部署 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

在服务器环境中,我们通常追求的是轻量、稳定和高效

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

二、在 Linux 服务器上部署 chrome-headless-shell

1. 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

2. k8s 部署

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 chromeheadlessshell.yaml

3. 直接部署

参考资料:

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 - 掘金

三、代码操作案例

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)
}

四、生产环境中的最佳实践与注意事项

  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 库,以获取最新的功能和安全补丁。

这里还有存在一个问题还没解决,如果操作网页截图,画质会特别差,但是本地用的 google chromedp 就很高清,一直没有解决这个问题,有没有小伙伴遇到同样问题并且有解决方案的,欢迎评论区解答讨论!!!

版权声明

未经授权,禁止转载本文章。
如需转载请保留原文链接并注明出处。即视为默认获得授权。
未保留原文链接未注明出处或删除链接将视为侵权,必追究法律责任!

本文原文链接: https://fiveyoboy.com/articles/linux-go-chromedp/

备用原文链接: https://blog.fiveyoboy.com/articles/linux-go-chromedp/