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

记一次阿里云 OSS 报错的解决

最近在使用 MinIO go-sdk 操作阿里云 OSS 时遇到了一个问题,特此记录下。 问题 在使用 sdk 调用 PutObject 方法时,发生了报错,具体报错如下: The request signature we calculated does not match the signature you provided. Check your key and signing method. 解决 可以看出报错的意思是签名不一致问题,我们首先检查 AccessKey ID 和 AccessKey Secret 是否正确,我发现同样创建的 client 在其它地方调用 方法时,没有报错,成功上传了文件对象。 后来查了多个相关的问题,发现可能是我填写的 objectName 的问题,我的 objectName 前带了 / 符号,导致在计算签名时变成 // ,使签名不一致。 我去掉前面的 ‘/’ 符号后,果然成功上传了。 总结 在编写相关操作 OSS 代码时,要注意 objectName 的格式,不要加上多余的符号。但奇怪的是原来上传至 MinIO 时没有报错,正常上传,可能是不同的存储有不同的签名规则吧。 参考 https://help.aliyun.com/document_detail/31951.htm?spm=a2c4g.11186623.0.0.3057jkGJjkGJjL https://blog.csdn.net/DCTANT/article/details/107917268 https://error-center.aliyun.com/status/search?Keyword=SignatureDoesNotMatch&source=PopGw";

八月 2, 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 的内容捆绑到您的容器映像中。 ...

七月 31, 2022 · overstarry

GRPC 健康检查

本篇文章我来介绍 gRPC 中的健康检查相关的知识。 简介 服务的健康检测一般是指的是检测服务是否正常运行: 是否存在,因为程序逻辑错误或者 OOM 等进程不存在; 存在是否可以正常的响应请求,尽管进程存在但可能因为请求量过大或者程序逻辑错误,导致服务 hang 住,无法正常对外请求。 gRPC 定义了一个健康检查协议,它允许使用了 gRPC 服务暴露服务器的状态,这样服务的消费者就能获得服务的健康状态。服务器的健康状态是由服务器是否响应非健康状态来确定的。 当服务器还没准备好处理 rpc 请求或者根本没有响应健康探针的请求时,就会发生这种情况。 健康服务定义 gRPC 健康检查协议基于 gRPC 定义了 API。下面就是服务定义: syntax = "proto3"; package grpc.health.v1; message HealthCheckRequest { string service = 1; } message HealthCheckResponse { enum ServingStatus { UNKNOWN = 0; SERVING = 1; NOT_SERVING = 2; } ServingStatus status = 1; } service Health { rpc Check(HealthCheckRequest) returns (HealthCheckResponse); rpc Watch(HealthCheckRequest) returns (stream HealthCheckResponse); } 客户端应该调用 Check 服务来判断服务是否正常运行,并且设置 deadline。客户端可以设置需要查询的服务名称,来返回对应的服务是否正常。 服务器应手动注册所有的服务并单个设置状态,包括空服务名称及其状态。对于收到的每一个请求,从注册表中查询服务的状态并返回。如果未找到,返回 NOT_FOUND 状态。服务器也可以根据实际的业务逻辑提供更为复杂的状态返回。 ...

七月 23, 2022 · overstarry

Go 发送邮件

介绍 电子邮件是一种用电子手段提供信息交换的通信方式,是互联网应用最广的服务。通过网络的电子邮件系统,用户可以以非常低廉的价格(不管发送到哪里,都只需负担网费)、非常快速的方式(几秒钟之内可以发送到世界上任何指定的目的地),与世界上任何一个角落的网络用户联系。 电子邮件在网络中的传输需要遵从一定的协议的,常用的电子邮件协议包括 SMTP,POP3,IMAP。电子邮件的创建和发送只涉及到 SMTP 协议,本文主要就是介绍 go 使用 SMTP 协议发生邮件。 使用 我们这里使用 https://github.com/jordan-wright/email 这个 pkg 来创建和发送邮件。pkg 的安装这里就不过多介绍了。 我们知道邮箱使用 SMTP/POP3/IMAP 等协议从邮件服务器上拉取邮件。邮件并不是直接发送到邮箱的,而是邮箱请求拉取的。所以,我们需要配置 SMTP/POP3/IMAP 服务器。从头搭建固然可行,而且也有现成的开源库,但是比较麻烦。这里我使用 gmail,配置流程就不过多介绍了 (ps: Google 的安全性还是比较强的,设置了好久。 代码: package main import ( "log" "net/smtp" "github.com/jordan-wright/email" ) func main() { e := email.NewEmail() e.From = "jm@gmail.com" e.To = []string{"1@qq.com"} e.Subject = "这是主题" e.Text = []byte("测试邮件发送") err := e.Send("smtp.gmail.com:587", smtp.PlainAuth("", "jm@gmail.com", "xx", "smtp.gmail.com")) if err != nil { log.Fatal(err) } } 过一会就顺利收到邮件了,有时候邮件会被当作垃圾邮件收入垃圾箱里,收不到时可以去垃圾箱看看。 参考 https://baike.baidu.com/item/%E7%94%B5%E5%AD%90%E9%82%AE%E4%BB%B6/111106

七月 16, 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