Go进行浏览器网页截图

前言 在本文中将介绍使用 golang 进行加载某个网站并进行截图。 chromedp 我们将使用 chromedp 通过浏览器驱动来加载网页并截图。 具体的步骤如下: 1 启动 chrome 浏览器 2 加载网页 (还可进行其他浏览器操作) 3 截图并保存 需要注意的是项目使用了 Chrome 的驱动,如果没有 Chrome 将不能顺利运行,需要运行 https://hub.docker.com/r/chromedp/headless-shell/ 来进行 或运行其他 版本的 Chrome 。 安装 go get -u github.com/chromedp/chromedp 示例 package main import ( "context" "io/ioutil" "log" "github.com/chromedp/chromedp" ) func main() { ctx, cancel := chromedp.NewContext(context.Background(), chromedp.WithDebugf(log.Printf)) defer cancel() url := "https://www.minigame.vip/" filename := "minigame.png" var imageBuf []byte // 捕获某个元素的截图 if err := chromedp.Run(ctx, elementScreenshot(`https://pkg.go.dev/`, `img.Homepage-logo`, &imageBuf)); err != nil { log.Fatal(err) } if err := ioutil.WriteFile("elementScreenshot.png", imageBuf, 0644); err != nil { log.Fatal(err) } if err := chromedp.Run(ctx, ScreenshotTasks(url, &imageBuf)); err != nil { log.Fatal(err) } if err := ioutil.WriteFile(filename, imageBuf, 0644); err != nil { log.Fatal(err) } } func elementScreenshot(urlstr, sel string, res *[]byte) chromedp.Tasks { return chromedp.Tasks{ chromedp.Navigate(urlstr), chromedp.Screenshot(sel, res, chromedp.NodeVisible), } } func ScreenshotTasks(url string, imageBuf *[]byte) chromedp.Tasks { return chromedp.Tasks{ chromedp.Navigate(url), chromedp.FullScreenshot(imageBuf, 90), } } 上面的例子分别是对网页中的单个元素进行截图和对网页全局截图。 ...

八月 20, 2022 · overstarry

gopsutil介绍

简介 psutil 是一个使用 Python 编写的跨平台平台进程和系统利用率监控库,gopsutil 就是 psutil 的 go 语言实现。 使用 安装 go get github.com/shirou/gopsutil/v3 CPU 获取cpu基本信息 func getCpuInfo() { cpuInfos, err := cpu.Info() if err != nil { fmt.Printf("get cpu info failed, err:%v", err) } for _, ci := range cpuInfos { fmt.Println(ci) } for { percent, _ := cpu.Percent(time.Second, false) fmt.Printf("cpu percent:%v\n", percent) } } {"cpu":0,"vendorId":"GenuineIntel","family":"205","model":"","stepping":0,"physicalId":"BFEBFBFF000406E3","coreId":"","cores":4,"modelName":"Intel(R) Core(TM) i5-6200U CPU @ 2.30GHz","mhz":2400,"cacheSize":0,"flags":[],"microcode":""} cpu percent:[40.625] cpu percent:[15] cpu percent:[9.615384615384617] cpu percent:[28.125] 获取cpu负载 func getCpuLoad() { info, err := load.Avg() if err != nil { panic(err) } fmt.Printf("%v\n", info) } 内存 获取内存 func getMemInfo() { memInfo, _ := mem.VirtualMemory() fmt.Printf("mem info:%v\n", memInfo) } mem info:{"total":8472920064,"available":1921372160,"used":6551547904,"usedPerce nt":77,"free":1921372160,"active":0,"inactive":0,"wired":0,"laundry":0,"buffers" :0,"cached":0,"writeBack":0,"dirty":0,"writeBackTmp":0,"shared":0,"slab":0,"srec laimable":0,"sunreclaim":0,"pageTables":0,"swapCached":0,"commitLimit":0,"commit tedAS":0,"highTotal":0,"highFree":0,"lowTotal":0,"lowFree":0,"swapTotal":0,"swap Free":0,"mapped":0,"vmallocTotal":0,"vmallocUsed":0,"vmallocChunk":0,"hugePagesT otal":0,"hugePagesFree":0,"hugePagesRsvd":0,"hugePagesSurp":0,"hugePageSize":0} 库的其他用法可以查看相应的官方文档。 ...

八月 13, 2022 · overstarry

gRPC健康探针

