效果展示
本文将展示如何使用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库实现极验滑动验证码自动识别的方法,包括模拟点击、获取背景图、识别缺口以及模拟拖动滑块的全过程。