200字范文,内容丰富有趣,生活中的好帮手!
200字范文 > Puppeteer PK 滑动验证码

Puppeteer PK 滑动验证码

时间:2021-02-21 00:47:15

相关推荐

Puppeteer PK 滑动验证码

当我们把 WebDriver 和 Puppeteer 放在一起的时候,还是有必要说明一下这二者的区别。

WebDriver 标准2,可以远程的操控目标浏览器。标准是语言无关、平台无关的。

有一种叫 Selenium 的框架,实现了 WebDriver 协议。Selenium 通过各种浏览器的 driver 来驱动相应的浏览器,可以支持 Python、Java、C#、JavaScript 等语言的编程,同时还可以通过 Selenium Server 实现集群测试。

Puppeteer 是 Google 出品的NodeJS包。使用Node脚本通过 DevTools Protocol 协议,直接对Chrome浏览器进行操作。由于有 Chrome 官方的背景,Puppeteer 和 Chrome 浏览器配合得异常完美。由于几乎直接操作浏览器,也使得操作的效率高于 Selenium。Puppeteer 官方给出了下面这张图,表述了 Puppeteer 中各个部分的关系。

随着 Puppeteer 项目的发展,Puppeteer 也正在向着跨浏览器方向发展。比如Puppeteer-firefox3,目前正在实验阶段。对于使用其他语言操控Puppeteer,官方暂时没有计划4,不过对于python,有一个非官方的实现pypuppeteer5。

因此,就目前的技术体系来看,如果需要多语言、带集群支持、多浏览器支持,请选择 Selenium;如果需要更快速的执行、更易上手的API,或者无需考虑多浏览器,请考虑 Puppeteer。

分析滑动验证码

验证码之所以存在,其实就是要区别出“人”和“机器”。特别的,对于滑动验证码,需要根据滑动的动作判断出操作者是人与否。这显然是与自动化测试是矛盾的。因此解决这个问题的关键点在于两点:

准确地识别出验证码需要滑到的位置。

以符合人类规律的形式把滑块滑到正确的位置

《震惊……》一文中给出了一种方法,这里我们给出基于 Puppeteer 的一种可行做法。

笔者这里把识别过程分为三步:

识别拖动终点地址

拖动

验证结果并区别对待

在这之前,我们先用 Puppeteer 把目标页面打开

constURL="/type/"

letbrowser

constinit=async()=>{

if(browser){

return;

}

browser=awaitpuppeteer.launch({

"headless":false,

"args":["--start-fullscreen"]

});

}

constconfigPage=async(page)=>{

awaitpage.setViewport({width:1280,height:1040});

}

consttoRightPage=async(page)=>{

awaitpage.goto(URL)

awaitpage.evaluate(_=>{

letrect=document.querySelector(".products-content")

.getBoundingClientRect()

window.scrollTo(0,rect.top-30)

})

awaitpage.waitFor(1000)

awaitpage.click(".products-content li:nth-child(2)")

}

~(async(page)=>{

awaitconfigPage(page)

awaittoRightPage(page)

})()

Puppeteer 的大多数方法都是异步的,为了代码更加易读,官方建议更多使用 await/async 来操作。上面这一段代码的含义是:使用 Puppeteer 打开页面,全屏化,打开相应的TAB。并滚动至相应的位置。

如果无需看到跳出的 Chrome,可以把 "headless": false 改为 true。就更像一个命令行程序了。当然在这个例子里我们还是要看到浏览器执行效果的。

为了看到浏览器元素结构,可以在调试时候,打开devtools。方法是在lanch方法中设置 devtools: true。

识别拖动终点地址

首先要做到的是,识别拖动终点的位置。通过分析页面看到,拖动目标是一个黑色的缺口。这个缺口实际上是勾画在一个 canvas.geetest_canvas_bg 元素上。

为了识别出缺口的初始位置,我们需要得到一张没有缺口的图像。实际上,这张图片位于另一个canvas元素 canvas.geetest_canvas_fullbg 上。

理论上,Puppeteer 可以进行对页面、元素等进行截图。然后分析图片的色彩数据。这时候,因为 canvas.geetest_canvas_fullbg 本身处于不可见状态,需要调用脚本将其变为可见,然后也可以得到它:

不过,今时今地我们来偷个懒。因为页面上的元素就是 canvas,而 canvas 本身就有 getImageData 方法。特别懒的笔者决定直接从页面的canvas吐出数据。这里,我们可以把方法封装起来,然后通过 Puppeteer 提供的注入方法,把这个方法直接注入到页面里。

