当我们把 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。

分析滑动验证码

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

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

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

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

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

  1. 识别拖动终点地址

  2. 拖动

  3. 验证结果并区别对待

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

const URL = "http://www.geetest.com/type/"

let browser

const init = async () => {

if (browser) {

return;

}

browser = await puppeteer.launch({

"headless": false,

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

});

}

const configPage = async (page) => {

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

}

const toRightPage = async (page) => {

await page.goto(URL)

await page.evaluate(_ => {

let rect = document.querySelector(".products-content")

.getBoundingClientRect()

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

})

await page.waitFor(1000)

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

}

~(async (page) => {

await configPage(page)

await toRightPage(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 提供的注入方法,把这个方法直接注入到页面里。

const injectedScript = `

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 就可以在上下文中加入这个方法。

await page.addScriptTag({content: injectedScript})

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

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

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

const THRESHOLD = 70

const _equals = (a, b) => {

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

return false

}

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

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

if (delta > THRESHOLD) {

return false

}

}

return true

}

const _differentSet = (a1, a2) => {

let rets = []

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

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

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

rets.push({

x,

y,

v: el2,

v2: a2[y][x]

})

}

})

})

return rets

}

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

const _getLeftest = (array) => {

return array.sort((a, b) => {

if (a.x < b.x) {

return -1

}

else if (a.x == b.x) {

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

return -1

}

return 1

}

return 1

}).shift()

}

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

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

拖动

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

let slider = await page.waitFor(".geetest_slider_button")

let sliderInfo = await slider.boundingBox()

let m = page.mouse

await m.move(sliderInfo.x + 5, sliderInfo.y + 6)

await m.down()

// 假装我是拖动代码

await m.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){

let trace = []

let t0 = 0.2

let curr = 0

let step = 0

let a = 0.8

while (curr < dis) {

let t = t0 * (++step)

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

trace.push(curr)

}

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

yield trace[i]

}

}

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

let gen = _moveTrace(dest.x)

for (let ret of gen) {

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

}

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

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

验证结果并区别对待

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

let isSuccess = await page.evaluate(_ => {if (!!document.querySelector(".geetest_success_animate")) {return true}return false
})

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

保存, 执行。

Puppeteer PK 滑动验证码相关推荐

  1. 从零破解一款轻量级滑动验证码

    关注下方「前端热榜」,回复 "思维图" 获取公众号所有JS思维图 昨天在掘金看到推荐文章<从零开发一款轻量级滑动验证码插件>[1],介绍了一些相关验证码及框架开发的知识 ...

  2. 200行代码实现一个滑动验证码

    作者 | 崔庆才 转载自进击的Coder(ID: FightingCoder) 做网络爬虫的同学肯定见过各种各样的验证码,比较高级的有滑动.点选等样式,看起来好像挺复杂的,但实际上它们的核心原理还是还 ...

  3. 用Python爬虫破解滑动验证码

    我们可以借用opencv来解决这个问题,主要步骤: opencv 是什么? OpenCV(Open Source Computer Vision Library)是开放源代码计算机视觉库,主要算法涉及 ...

  4. thinkphp整合极验滑动验证码源码演示下载

    thinkphp整合极验滑动验证码源码演示下载-二当家的php源码下载 <!DOCTYPE html><html lang="en"><head> ...

  5. 爬虫python代码-Python爬虫教程:200行代码实现一个滑动验证码

    Python爬虫教程:教你用200行代码实现一个滑动验证码 做网络爬虫的同学肯定见过各种各样的验证码,比较高级的有滑动.点选等样式,看起来好像挺复杂的,但实际上它们的核心原理还是还是很清晰的,本文章大 ...

  6. 爬虫学习笔记(十九)—— 滑动验证码

    文章目录 一.概念 二.实现步骤 2.1.获取验证码图片 2.1.1.获取缺口图 2.1.2.获取滑块图 2.1.3.获取完整图 2.1.4.完整代码 2.2.计算缺口位置 2.3.模拟人工移动 2. ...

  7. python滑动验证码处理_python+selenium滑动式验证码解决办法

    from selenium importwebdriverfrom selenium.webdriver.support.ui import WebDriverWait #等待元素加载的 from s ...

  8. Python3 爬虫实战 — 模拟登陆哔哩哔哩【滑动验证码对抗】

    登陆时间:2019-10-21 实现难度:★★★☆☆☆ 请求链接:https://passport.bilibili.com/login 实现目标:模拟登陆哔哩哔哩,攻克滑动验证码 涉及知识:滑动验证 ...

  9. Python3 爬虫学习笔记 C13【验证码对抗系列 — 滑动验证码】

    Python3 爬虫学习笔记第十三章 -- [验证码对抗系列 - 滑动验证码] 文章目录 [13.1]关于滑动验证码 [13.2]滑动验证码攻克思路 [13.3]模拟登录 bilibili - 总体思 ...

最新文章

  1. 艾伟也谈项目管理,敏捷教练的工具箱
  2. DEV开发之控件NavBarControl
  3. mysql text 最大长度 报错 Row size too large. The maximum row size for the used table type
  4. rhel6下组建两台主机的HA集群
  5. 自定义ArcGIS JavaScript 工具条样式
  6. boost::mpi模块对gather() 和gatherv() 集合的测试
  7. 资源征集 | 2021年全国知识图谱与语义计算大会开放资源征集(Resource Track)通知...
  8. [Node.js] mySQL数据库 -- NPM包
  9. Data-Mediator入门系列4----常用类说明
  10. HTML的语义化和一些简单优化
  11. 超小股票行情查看软件
  12. 计算机配置很高 但是很卡,电脑配置高但很卡_电脑配置很高但还是很卡是怎么回事啊?...
  13. 关于ModifyStyle
  14. 港科夜闻|香港科技大学(广州)拟获批首个省级重点实验室
  15. 搭建一个vue小页面
  16. 前端页面如果不放在statis等文件夹,想正常访问该如何解决
  17. BACnet安全连接(BACnet/SC) 介绍
  18. centos7 SSH服务启动时报“main process exited, code=exited”status 255错误
  19. vue css style 调整字体大小 font-size
  20. 辰视智能冯良炳:让机器人拥有灵敏的眼睛!

热门文章

  1. UDP-疑难杂症和使用
  2. 【资源分享(免积分)】(视频)李宏毅2018最新GAN课程(lecture1--10),附srt字幕高免积分下载,侵删
  3. ⭐李宏毅机器学习2020作业汇总
  4. Linux 运行python命令
  5. 三个人分汤终极解答,四人分汤,多人分汤终极答案。
  6. (完整版)HTML5常用标签大全
  7. 企业供应链管理与贸易融资的那些事儿
  8. java刺客信条启示录_刺客信条启示录为什么叫启示录?
  9. 时间显示rpc服务器不可用,“电脑时间的RPC服务器不可用”是什么原因?
  10. 一个Job在OneFlow中的执行过程—下篇