chromep模拟抖音人机滑块校验
前言
使用chromedp启动浏览器,打开抖音网页版的时候,一直出现滑块人机校验,就用chromedp模拟拖动滑块一、监听网页版获取滑块的接口,将图片下载到本地
opts := append(chromedp.DefaultExecAllocatorOptions[:],
chromedp.Flag("headless", false),
chromedp.Flag("ignore-certificate-errors", true),
)
var allocCtx context.Context
allocCtx,_ = chromedp.NewExecAllocator(context.Background(), opts...)
ctx,_ := chromedp.NewContext(allocCtx,)
// 过滑块校验
// 监听是否人机校验接口,如果有将两个图片存储到本地 C:\Users\jack\Desktop\pic,供python使用
var requestId network.RequestID
var gapImg string
var bgImg string
chromedp.ListenTarget(ctx, func(ev interface{}) {
switch ev := ev.(type) {
case *network.EventRequestWillBeSent:
if strings.HasPrefix(ev.Request.URL, "https://verify.snssdk.com/captcha/get") {
requestId = ev.RequestID
}
}
if event, ok := ev.(*network.EventLoadingFinished); ok {
if requestId != "" && event.RequestID == requestId {
go func() {
var data []byte
if err := chromedp.Run(ctx, chromedp.ActionFunc(func(ctx context.Context) error {
var err error
data, err = network.GetResponseBody(requestId).Do(ctx)
if err != nil {
return err
}
return nil
})); err != nil {
fmt.Println("err ", err)
}
bgImgUrl := gjson.Get(string(data), "data").Get("question").Get("url1").String()
gapImgUrl := gjson.Get(string(data), "data").Get("question").Get("url2").String()
// 将图片下载到本地
bgImg = downloadImg(bgImgUrl)
gapImg = downloadImg(gapImgUrl)
}()
}
}
})
chromedp.Run(
ctx,
chromedp.Navigate("https://www.douyin.com/video/7067020209298705668"),
chromedp.WaitReady("//body"),
)
将图片下载到本地后,把路径存储起来,供计算滑块需要滑动的距离
二、计算滑块需要滑动的距离
此处使用的是python的开源项目: https://github.com/crazyxw/SlideCrack
注意原图的大小和网页中该图片大小是不同的,需要对原图resize
# 抖音人机校验获取的原图,需要缩小大概1.624倍,才能使用
def img_resize(image):
height, width = image.shape[0], image.shape[1]
width_new = int(height / 1.624)
height_new = int(width / 1.624)
m = 1.624
img_new = cv2.resize(image, (height_new, width_new))
return img_new
class SlideCrack(object):
def __init__(self, gap, bg):
"""
init code
:param gap: 缺口图片
:param bg: 背景图片
:param out: 输出图片
"""
self.gap = gap
self.bg = bg
@staticmethod
def clear_white(img):
# 清除图片的空白区域,这里主要清除滑块的空白
_img = cv2.imread(img)
img = img_resize(_img)
rows, cols, channel = img.shape
min_x = 255
min_y = 255
max_x = 0
max_y = 0
for x in range(1, rows):
for y in range(1, cols):
t = set(img[x, y])
if len(t) >= 2:
if x <= min_x:
min_x = x
elif x >= max_x:
max_x = x
if y <= min_y:
min_y = y
elif y >= max_y:
max_y = y
img1 = img[min_x:max_x, min_y: max_y]
return img1
def template_match(self, tpl, target):
th, tw = tpl.shape[:2]
result = cv2.matchTemplate(target, tpl, cv2.TM_CCOEFF_NORMED)
# 寻找矩阵(一维数组当作向量,用Mat定义) 中最小值和最大值的位置
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)
tl = max_loc
br = (tl[0] + tw, tl[1] + th)
# 绘制矩形边框,将匹配区域标注出来
# target:目标图像
# tl:矩形定点
# br:矩形的宽高
# (0,0,255):矩形边框颜色
# 1:矩形边框大小
cv2.rectangle(target, tl, br, (0, 0, 255), 2)
# cv2.imwrite(self.out, target)
return tl[0]
@staticmethod
def image_edge_detection(img):
edges = cv2.Canny(img, 100, 200)
return edges
def discern(self):
img1 = self.clear_white(self.gap)
img1 = cv2.cvtColor(img1, cv2.COLOR_RGB2GRAY)
slide = self.image_edge_detection(img1)
_back = cv2.imread(self.bg, 0)
back = img_resize(_back)
back = self.image_edge_detection(back)
slide_pic = cv2.cvtColor(slide, cv2.COLOR_GRAY2RGB)
back_pic = cv2.cvtColor(back, cv2.COLOR_GRAY2RGB)
x = self.template_match(slide_pic, back_pic)
# 输出横坐标, 即 滑块在图片上的位置
return x
然后开启一个http服务,将两个路径传输过来,最终传回去的就是滑块需要滑动的距离,注意该距离还需要减去一个边缘的距离,大概是6
三、chromedp获取到需要移动的距离后,模拟拖拽
var lap int // 本次人机校验需要滑动的距离
var sel = `#captcha_container > div > div.captcha_verify_img--wrapper.sc-gZMcBi.jzVByM > img.captcha_verify_img_slide.react-draggable.sc-VigVT.ggNWOG`
func DragElement(sel interface{}, xlap int) chromedp.QueryAction {
fmt.Println("dragElement")
return chromedp.QueryAfter(sel, func(ctx context.Context, id runtime.ExecutionContextID, node ...*cdp.Node) error {
if len(node) == 0 {
fmt.Println("找不到相应node")
return fmt.Errorf("找不到相关 Node")
}
return MouseDragNode(node[0], xlap).Do(ctx)
})
}
func MouseDragNode(n *cdp.Node, xlap int) chromedp.ActionFunc {
return func(ctx context.Context) error {
boxes, err := dom.GetContentQuads().WithNodeID(n.NodeID).Do(ctx)
if err != nil {
return err
}
box := boxes[0]
c := len(box)
if c%2 != 0 || c < 1 {
return chromedp.ErrInvalidDimensions
}
var x, y float64
for i := 0; i < c; i += 2 {
x += box[i]
y += box[i+1]
}
x /= float64(c / 2)
y /= float64(c / 2)
p := &input.DispatchMouseEventParams{
Type: input.MousePressed,
X: x,
Y: y,
Button: input.Left,
ClickCount: 1,
}
// 鼠标左键按下
if err := p.Do(ctx); err != nil {
return err
}
// 拖动
p.Type = input.MouseMoved
t := rand.Intn(20) + 40
totalX := 0
// 生成随机的路径,模拟拖动
for i:=0 ; i < t ; i++ {
rt := rand.Intn(20) + 20
chromedp.Run(ctx, chromedp.Sleep(time.Millisecond * time.Duration(rt)))
x := rand.Intn(2) + 4
if totalX >= xlap {
break
}
if totalX + x >= xlap {
x = xlap - totalX
}
totalX += x
y := rand.Intn(2)
p.Y = p.Y + float64(y)
p.X = p.X + float64(x)
if err := p.Do(ctx); err != nil {
return err
}
}
// 鼠标松开
p.Type = input.MouseReleased
return p.Do(ctx)
}
}
总结
如果滑块校验多了,会出现汉字校验,目前还没做
其实用chromedp启动浏览器是可以绕过抖音人机校验