简介 gRPC 健康探针 grpc-health-probe 是社区提供的一个工具,用来检查 gRPC 服务的健康状态,此工具 是通过 gRPC 健康检查协议公开服务的状态。 使用 我在本地使用 kratos 创建一个使用 9000 端口的 gRPC 的服务。通过 grpc-health-probe 可以检查服务的健康状态。 grpc-health-probe -addr=localhost:9000 status: SERVING 可以看到此服务目前是健康的,不健康的服务将以非零退出代码退出。 grpc_health_probe -addr=localhost:9000 -connect-timeout 250ms -rpc-timeout 100ms failed to connect service at "localhost:9000": context deadline exceeded exit status 2 grpc_health_probe 发送了一个对 /grpc.health.v1.Health/Check 的RPC 请求。如果已 SERVING 状态作为响应,就会正常成功退出,否则会给出一个非零的退出。 Kubernetes 使用 grpc_health_probe 可用于 Kubernetes对 Pod 中运行的 gRPC 服务器进行健康检查。建议您使用Kubernetes exec探针并为您的 gRPC 服务器 pod 定义活跃度和/或就绪性检查。 您可以将静态编译grpc_health_probe的内容捆绑到您的容器映像中。 RUN GRPC_HEALTH_PROBE_VERSION=v0.3.1 && \ wget -qO/bin/grpc_health_probe https://github.com/grpc-ecosystem/grpc-health-probe/releases/download/${GRPC_HEALTH_PROBE_VERSION}/grpc_health_probe-linux-amd64 && \ chmod +x /bin/grpc_health_probe 在您的 Kubernetes Pod 规范清单中,为容器指定一个livenessProbe和/或 :readinessProbe ...

七月 31, 2022 · overstarry

Go协程闭包的问题

问题 最近在代码中遇到了这么一个问题,现在有一个循环,每一个循环中创建一个协程用来执行函数,我发现函数运行的结果却是大部分时候都是使用最后一个循环变量,不符合实际要求。 大概的代码如下: package main import ( "fmt" "net/http" ) func main() { for i := 0; i < 10; i++ { go func() { fmt.Println(i) }() } http.ListenAndServe(":8080", nil) } 运行: 10 10 10 10 10 10 10 10 10 10 多次运行,可以发现大部分情况都是将 i = 10 代入函数执行。 原因 那这是为什么呢? 这个就是函数闭包。协程运行的是一个闭包函数,其中使用了主线程的变量i 。看上去这和第一组几乎一样。但是在每个协程中,从进入匿名函数到调用Println将i的值复制入栈之间仍需要一小段时间运行,而这段时间内足以主线程完成全部10次循环。所以终于到将i的值复制入栈调用Println时,i已经成为10且不再变化了。 解决 那该怎么解决使代码如我们的需求运行呢? 我们只需将变量 i 复制进栈中即可,改动后的代码: package main import ( "fmt" "net/http" ) func main() { for i := 0; i < 10; i++ { go func(j int) { fmt.Println(j) }(i) } http.ListenAndServe(":8080", nil) } 结果: ...

七月 10, 2022 · overstarry

Go压缩png图像大小

起因 最近在处理一个需求,需要将 png 图像按比例调整图像尺寸,要求在保证图像质量的情况下尽量缩小文件大小。在本篇文章主要介绍我将 png 文件大小缩小使用的方法。 方法 这个需求缩小图像的尺寸很好解决,但缩小后的图像大小不尽人意,缩小的图像文件大小没有变化过多,甚至更大。我通过查询,发现了一种方法,就是先将图片转换为 jpeg 格式,再进行压缩后转换为 png 即可。 Jpeg的图片压缩是很好做的,因为jpeg这个协议本身就支持调整图片质量的。在golang中,我们只需要使用标准库的image/jpeg,将图片从二进制数据解码后,降低质量再编码为二进制数据即可实现压缩。而且质量和压缩比例相对而言还不错。 func compressImageResource(data []byte) []byte { imgSrc, _, err := image.Decode(bytes.NewReader(data)) if err != nil { return data } newImg := image.NewRGBA(imgSrc.Bounds()) draw.Draw(newImg, newImg.Bounds(), &image.Uniform{C: color.White}, image.Point{}, draw.Src) draw.Draw(newImg, newImg.Bounds(), imgSrc, imgSrc.Bounds().Min, draw.Over) buf := bytes.Buffer{} err = jpeg.Encode(&buf, newImg, &jpeg.Options{Quality: 40}) if err != nil { return data } if buf.Len() > len(data) { return data } return buf.Bytes() } 小结 本文主要介绍了将 png 图像大小缩小的一种简单方法,还有其他方法等待你们的发掘。

