最近西部世界第二季很火, 小编常常分不清谁是机器人谁是真人css
为了区分人类和机器, 有我的发明了一种测试, 他叫图灵~git
验证码就是一个典型的图灵测试, 英文名 captcha, 全称以下github
Completely Automated Public Turing test to tell Computers and Humans Apartweb
全自动区分计算机和人类的图灵测试canvas
目前主流的验证码有api
图形验证码浏览器
短信验证码微信
滑块验证码dom
图中点选验证码async
但如今的人工智能过于强大, 大部分扭曲的图形验证码均可以被机器破解, 已经再也不是一个可靠的图灵测试
并且图形验证码体验不好, 输入困难
这时候, 滑动验证码出现了, 最具表明性的就是 Geetest(极验)
滑动验证码对机器破解有两大难点, 第一个是须要经过图像识别知道滑到哪里, 第二个是须要模仿人类作出滑动的手势
滑动验证码 操做简便, 破解难度大, 很快就流行起来了
正好 W3C 近日发布了一个浏览器自动化操做的标准, 名叫 WebDriver
https://www.w3.org/TR/webdriver1/
小编就拿极验滑动验证码开刀, 和你们一块儿感觉 WebDriver 的强大功能
先附上小编成果(本视频20秒, 没有声音)
WebDriver 已是既定标准, 各大浏览器最新版都自然支持 WebDriver 协议, 使用门槛大大下降
小编本次使用的是支持度较好的浏览器 firefox
从 https://github.com/mozilla/geckodriver 官方仓库中便可下载 firefox 的 diver
WebDriver 标准自己只是定义了一系列操做浏览器的 HTTP 协议, 但 selenium 已经为咱们封装成了 sdk, 直接调用函数便可
const webdriver = require('selenium-webdriver')
咱们先用 WebDriver 实现一个最简单的例子: 打开一个网页
const webdriver = require('selenium-webdriver')
!async function() {
// 新建一个 firefox 的 driver 实例
let driver = await new webdriver.Builder().forBrowser('firefox').build()
// 访问极验demo页
await driver.get('http://www.geetest.com/type/')
console.log('success')
}()
运行这段代码, 咱们能看到浏览器打开了极验的 demo 页
此时浏览器的地址栏是黄色的, 表示该浏览器被控制了, 就好像电视剧里面被心灵控制的人眼睛是红色的
极验第一个标签是智能组合验证, 第二个标签才是咱们要破解的滑动验证, 所以须要先切换至滑动验证
咱们经过 CSS 选择器选出要点击的标签, 而后使用 WebDriver 点击这个元素
await driver.findElement(webdriver.By.css('.products-content li:nth-child(2)')).click()
来到了滑动行为验证区域, 接下来就要点击验证按钮, 一样也是先用 CSS 选择器选出按钮, 而后再点击
await driver.findElement(webdriver.By.css('.geetest_radar_tip')).click()
到这一步, 滑动验证码已经弹出
咱们碰到了破解过程当中的第一个难点, 图像识别
要知道拼图须要滑动到哪里, 先要知道完整图片以及缺一块的图片, 放一块儿对比, 才能知道滑块须要滑动到哪里
WebDriver 提供了两种截图方式, 一种是全屏截图, 一种是元素截图
这里咱们仅须要获取拼图缺失的背景图, 所以使用元素截图
// 隐藏原图再截图
await driver.executeScript(`document.querySelector('.geetest_canvas_fullbg').style.display = 'none'`)
// 找到验证码背景图元素, 是一个 canvas
const bgCanvas = await driver.findElement(webdriver.By.css('.geetest_canvas_bg'))
// 得到一个 base64 格式的 png 截图
const bgPng = await bgCanvas.takeScreenshot()
小编并不懂图像识别, 为了下降实现难度, 用了一个简单取巧的方法
由于拼图缺失的区域会有 阴影, 而阴影通常比较黑
所以咱们把题目从 寻找拼图丢失区 改为了 寻找比较黑的点
固然这个草率的规则正确率并不高, 但为了demo演示已经足够了
那怎么定义 比较黑 呢? 最简单的方法就是挨个读取图像中的像素
咱们把 R, G, B 三值相加, 数字越小就认为越黑, 最黑的 rgb(0, 0, 0) 就是0
附上小编用的读取像素库 get-pixels
滑动操做使用了 WebDriver 中的 actions api, 能够完成一系列操做, 好比键盘输入, 鼠标移动, 点击等
// 获取拼图滑块按钮
const button = await driver.findElement(webdriver.By.css('.geetest_slider_button'))
// 获取按钮位置等信息
const buttonRect = await button.getRect()
// 初始化 action
let actions = driver.actions({async: true})
// 把鼠标移动到滑块上, 而后点击
actions = actions.move({
x: x + 10,
y: y + 10,
duration: 100
}).press()
// 花一秒钟把滑块拖动至拼图缺失区, 松开鼠标
await actions.move({
x: x + 10 + point.x - 5,
y: y + 10,
duration: 1000
}).release().perform()
写完代码, 执行, 看着拼图顺畅的向前滑动, 等待着奇迹的发生
然而奇迹并无发生, 只有一行黄字映入眼帘
怪物吃了拼图, 请3秒后重试
重复好屡次, 咱们发现
即使彻底吻合, 也没法绕过极验的认证!
低估, 彻底的低估!
拼图吻合只是必要条件, 破解的基础门槛
真正的难点是拖动过程当中的 滑动轨迹!
咱们再次想到了西部世界二, 最后的彩蛋威廉在世外山谷绝望的问艾米莉
威廉: Verify what?
艾米莉: Fidelity
Fidelity, 真实度
破解到了这一步, 可否模仿人类的滑动轨迹成了关键
咱们反复不断的调教滑动代码, again and again
小编进行了屡次尝试, 好比把 move action 分红多段, 按照不一样的速度进行拖动, 甚至加入各类随机数, 但始终没法经过极验的轨迹检查
最后小编仿照微信随机红包的方式, 先把要滑动的距离切成几十份, 而且容许有负数
人在滑动拼图的时候很容易出现, 滑过了再滑动回去的场景, 所以负数能够增长 Fidelity
const count = 30 // 小编分红30步进行滑动
const steps = getSteps(distance, count)
const totalDuration = 8000 // 一共耗时8秒, 慢才能充实轨迹~
_.reduce(steps, (actions, step) => {
return actions.move({
x: x + 10 + step,
y: y + 10 + _.random(-5, 40), // 加上y轴随机数
duration: parseInt(_.random(totalDuration / count / 2, totalDuration / count * 2)) // 加上时长随机数
})
}, actions)
// 随机拆成n份
function getRandomDistribution(total, count) {
let item = total / count
item = item + _.random(-item * 2, item * 3)
item = parseInt(item)
if (count === 1) {
return [total]
} else {
return [item].concat(getRandomDistribution(total - item, count - 1))
}
}
// 获取每次滑动的X坐标
function getSteps(total, count) {
let distribution = getRandomDistribution(total, count)
return _.map(distribution, (item, i) => {
return _.sum(distribution.slice(0, i + 1))
})
}
保存, 执行
小编放下鼠标, 端起桌上的马克杯, 看着 WebDriver 再次控制浏览器
打开网页, 点击按钮, 拖动滑块, 滑块曲折前行...
摩擦摩擦, 似魔鬼的步伐, 似老奶奶颤巍巍的手
终于, 极验显示出一个清爽绿色的横幅, 仿佛在向咱们招手: 欢迎你, 人类