- Github源码 - chromedp
- 文章项目Gitee源码
chromedp
是一个更快
、更简单
的Golang库用于调用支持Chrome DevTools协议
的浏览器,同时不需要额外的依赖(例如Selenium
和PhantomJS
)
Chrome和Golang都与Google有着相当密切的关系,而Chrome DevTools其实就是Chrome浏览器按下F12之后的控制终端
对于Golang
开发来说,使用chromedp
更为便捷,因为它仅仅需要Chrome
浏览器而并不需要依赖ChromeDriver
,省去了依赖问题,有助于自动化的构建和多平台架构的迁移
chromedp
进行二维码登陆makiuchi-d/gozxing
解码 skip2/ go-qrcode
编码)网站会更新,文章不保证更新,请务必学会举一反三
Go Module
(在项目目录下使用终端输入go mod init
)go get -u github.com/chromedp/chromedp
(如果有依赖问题请删除-u
)https://account.wps.cn/
为例)"有头"
的方式打开,以便于我们进行debug
func main(){
// chromdp依赖context上限传递参数
ctx, _ := chromedp.NewExecAllocator(
context.Background(),
// 以默认配置的数组为基础,覆写headless参数
// 当然也可以根据自己的需要进行修改,这个flag是浏览器的设置
append(
chromedp.DefaultExecAllocatorOptions[:],
chromedp.Flag("headless", false),
)...,
)
}
func main(){
// chromdp依赖context上限传递参数
...
// 创建新的chromedp上下文对象,超时时间的设置不分先后
// 注意第二个返回的参数是cancel(),只是我省略了
ctx, _ = context.WithTimeout(ctx, 30*time.Second)
ctx, _ = chromedp.NewContext(
ctx,
// 设置日志方法
chromedp.WithLogf(log.Printf),
)
// 通常可以使用 defer cancel() 去取消
// 但是在Windows环境下,我们希望程序能顺带关闭掉浏览器
// 如果不希望浏览器关闭,使用cancel()方法即可
// defer cancel()
// defer chromedp.Cancel(ctx)
}
func main(){
// chromdp依赖context上限传递参数
...
// 创建新的chromedp上下文对象,超时时间的设置不分先后
// 注意第二个返回的参数是cancel(),只是我省略了
...
// 执行我们自定义的任务 - myTasks函数在第4步
if err := chromedp.Run(ctx, myTasks()); err != nil {
log.Fatal(err)
return
}
}
// 自定义任务
func myTasks() chromedp.Tasks {
return chromedp.Tasks{
// 1. 打开金山文档的登陆界面
chromedp.Navigate(loginURL),
}
}
微信登陆
按钮,先找到按钮的选择器
,右键按钮并在菜单中点击检查
,然后可以看到按钮的元素copy
下的copy selector
,即获取到选择器
确认按钮
的选择器// 自定义任务
func myTasks() chromedp.Tasks {
return chromedp.Tasks{
// 1. 打开金山文档的登陆界面
chromedp.Navigate(loginURL),
// 2. 点击微信登陆按钮
// #wechat > span:nth-child(2)
chromedp.Click(`#wechat > span:nth-child(2)`),
// 3. 点击确认按钮
// #dialog > div.dialog-wrapper > div > div.dialog-footer > div.dialog-footer-ok
chromedp.Click(`#dialog > div.dialog-wrapper > div > div.dialog-footer > div.dialog-footer-ok`),
}
}
二维码图片
的选择器
js
来获取对应的href
并下载,但是为了照顾小白,选择最简单的)func myTasks() chromedp.Tasks {
return chromedp.Tasks{
// 1. 打开金山文档的登陆界面
...
// 2. 点击微信登陆按钮
...
// 3. 点击确认按钮
...
// 4. 获取二维码
// #wximport
getCode(),
}
}
func getCode() chromedp.ActionFunc {
return func(ctx context.Context) (err error) {
// 1. 用于存储图片的字节切片
var code []byte
// 2. 截图
// 注意这里需要注明直接使用ID选择器来获取元素(chromedp.ByID)
if err = chromedp.Screenshot(`#wximport`, &code, chromedp.ByID).Do(ctx); err != nil {
return
}
// 3. 保存文件
if err = ioutil.WriteFile("code.png", code, 0755); err != nil {
return
}
return
}
}
gozxing
库func printQRCode(code []byte) (err error) {
// 1. 因为我们的字节流是图像,所以我们需要先解码字节流
img, _, err := image.Decode(bytes.NewReader(code))
if err != nil {
return
}
// 2. 然后使用gozxing库解码图片获取二进制位图
bmp, err := gozxing.NewBinaryBitmapFromImage(img)
if err != nil {
return
}
// 3. 用二进制位图解码获取gozxing的二维码对象
res, err := qrcode.NewQRCodeReader().Decode(bmp, nil)
if err != nil {
return
}
return
}
go-qrcode
库// 请注意import的库发生了重名
import (
"github.com/makiuchi-d/gozxing"
"github.com/makiuchi-d/gozxing/qrcode"
goQrcode "github.com/skip2/go-qrcode"
)
func printQRCode(code []byte) (err error) {
// 1. 因为我们的字节流是图像,所以我们需要先解码字节流
...
// 2. 然后使用gozxing库解码图片获取二进制位图
...
// 3. 用二进制位图解码获取gozxing的二维码对象
...
// 4. 用结果来获取go-qrcode对象(注意这里我用了库的别名)
qr, err := goQrcode.New(res.String(), goQrcode.High)
if err != nil {
return
}
// 5. 输出到标准输出流
fmt.Println(qr.ToSmallString(false))
return
}
func getCode() chromedp.ActionFunc {
return func(ctx context.Context) (err error) {
// 1. 用于存储图片的字节切片
...
// 2. 截图
// 注意这里需要注明直接使用ID选择器来获取元素(chromedp.ByID)
...
// 3. 把二维码输出到标准输出流
if err = printQRCode(code); err != nil {
return err
}
return
}
}
cookies
来维持我们的登录状态,代码实现如下// 保存Cookies
func saveCookies() chromedp.ActionFunc {
return func(ctx context.Context) (err error) {
// 等待二维码登陆
if err = chromedp.WaitVisible(`#app`, chromedp.ByID).Do(ctx); err != nil {
return
}
// cookies的获取对应是在devTools的network面板中
// 1. 获取cookies
cookies, err := network.GetAllCookies().Do(ctx)
if err != nil {
return
}
// 2. 序列化
cookiesData, err := network.GetAllCookiesReturns{Cookies: cookies}.MarshalJSON()
if err != nil {
return
}
// 3. 存储到临时文件
if err = ioutil.WriteFile("cookies.tmp", cookiesData, 0755); err != nil {
return
}
return
}
}
// 加载Cookies
func loadCookies() chromedp.ActionFunc {
return func(ctx context.Context) (err error) {
// 如果cookies临时文件不存在则直接跳过
if _, _err := os.Stat("cookies.tmp"); os.IsNotExist(_err) {
return
}
// 如果存在则读取cookies的数据
cookiesData, err := ioutil.ReadFile("cookies.tmp")
if err != nil {
return
}
// 反序列化
cookiesParams := network.SetCookiesParams{}
if err = cookiesParams.UnmarshalJSON(cookiesData); err != nil {
return
}
// 设置cookies
return network.SetCookies(cookiesParams.Cookies).Do(ctx)
}
}
js
脚本获取当前页面的网址,判断是否已经个人中心页面,如果为真,则停止操作// 检查是否登陆
func checkLoginStatus() chromedp.ActionFunc {
return func(ctx context.Context) (err error) {
var url string
if err = chromedp.Evaluate(`window.location.href`, &url).Do(ctx); err != nil {
return
}
if strings.Contains(url, "https://account.wps.cn/usercenter/apps") {
log.Println("已经使用cookies登陆")
chromedp.Stop()
}
return
}
}
// 自定义任务
func myTasks() chromedp.Tasks {
return chromedp.Tasks{
// 0. 加载cookies <-- 变动
loadCookies(),
// 1. 打开金山文档的登陆界面
...
// 判断一下是否已经登陆 <-- 变动
checkLoginStatus(),
// 2. 点击微信登陆按钮
// #wechat > span:nth-child(2)
...
// 3. 点击确认按钮
// #dialog > div.dialog-wrapper > div > div.dialog-footer > div.dialog-footer-ok
...
// 4. 获取二维码
// #wximport
...
// 5. 若二维码登录后,浏览器会自动跳转到用户信息页面 <-- 变动
saveCookies(),
}
}
已经登陆的cookies
运行程序可以发现我们成功跳过登陆过程