用Go语言实现极验滑动验证码识别


效果展示
本文将展示如何使用Go语言实现极验滑动验证码的自动识别,从模拟点击到识别滑动缺口、计算位移并模拟拖动滑块。如果认证失败,则重复调用直到成功。

识别思路
模拟点击切换为滑动验证,并显示验证界面。
识别滑动缺口的位置,计算位移。
模拟拖动滑块。
若认证失败,重复调用。
详细过程及代码
初始化
首先,初始化selenium对象和一些参数配置,极验验证码测试页面的网址如下:

go

package main

import (
    "bytes"
    "fmt"
    "image"
    "image/png"
    "io/ioutil"
    "time"

    "github.com/sclevine/agouti"
)

const BORDER = 6

type CrackGeetest struct {
    url     string
    driver  *agouti.WebDriver
    page    *agouti.Page
}

func NewCrackGeetest() *CrackGeetest {
    driver := agouti.ChromeDriver()
    if err := driver.Start(); err != nil {
        panic(fmt.Sprintf("Failed to start driver: %s", err))
    }
    page, err := driver.NewPage()
    if err != nil {
        panic(fmt.Sprintf("Failed to open page: %s", err))
    }
    return &CrackGeetest{
        url:    "https://www.geetest.com/type/",
        driver: driver,
        page:   page,
    }
}

func (c *CrackGeetest) Open() {
    if err := c.page.Navigate(c.url); err != nil {
        panic(fmt.Sprintf("Failed to navigate: %s", err))
    }
}

func (c *CrackGeetest) Close() {
    c.page.Destroy()
    c.driver.Stop()
}
定义了一个 CrackGeetest 类,初始化selenium对象和一些参数配置,网址是极验的验证码测试页面。

模拟点击
首先模拟点击切换为滑动验证,然后模拟点击弹出验证图片。

go

func (c *CrackGeetest) ChangeToSlide() *agouti.Selection {
    return c.page.FindByCSS(".products-content ul > li:nth-child(2)")
}

func (c *CrackGeetest) GetGeetestButton() *agouti.Selection {
    return c.page.FindByCSS(".geetest_radar_tip")
}
该步骤定义了两个方法,均利用显示等待的方法实现。并返回按钮对象,后用Click方法模拟点击。

获取背景图
首先等待验证码加载完成(WaitPic),获取网页截图(GetScreenshot),然后获取验证背景图所在的位置及大小参数(GetPosition)和滑块对象(GetSlider)。

go

func (c *CrackGeetest) WaitPic() {
    c.page.FindByCSS(".geetest_popup_wrap").Visible()
}

func (c *CrackGeetest) GetScreenshot() image.Image {
    imgBytes, _ := c.page.Screenshot()
    img, _, _ := image.Decode(bytes.NewReader(imgBytes))
    return img
}

func (c *CrackGeetest) GetPosition() (int, int, int, int) {
    img := c.page.FindByClass("geetest_canvas_img")
    time.Sleep(2 * time.Second)
    location, _ := img.Location()
    size, _ := img.Size()
    top, bottom := location.Y, location.Y+size.Height
    left, right := location.X, location.X+size.Width
    return top, bottom, left, right
}

func (c *CrackGeetest) GetSlider() *agouti.Selection {
    return c.page.FindByClass("geetest_slider_button")
}
再通过上述返回的背景图位置和大小参数,对网页截图进行切片(GetGeetestImage),最后获取背景图。

go

func (c *CrackGeetest) GetGeetestImage(name string) image.Image {
    top, bottom, left, right := c.GetPosition()
    fmt.Printf("验证码位置 %d, %d, %d, %d\n", top, bottom, left, right)
    screenshot := c.GetScreenshot()
    img := screenshot.(interface {
        SubImage(r image.Rectangle) image.Image
    }).SubImage(image.Rect(left, top, right, bottom))
    out, _ := ioutil.TempFile("", name)
    defer out.Close()
    png.Encode(out, img)
    return img
}
到这里,已经获取了带缺口的背景图。我们需要获取不带缺口滑块的原图。这里通过改变CSS样式获得原图。

go