七月 2, 2022 · overstarry

Go处理zip解压乱码问题

问题 最近在某个场景中,需要使用 go 官方的 archive/zip 处理 zip 压缩包,在处理过程中,遇到了一个问题: go 解压后的文件存在文件名乱码的情况。 解决 我们知道在 go 中,字符串是以 UTF-8 编码的,所以有可能出现乱码的情况。 我们只要在处理压缩包中的文件时,通过判断 Flags 字段,如果 Flags 为 0 , 则使用本地编码,默认为 GBK。 如果为 1 , 则使用 UTF-8 编码。 我们只要在为 0 时对文件名进行处理就好。 代码: func Unzip(zipFile string, destDir string) error { zipReader, err := zip.OpenReader(zipFile) if err != nil { return err } defer zipReader.Close() var decodeName string for _, f := range zipReader.File { if f.Flags == 0{ i:= bytes.NewReader([]byte(f.Name)) decoder := transform.NewReader(i, simplifiedchinese.GB18030.NewDecoder()) content,_:= ioutil.ReadAll(decoder) decodeName = string(content) }else{ decodeName = f.Name } fpath := filepath.Join(destDir, decodeName) if f.FileInfo().IsDir() { os.MkdirAll(fpath, os.ModePerm) } else { if err = os.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil { return err } inFile, err := f.Open() if err != nil { return err } defer inFile.Close() outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode()) if err != nil { return err } defer outFile.Close() _, err = io.Copy(outFile, inFile) if err != nil { return err } } } return nil } 参考 https://thismj.cn/2019/02/14/qian-xi-zip-ge-shi/ https://chai2010.cn/post/golang/go-zip-utf8/ https://codereview.appspot.com/54360043/ https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT

六月 25, 2022 · overstarry

Singleflight介绍

