最近西部世界第二季很火, 小編常常分不清誰是機器人誰是真人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 再次控制瀏覽器
打開網頁, 點擊按鈕, 拖動滑塊, 滑塊曲折前行...
摩擦摩擦, 似魔鬼的步伐, 似老奶奶顫巍巍的手
終於, 極驗顯示出一個清爽綠色的橫幅, 彷彿在向咱們招手: 歡迎你, 人類