服务器部署 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?
在服务器环境中,我们通常追求的是轻量、稳定和高效。
- 体积小:
chrome-headless-shell不包含图形界面、GPU 渲染等组件,安装包和运行时占用的磁盘空间、内存都远小于完整的 Chrome 浏览器。 - 启动快:由于组件精简,它的启动速度比完整 Chrome 快得多,非常适合需要频繁启停浏览器的自动化任务。
- 资源占用低:在执行自动化任务时,CPU 和内存的占用也更低,这意味着你的服务器可以同时处理更多的任务。
- 专为自动化设计:它就是为了在 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-shell2. 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. 直接部署
参考资料:
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)
}四、生产环境中的最佳实践与注意事项
- 进程管理:在生产环境中,不建议使用
nohup。更好的方式是使用systemd或supervisor来管理chrome-headless-shell进程,确保它在意外退出时能自动重启。 - 端口安全:如果你的服务器暴露在公网,直接开放
9222端口是不安全的。建议只允许来自特定 IP 的连接,或者通过 SSH 隧道、VPN 等方式访问。 - 资源限制:可以为
chrome-headless-shell进程设置资源限制(如 CPU、内存),防止单个自动化任务耗尽服务器资源。 - 并发控制:
chromedp支持在同一个浏览器实例中创建多个标签页(context)来并发执行任务。但也要注意控制并发数,避免给服务器带来过大压力。 - 会话隔离:对于不同的自动化任务,最好创建独立的浏览器上下文(
chromedp.NewContext),以确保 Cookie、LocalStorage 等不会相互干扰。 - 定期更新:定期更新
chrome-headless-shell和chromedp库,以获取最新的功能和安全补丁。
这里还有存在一个问题还没解决,如果操作网页截图,画质会特别差,但是本地用的 google chromedp 就很高清,一直没有解决这个问题,有没有小伙伴遇到同样问题并且有解决方案的,欢迎评论区解答讨论!!!
版权声明
未经授权,禁止转载本文章。
如需转载请保留原文链接并注明出处。即视为默认获得授权。
未保留原文链接未注明出处或删除链接将视为侵权,必追究法律责任!
本文原文链接: https://fiveyoboy.com/articles/linux-go-chromedp/
备用原文链接: https://blog.fiveyoboy.com/articles/linux-go-chromedp/