缓存击穿 对于一些设置了过期时间的key,如果这些key可能会在某些时间点被超高并发地访问,是一种非常“热点”的数据。这个时候,需要考虑一个问题:缓存被“击穿”的问题,这个和缓存雪崩的区别在于这里针对某一key缓存,前者则是很多key。 缓存在某个时间点过期的时候,恰好在这个时间点对这个Key有大量的并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。 我在 go-redis/cache 中发现了库使用了 singleflight , 经过查阅资料,了解了 这个库的主要作用就是将一组相同的请求合并成一个请求,实际上只会去请求一次,然后对所有的请求返回相同的结果。 这样会大大降低数据库的压力。 singleflight 使用 函数签名 type Group struct { mu sync.Mutex // protects m m map[string]*call // lazily initialized } // Do 执行函数, 对同一个 key 多次调用的时候,在第一次调用没有执行完的时候 // 只会执行一次 fn 其他的调用会阻塞住等待这次调用返回 // v, err 是传入的 fn 的返回值 // shared 表示是否真正执行了 fn 返回的结果,还是返回的共享的结果 func (g *Group) Do(key string, fn func() (interface{}, error)) (v interface{}, err error, shared bool) // DoChan 和 Do 类似,只是 DoChan 返回一个 channel,也就是同步与异步的区别 func (g *Group) DoChan(key string, fn func() (interface{}, error)) <-chan Result // Forget 用于通知 Group 删除某个 key 这样后面继续这个 key 的调用的时候就不会在阻塞等待了 func (g *Group) Forget(key string) 示例 接下来我们来讲解一个简单的例子,我们来看看 singleflight 的使用方式,先来看一个简单的例子: ...

六月 18, 2022 · overstarry

Go并发Sync.Once解析

在 go 语言中我们可以使用 sync.Once 对象来实现函数方法只执行一次的功能。 简单代码示例 package main import ( "fmt" "sync" ) func main() { var ( o sync.Once wg sync.WaitGroup ) for i := 0; i < 10; i++ { wg.Add(1) go func(i int) { defer wg.Done() o.Do(func() { fmt.Printf("hello %d\n", i) }) }(i) } wg.Wait() } 输出: hello 9 不使用 Sync.Once 的结果如下: hello 9 hello 4 hello 0 hello 1 hello 2 hello 3 hello 6 hello 5 hello 7 hello 8 可以看到, 在使用 sync.Once 的情况下, 只执行一次函数。 ...

六月 3, 2022 · overstarry

Go截取视频某一帧图片

前言 最近遇到一个需求,需要截取视频的某一帧图片作为视频封面。我搜寻了相关资料,在 go 语言端常见的有两种做法,1)使用 opencv 的 go 绑定库,2)使用 ffmpeg 的 go 绑定库。 这里我打算使用第二种方法,使用 ffmpeg 的 go 绑定库。 ffmpeg 介绍 FFmpeg是一个开源免费跨平台的视频和音频流方案,属于自由软件,采用LGPL或GPL许可证(依据你选择的组件)。它提供了录制、转换以及流化音视频的完整解决方案。它包含了非常先进的音频/视频编解码库libavcodec,为了保证高可移植性和编解码质量,libavcodec里很多codec都是从头开发的。 FFmpeg在Linux平台下开发,但它同样也可以在其它操作系统环境中编译运行,包括Windows、Mac OS X等。 FFmpeg项目由以下几部分组成: FFMpeg 视频文件转换命令行工具,也支持经过实时电视卡抓取和编码成视频文件。 FFServer 基于HTTP(RTSP正在开发中)用于实时广播的多媒体服务器,也支持时间平移. FFplay 用SDL和FFmpeg库开发的一个简单的媒体播放器. libavcodec 一个包含了所有FFmpeg音视频编解码器的库.为了保证最优性能和高可复用性,大多数编解码器从头开发的. libavformat 一个包含了所有的普通音视格式的解析器和产生器的库 解决 1 安装 ffmpeg 浏览器访问 https://ffbinaries.com/downloads 根据你的系统安装 ffmpeg 2 安装 ffmpeg 的 go 绑定库 这里我使用了 github.com/u2takey/ffmpeg-go 这个项目,它是一个绑定库,可以在 go 语言端使用 ffmpeg 命令行工具。 3 编写代码 package main import ( "bytes" "fmt" "github.com/disintegration/imaging" ffmpeg "github.com/u2takey/ffmpeg-go" "log" "os" "strings" ) func GetSnapshot(videoPath, snapshotPath string, frameNum int) (snapshotName string, err error) { buf := bytes.NewBuffer(nil) err = ffmpeg.Input(videoPath). Filter("select", ffmpeg.Args{fmt.Sprintf("gte(n,%d)", frameNum)}). Output("pipe:", ffmpeg.KwArgs{"vframes": 1, "format": "image2", "vcodec": "mjpeg"}). WithOutput(buf, os.Stdout). Run() if err != nil { log.Fatal("生成缩略图失败:", err) return "", err } img, err := imaging.Decode(buf) if err != nil { log.Fatal("生成缩略图失败:", err) return "", err } err = imaging.Save(img, snapshotPath+".png") if err != nil { log.Fatal("生成缩略图失败:", err) return "", err } names := strings.Split(snapshotPath, "\\") snapshotName = names[len(names)-1] + ".png" return } func main() { _, err := GetSnapshot("./test.mp4", "test", 1) if err != nil { return } } 函数里 ffmpeg 相关的代码主要是提取视频的某一帧以mjpeg编码为图片,然后保存到本地。 ...

五月 28, 2022 · overstarry

Go errgroup

我们知道在 go 语言中很容易开启携程进行并发任务, 但是如何处理并发过程中的错误是非常棘手的,接下来我就来介绍 errgroup 的用法。 errgroup errgroup 包里主要是一个结构体和相应的方法,它可以让你在并发任务中处理错误。 type Group struct { // context 的 cancel 方法 cancel func() // 复用 WaitGroup wg sync.WaitGroup sem chan token // 用来保证只会接受一次错误 errOnce sync.Once // 保存第一个返回的错误 err error } func WithContext(ctx context.Context) (*Group, context.Context) func (g *Group) done() func (g *Group) Wait() error func (g *Group) Go(f func() error) func (g *Group) TryGo(f func() error) bool func (g *Group) SetLimit(n int) 通过 WithContext 可以创建一个可以取消的 Group,当然除此之外也可以零值的 Group 也可以直接使用,但是出错之后就不会取消其他的 goroutine 了. Go方法 传入一个函数参数,会启动一个 goroutine 处理。 Wait 类似 WaitGroup 的 Wait 方法,等待所有的 goroutine 结束后退出,返回的错误是一个出错的 err。 TryGo 是和 SetLimit 配套的,只有当 group 中的 goroutines 数量小于配置的数量时,才会在 goroutine 中调用函数。 TryGo 用来判断 goroutine 是否启动。 ...

五月 21, 2022 · overstarry