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 把目标页面打开
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 滑动验证码相关推荐
- 从零破解一款轻量级滑动验证码
关注下方「前端热榜」,回复 "思维图" 获取公众号所有JS思维图 昨天在掘金看到推荐文章<从零开发一款轻量级滑动验证码插件>[1],介绍了一些相关验证码及框架开发的知识 ...
- 200行代码实现一个滑动验证码
作者 | 崔庆才 转载自进击的Coder(ID: FightingCoder) 做网络爬虫的同学肯定见过各种各样的验证码,比较高级的有滑动.点选等样式,看起来好像挺复杂的,但实际上它们的核心原理还是还 ...
- 用Python爬虫破解滑动验证码
我们可以借用opencv来解决这个问题,主要步骤: opencv 是什么? OpenCV(Open Source Computer Vision Library)是开放源代码计算机视觉库,主要算法涉及 ...
- thinkphp整合极验滑动验证码源码演示下载
thinkphp整合极验滑动验证码源码演示下载-二当家的php源码下载 <!DOCTYPE html><html lang="en"><head> ...
- 爬虫python代码-Python爬虫教程:200行代码实现一个滑动验证码
Python爬虫教程:教你用200行代码实现一个滑动验证码 做网络爬虫的同学肯定见过各种各样的验证码,比较高级的有滑动.点选等样式,看起来好像挺复杂的,但实际上它们的核心原理还是还是很清晰的,本文章大 ...
- 爬虫学习笔记(十九)—— 滑动验证码
文章目录 一.概念 二.实现步骤 2.1.获取验证码图片 2.1.1.获取缺口图 2.1.2.获取滑块图 2.1.3.获取完整图 2.1.4.完整代码 2.2.计算缺口位置 2.3.模拟人工移动 2. ...
- python滑动验证码处理_python+selenium滑动式验证码解决办法
from selenium importwebdriverfrom selenium.webdriver.support.ui import WebDriverWait #等待元素加载的 from s ...
- Python3 爬虫实战 — 模拟登陆哔哩哔哩【滑动验证码对抗】
登陆时间:2019-10-21 实现难度:★★★☆☆☆ 请求链接:https://passport.bilibili.com/login 实现目标:模拟登陆哔哩哔哩,攻克滑动验证码 涉及知识:滑动验证 ...
- Python3 爬虫学习笔记 C13【验证码对抗系列 — 滑动验证码】
Python3 爬虫学习笔记第十三章 -- [验证码对抗系列 - 滑动验证码] 文章目录 [13.1]关于滑动验证码 [13.2]滑动验证码攻克思路 [13.3]模拟登录 bilibili - 总体思 ...
最新文章
- 艾伟也谈项目管理,敏捷教练的工具箱
- DEV开发之控件NavBarControl
- mysql text 最大长度 报错 Row size too large. The maximum row size for the used table type
- rhel6下组建两台主机的HA集群
- 自定义ArcGIS JavaScript 工具条样式
- boost::mpi模块对gather() 和gatherv() 集合的测试
- 资源征集 | 2021年全国知识图谱与语义计算大会开放资源征集(Resource Track)通知...
- [Node.js] mySQL数据库 -- NPM包
- Data-Mediator入门系列4----常用类说明
- HTML的语义化和一些简单优化
- 超小股票行情查看软件
- 计算机配置很高 但是很卡,电脑配置高但很卡_电脑配置很高但还是很卡是怎么回事啊?...
- 关于ModifyStyle
- 港科夜闻|香港科技大学(广州)拟获批首个省级重点实验室
- 搭建一个vue小页面
- 前端页面如果不放在statis等文件夹,想正常访问该如何解决
- BACnet安全连接(BACnet/SC) 介绍
- centos7 SSH服务启动时报“main process exited, code=exited”status 255错误
- vue css style 调整字体大小 font-size
- 辰视智能冯良炳:让机器人拥有灵敏的眼睛!
热门文章
- UDP-疑难杂症和使用
- 【资源分享(免积分)】(视频)李宏毅2018最新GAN课程(lecture1--10),附srt字幕高免积分下载,侵删
- ⭐李宏毅机器学习2020作业汇总
- Linux 运行python命令
- 三个人分汤终极解答,四人分汤,多人分汤终极答案。
- (完整版)HTML5常用标签大全
- 企业供应链管理与贸易融资的那些事儿
- java刺客信条启示录_刺客信条启示录为什么叫启示录?
- 时间显示rpc服务器不可用,“电脑时间的RPC服务器不可用”是什么原因?
- 一个Job在OneFlow中的执行过程—下篇