效果展示
本文将展示如何使用Go语言实现极验滑动验证码的自动识别。从模拟点击到识别滑动缺口、计算位移并模拟拖动滑块。如果认证失败,则重复调用直到成功。
识别思路
模拟点击切换为滑动验证,并显示验证界面。
识别滑动缺口的位置,计算位移。
模拟拖动滑块。
若认证失败,重复调用。
详细过程及代码
初始化
首先,初始化Selenium对象和一些参数配置,极验验证码测试页面的网址如下:
go
package main
import (
"bytes"
"image"
"image/png"
"log"
"time"
"github.com/tebeka/selenium"
)
const (
browserDriverPath = "/path/to/chromedriver"
port = 9515
browserPath = "http://localhost:9515"
url = "https://www.geetest.com/type/"
)
type CrackGeetest struct {
WebDriver selenium.WebDriver
}
func NewCrackGeetest() *CrackGeetest {
opts := []selenium.ServiceOption{}
service, err := selenium.NewChromeDriverService(browserDriverPath, port, opts...)
if err != nil {
log.Fatalf("Error starting the ChromeDriver server: %v", err)
}
caps := selenium.Capabilities{"browserName": "chrome"}
wd, err := selenium.NewRemote(caps, browserPath)
if err != nil {
log.Fatalf("Error creating new WebDriver session: %v", err)
}
return &CrackGeetest{WebDriver: wd}
}
func (cg *CrackGeetest) Open() {
if err := cg.WebDriver.Get(url); err != nil {
log.Fatalf("Error opening the URL: %v", err)
}
}
func (cg *CrackGeetest) Close() {
cg.WebDriver.Quit()
}
模拟点击
首先模拟点击切换为滑动验证,然后模拟点击弹出验证图片。
go
func (cg *CrackGeetest) ChangeToSlide() selenium.WebElement {
huadong, err := cg.WebDriver.FindElement(selenium.ByCSSSelector, ".products-content ul > li:nth-child(2)")
if err != nil {
log.Fatalf("Error finding the slide option element: %v", err)
}
return huadong
}
func (cg *CrackGeetest) GetGeetestButton() selenium.WebElement {
button, err := cg.WebDriver.FindElement(selenium.ByCSSSelector, ".geetest_radar_tip")
if err != nil {
log.Fatalf("Error finding the initial verification button: %v", err)
}
return button
}
获取背景图
首先等待验证码加载完成(WaitPic),获取网页截图(GetScreenshot),然后获取验证背景图所在的位置及大小参数(GetPosition)和滑块对象(GetSlider)。
go
func (cg *CrackGeetest) WaitPic() {
_, err := cg.WebDriver.FindElement(selenium.ByCSSSelector, ".geetest_popup_wrap")
if err != nil {
log.Fatalf("Error waiting for the verification image to load: %v", err)
}
}
func (cg *CrackGeetest) GetScreenshot() image.Image {
screenshot, err := cg.WebDriver.Screenshot()
if err != nil {
log.Fatalf("Error taking screenshot: %v", err)
}
img, err := png.Decode(bytes.NewReader(screenshot))
if err != nil {
log.Fatalf("Error decoding screenshot: %v", err)
}
return img
}
func (cg *CrackGeetest) GetPosition() (int, int, int, int) {
img, err := cg.WebDriver.FindElement(selenium.ByClassName, "geetest_canvas_img")
if err != nil {
log.Fatalf("Error finding the verification image element: %v", err)
}
location, err := img.Location()
if err != nil {
log.Fatalf("Error getting location of the verification image: %v", err)
}
size, err := img.Size()
if err != nil {
log.Fatalf("Error getting size of the verification image: %v", err)
}
top, bottom, left, right := location.Y, location.Y+size.Height, location.X, location.X+size.Width
return top, bottom, left, right
}
func (cg *CrackGeetest) GetSlider() selenium.WebElement {
slider, err := cg.WebDriver.FindElement(selenium.ByClassName, "geetest_slider_button")
if err != nil {
log.Fatalf("Error finding the slider element: %v", err)
}
return slider
}
再通过上述返回的背景图位置和大小参数,对网页截图进行切片(GetGeetestImage),最后获取背景图。
go
func (cg *CrackGeetest) GetGeetestImage(name string) image.Image {
top, bottom, left, right := cg.GetPosition()
log.Printf("验证码位置: %d %d %d %d\n", top, bottom, left, right)
screenshot := cg.GetScreenshot()
captcha := screenshot.(interface {
SubImage(r image.Rectangle) image.Image
}).SubImage(image.Rect(left, top, right, bottom))
file, err := os.Create(name)
if err != nil {
log.Fatalf("Error creating image file: %v", err)
}
defer file.Close()
png.Encode(file, captcha)
return captcha
}
删除样式获取无缺口背景图
go
func (cg *CrackGeetest) DeleteStyle() {
script := "document.querySelectorAll('canvas')[2].style=''"
_, err := cg.WebDriver.ExecuteScript(script, nil)
if err != nil {
log.Fatalf("Error executing script to delete style: %v", err)
}
}
识别缺口
go
func IsPixelEqual(img1, img2 image.Image, x, y int) bool {
pix1 := img1.At(x, y)
pix2 := img2.At(x, y)
r1, g1, b1, _ := pix1.RGBA()
r2, g2, b2, _ := pix2.RGBA()
threshold := uint32(60)
return (abs(r1, r2) < threshold) && (abs(g1, g2) < threshold) && (abs(b1, b2) < threshold)
}
func abs(a, b uint32) uint32 {
if a > b {
return a - b
}
return b - a
}
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 {
a := 2.0
if current >= mid {
a = -3.0
}
v0 := v
v = v0 + a*t
move := v0*t + 0.5*a*t*t
current += int(move)
track = append(track, int(move))
}
return track
}
func ShakeMouse(wd selenium.WebDriver) {
actions := wd.Actions()
actions.MoveByOffset(-3, 0).Perform()
actions.MoveByOffset(2, 0).Perform()
}
func MoveToGap(wd selenium.WebDriver, slider selenium.WebElement, tracks []int) {
actions := wd.Actions()
actions.ClickAndHold(slider).Perform()
for _, x := range tracks {
actions.MoveByOffset(x, 0).Perform()更多内容联系1436423940
}
backTracks := []int{-1, -1, -2, -2, -3, -2, -2, -1, -1}
for _, x := range backTracks {
actions.MoveByOffset(x, 0).Perform()
}
ShakeMouse(wd)
time.Sleep(500 * time.Millisecond)
actions.Release().Perform()
}
整个控制流程
执行主体流程,若验证失败,则再次调用Crack进行识别,直至成功。
go
func (cg *CrackGeetest) Crack() {
defer cg.Close()
for {
cg.Open()
slideBtn := cg.ChangeToSlide()
slideBtn.Click()
geetestBtn := cg.GetGeetestButton()
geetestBtn.Click()
cg.WaitPic()
slider := cg.GetSlider()
img1 := cg.GetGeetestImage("captcha1.png")
cg.DeleteStyle()
img2 := cg.GetGeetestImage("captcha2.png")
gap := GetGap(img1, img2)
log.Printf("缺口位置: %d\n", gap)
gap -= BORDER
tracks := GetTrack(gap)
MoveToGap(cg.WebDriver, slider, tracks)
success, err := cg.WebDriver.FindElement(selenium.ByCSSSelector, ".geetest_success_radar_tip_content")
if err == nil {
text, _ := success.Text()
if text == "验证成功" {
break
}
}
time.Sleep(5 * time.Second)
}
}
func main() {
crack := NewCrackGeetest()
crack.Crack()
}