func (c *CrackGeetest) DeleteStyle() {
    c.page.RunScript(`document.querySelectorAll("canvas")[2].style=""`, nil, nil)
}
执行js脚本之后(DeleteStyle)获得了无缺口的原图,再调用之前的截图方法,就可以获取同大小的背景图。

识别缺口
我们得到了两张图,接下来对比它们来获取缺口位置。

go

func IsPixelEqual(img1, img2 image.Image, x, y int) bool {
    r1, g1, b1, _ := img1.At(x, y).RGBA()
    r2, g2, b2, _ := img2.At(x, y).RGBA()
    threshold := uint32(60)
    return abs(r1-r2) < threshold && abs(g1-g2) < threshold && abs(b1-b2) < threshold
}

func abs(a uint32) uint32 {
    if a < 0 {
        return -a
    }
    return a
}
GetGap()方法遍历两张图片的每个像素,再利用IsPixelEqual()方法判断两张图片同一位置的像素。

go

func GetGap(img1, img2 image.Image) int {
    left := 60
    for i := left; i < img1.Bounds().Dx(); i++ {
        for j := 0; j < img1.Bounds().Dy(); j++ {
            if !IsPixelEqual(img1, img2, i, j) {
                return i
            }
        }
    }
    return left
}
模拟拖动
我们获得了滑块的位置,现在只需计算距离并模拟拖动即可。

go

func GetTrack(distance int) []int {
    track := []int{}
    current := 0
    mid := distance * 3 / 5
    t := 0.2
    v := 0.0
    distance += 14
    for current < distance {
        var a float64
        if current < mid {
            a = 2
        } else {
            a = -1.5
        }
        v0 := v
        v = v0 + a*t
        move := v0*t + 0.5*a*t*t
        current += int(move)
        track = append(track, int(move))
    }
    return track
}
前3/5路程加速,后面减速,track返回的是一个列表,其中每个元素代表的是每次移动的距离。然后模拟释放鼠标时的人手抖动(ShakeMouse)。

go

func ShakeMouse(page *agouti.Page) {
    page.MouseTo(-3, 0)
    page.MouseTo(2, 0)
}

func MoveToGap(page *agouti.Page, slider *agouti.Selection, tracks []int) {
    page.MouseDownAt(agouti.Center, slider)
    for _, x := range tracks {
        page.MouseMoveBy(x, 0)
    }
    backTracks := []int{-1, -1, -2, -2, -3, -2, -2, -1, -1}
    for _, x := range backTracks {
        page.MouseMoveBy(x, 0)
    }
    ShakeMouse(page)更多内容联系1436423940
    time.Sleep(500 * time.Millisecond)
    page.MouseUp()
}
最后根据之前所得到的运动轨迹拖动滑块(MoveToGap)即可。

整个控制流程
执行主体流程,若验证失败,则再次调用Crack()进行识别,直至成功。

go

func (c *CrackGeetest) Crack() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Failed-Retry")
            c.Crack()
        }
    }()
    c.Open()
    sButton := c.ChangeToSlide()
    sButton.Click()
    gButton := c.GetGeetestButton()
    gButton.Click()
    c.WaitPic()
    slider := c.GetSlider()
    image1 := c.GetGeetestImage("captcha1.png")
    c.DeleteStyle()
    image2 := c.GetGeetestImage("captcha2.png")
    gap := GetGap(image1, image2)
    fmt.Printf("缺口位置: %d\n", gap)
    gap -= BORDER
    track := GetTrack(gap)
    MoveToGap(c.page, slider, track)
    success := c.page.FindByClass("geetest_success_radar_tip_content").Text() == "验证成功"
    fmt.Println(success)
    time.Sleep(5 * time.Second)
    c.Close()
}

func main() {
    crack := NewCrackGeetest()
    crack.Crack()
}
至此,使用Go语言实现极验滑动验证码识别的方法已经记录完毕。

关键字总结
agouti
image
image/png
time
代码风格与验证码分析思路
本文详细介绍了利用Go语言、agouti和image库实现极验滑动验证码自动识别的方法,包括模拟点击、获取背景图、识别缺口以及模拟拖动滑块的全过程。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值