constinjectedScript=`

const getCanvasValue = (selector) => {

let canvas = document.querySelector(selector)

let ctx = canvas.getContext('2d')

let [width, height] = [canvas.width, canvas.height]

let rets = [...Array(height)].map(_ => [...Array(width)].map(_ => 0))

for (let i = 0; i < height; ++i) {

for (let j = 0; j < width; ++j) {

rets[i][j] = Object.values(ctx.getImageData(j,i,1,1).data)

}

}

return rets

}

`

此时调用Puppeteer提供的注入方法 addScriptTag 就可以在上下文中加入这个方法。

awaitpage.addScriptTag({content:injectedScript})

当然这依然是异步方法。后面我们就可以在页面中通过调用注入的 getCanvasValue 方法来获取所选canvas的颜色值了。

这当然是投机取巧的办法,如果目标对象不是canvas,就只好老老实实的截图获取,同时,对于不可见的元素,也可以通过 addScriptTag 的方法,注入 JavaScript 以改变可见性,以便正确地截图。

仔细观察原图和带缺口的图,发现只要取得两个图片像素值的差集,最左边的坐标就是拖动目的地。这里,带缺口的图上面有一个浅色的干扰图。可以在对比“相等”时候,增加一些阀值,定义“相等”为容许有一定范围内的差异。

constTHRESHOLD=70

const_equals=(a,b)=>{

if(a.length!==b.length){

returnfalse

}

for(leti=0;i<a.length;++i){

letdelta=Math.abs(a[i]-b[i])

if(delta>THRESHOLD){

returnfalse

}

}

returntrue

}

const_differentSet=(a1,a2)=>{

letrets=[]

a1.forEach((el,y)=>{

el.forEach((el2,x)=>{

if(!_equals(el2,a2[y][x])){

rets.push({

x,

y,

v:el2,

v2:a2[y][x]

})

}

})

})

returnrets

}

这时,取得差集的x最小值即可。

const_getLeftest=(array)=>{

returnarray.sort((a,b)=>{

if(a.x<b.x){

return-1

}

elseif(a.x==b.x){

if(a.y<=b.y){

return-1

}

return1

}

return1

}).shift()

}

终点有了,起点即为0,因为我们拖动的对象为:.geetest_slider_button。这个圆钮始终位于左侧起始位置。

因此,识别任务完成:从0拖动到识别出的目标点的横坐标。

拖动

Puppeteer不直接提供drag方法。不过提供了对mouse的控制方法。可以通过调用mouse的方法,模拟拖动。同时由于boundingBox方法获取的是圆钮左上角的坐标,需要适当的往内部移几个像素,否则鼠标“抓”不到圆钮。

letslider=awaitpage.waitFor(".geetest_slider_button")

letsliderInfo=awaitslider.boundingBox()

letm=page.mouse

awaitm.move(sliderInfo.x+5,sliderInfo.y+6)

awaitm.down()

// 假装我是拖动代码

awaitm.up()

下面需要模拟拖动。

这里可以随机分段,也可以模拟匀加速直线运动。但需要注意,一定不要使用匀速运动,并且速度不要太快,时间要稍长一些。

以模拟匀加速直线运动为例,

匀加速直线运动的公式为:S = v0t + 1/2*a*t*t其中,S为位移,v0为初速度,t为时间,a为加速度。因为我们的初速度为0,上式可以简化为:S = 1/2*a*t*t。

我们不需要真的用 setInterval 来解决,可以用 generator 来模拟每次调用,并假设每次调用都过了0.2秒,这样可以把每次位移的距离一次性算出来。

let_moveTrace=function*(dis){

lettrace=[]

lett0=0.2

letcurr=0

letstep=0

leta=0.8

while(curr<dis){

lett=t0*(++step)

curr=parseFloat((1/2*a*t*t).toFixed(2))

trace.push(curr)

}

for(leti=0;i<trace.length;++i){

yieldtrace[i]

}

}

使用时候,我们用generator的方式调用之,使用for...of,当generator结束返回,for...of也自动中止了。同时,为了真实性,可以做一些对y的随机指定(实际y是不会动的)。横坐标x也可以做一些超过再退回的扰动设置。

letgen=_moveTrace(dest.x)

for(letretofgen){

awaitm.move(sliderInfo.x+ret,sliderInfo.y+6+_getY(-5,40))

}

awaitm.move(sliderInfo.x+dest.x,sliderInfo.y+6+_getY(-5,40))

此时,拖动的任务基本完成。

验证结果并区别对待

由于这些测试可能会有一定程度的误差,也会造成一些失败率。为了真正“自动”起来,需要对识别和拖动结果作出一些判断,对于错误的,重新启动测试。对于多次犯错的,重新刷新页面。

letisSuccess=awaitpage.evaluate(_=>{if(!!document.querySelector(".geetest_success_animate")){returntrue}returnfalse})

好了,Puppeteer 这个任务的细节基本完成。

保存, 执行。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。