selenium模擬geetest極驗滑動驗證

前言

前一陣子打球認識了一個小學生,爲了騙他錢,吹了好半天,什麼盜取心上人的QQ密碼,喫雞外掛,學校飯卡無限充值,最終達成交易10塊錢幫他刷QQ贊。
回家趕快百度研究了一下,原來刷贊很簡單:很多網站都可以每天免費領取100個贊,心想把這些網站統計一下,寫個程序每天自動提交不就可以了。但是我仔細看了一下,幾乎所有的網站都需要人工驗證才能免費領取,由於暫時我的客戶數量較少,先手工領一下吧。
需要人工驗證

然後又發現一個站可以通過分享推廣鏈接來刷。想起來之前幫別人用PHP模擬IP刷票,同理應該可以用來刷贊。於是簡單寫了個頁面,每天能刷幾千個贊。

給小學生刷了幾萬個贊,取得他的信任後,我說讓他做總代理,也不收他費用了,讓他發展下線,讓他的同學朋友都來刷贊,然後給他15%的抽成,他很高興。

我開始考慮怎麼用程序過人工驗證,這樣才能實現每天免費領取幾百萬個贊,建立我的刷贊帝國。

代碼正文

本文以geetest官方demo爲例,使用nodejs與selenium模擬人工滑動驗證碼。

先百度一番,發現都是一兩年前的文章,沒有現成的代碼。讀來讀去其實思路都差不多:

  1. 模擬點擊
  2. 獲取缺口的座標位置
  3. 模擬鼠標拖動滑塊

一、模擬點擊

selenium和nodejs的安裝使用就不贅述了,官網文檔都有。

新建一個firefox實例:

let options = new firefox.Options()
.setProfile('C:/Users/2bt/AppData/Roaming/Mozilla/Firefox/Profiles/m1mwcwm8.default')
let driver = await new webdriver.Builder().forBrowser('firefox').setFirefoxOptions(options).build()

我這裏添加的設置項是爲了每次調試的時候在firefox中改動的參數保留下來,比如瀏覽器的位置,大小。不然每次啓動firefox都是新的臨時窗口。其次是爲了加載我專門爲了方便測試寫的一個模擬鼠標指針的擴展插件(selenium不顯示鼠標的移動軌跡),但比較鬱悶的是自己寫的擴展沒法安裝。

打開極驗首頁,並且把驗證區域置頂:

await driver.get('http://www.geetest.com/en/')

// 置頂DOM
// await driver.executeScript('window.scrollBy(0, 5142)')
await driver.executeScript('document.querySelector(".gt-en--experience").style.position = "absolute";document.querySelector(".gt-en--experience").style.top = 0')

註釋掉的那個代碼可能會因爲瀏覽器/分辨率/系統的不同產生誤差,所以直接把頁面CSS的position設置爲absolute放在頁面頂部就可以。這樣也爲了方便後面截取缺口的圖片(若使用第一種方案,直接截圖得不到想要的區域,需要截取完整的頁面圖片,然後計算缺口在頁面的相對位置,再裁剪圖片才能得到,很麻煩)。
置頂DOM
選擇SLIDE方式驗證並且點擊驗證按鈕:

let slide = await driver.wait(webdriver.until.elementLocated(webdriver.By.css('.experience-box .box-left ul li:nth-child(2)')), 10000)
await slide.click();
console.log('選擇滑動驗證')

let btn = await driver.wait(webdriver.until.elementLocated(webdriver.By.css('.geetest_btn')), 10000);
await btn.click()
console.log('點擊驗證')

驗證要開始了
加個監聽鼠標移動的特效,模擬鼠標指針:

await driver.executeScript('ox=document.createElement("div");oy=document.createElement("div");tip=document.createElement("div");ox.style.width="100%";ox.style.pointerEvents="none";ox.style.height="1px";ox.style.zIndex=32767;ox.style.backgroundColor="#ddd";ox.style.position="fixed";ox.style.left=0;document.body.appendChild(ox);oy.style.pointerEvents="none";oy.style.height="100%";oy.style.width="1px";oy.style.zIndex=32767;oy.style.backgroundColor="#ddd";oy.style.position="fixed";oy.style.top=0;document.body.appendChild(oy);tip.id="sele-mouse";tip.style.position="fixed";tip.style.top=0;tip.style.left=0;tip.style.zIndex=32767;tip.style.backgroundColor="#fffa";document.body.appendChild(tip);document.οnmοusemοve=function(e){e=e||event;x=e.pageX;y=e.pageY-document.documentElement.scrollTop;ox.style.top=y+"px";oy.style.left=x+"px";document.getElementById("sele-mouse").innerHTML="x:"+x+"<br/>y:"+y;};document.querySelector(".geetest_slider_button").addEventListener("mousemove",function(e){e=e||event;x=e.pageX;y=e.pageY-document.documentElement.scrollTop;ox.style.top=y+"px";oy.style.left=x+"px";document.getElementById("sele-mouse").innerHTML="x:"+x+"<br/>y:"+y},true);document.querySelector(".geetest_slider_button").addEventListener("mousedown",function(e){ox.style.backgroundColor="red";oy.style.backgroundColor="red";},true);document.querySelector(".geetest_slider_button").addEventListener("mouseup",function(e){ox.style.backgroundColor="#ddd";oy.style.backgroundColor="#ddd";},true);')

這樣selenium做模擬鼠標移動操作的時候,就能方便觀察了。

二、獲取缺口的座標位置

先找到驗證碼背景圖的頁面DOM, 是一個 canvas,隱藏滑塊後截圖:

const bgCanvas =  await driver.wait(webdriver.until.elementLocated(webdriver.By.css('.geetest_window')), 10000)

//把滑塊的透明度改成0
await driver.executeScript('document.querySelector(".geetest_canvas_bg").style.opacity = 1;document.querySelector(".geetest_canvas_slice").style.opacity = 0;')
const bgPng = await bgCanvas.takeScreenshot()

把截取的圖片保存後分析出缺口座標/位置:

fs.writeFile("bg.png", bgPng,{encoding: 'base64'}, function(err) {
	if(err){
		console.log('截取背景圖片錯誤')
	}else{
		getPixels('bg.png',function(err, pixels) {
			if(err) {
				console.log("讀取背景缺口位置錯誤")
				return
			}
			bgX = getBoundary(pixels,getGrey(pixels)/10*3)
			console.log('讀取背景缺口邊界X座標: ',bgX);
		})

	}
});

//獲取灰度值
function getGrey(pixels){
	var rgb = 0;
	for(var i=0; i < pixels.shape[0]; i++) {
		for(var j=0; j < pixels.shape[1]; j++) {
			rgb += pixels.get(i,j,0) + pixels.get(i,j,1) + pixels.get(i,j,2)
		}
	}

	return Math.floor(rgb/pixels.shape[0]/pixels.shape[1])
}

//getBoundary查找邊界函數。  
function getBoundary(pixels, level){
	var lastY=0, count=0;

	for(var i=0; i < pixels.shape[0]; i++) {
		for(var j=0; j < pixels.shape[1]; j++) {
			var rgb = pixels.get(i,j,0) + pixels.get(i,j,1) + pixels.get(i,j,2)
			if(rgb < level && lastY+1 == j) {
				count++;
        //console.log(rgb, i,j ,count);
    }
    else count = 0;
    lastY = j;
    if(count > 6) return i;
}
}

return 0;
}

背景圖片
我把這個截取到的圖片稱爲”缺口背景圖片”,下文還會出現“缺口圖片

分析圖片像素用的是get-pixels。後面的getBoundary和getGrey這兩個函數不太精確,但應付demo效果還差不多。
getBoundary的原理是讀取圖片所有像素相對黑的點(RGB加起來小於一定的值,純黑的RGB和是0),如果滿足多個點位處於同一條豎線,也就是X座標一致,Y座標相鄰,就判定這是缺口的一個邊。
getGrey是獲取圖片的灰度值,原理是把所有的像素點RGB值加起來,然後除以全部點數,得到一個平均值。這個函數是爲了輔助getBoundary,因爲不同顏色深淺的帶有缺口的圖片,缺口位置的黑色濃度也不一樣,有時候特別黑,有時一般,如果不變通,有時會取不到邊界座標。

由於拖動滑塊時會產生一個隨機的位移,計算需要模擬拖動的距離要把這個位移去掉:

// 獲取拼圖滑塊按鈕
const button = await driver.wait(webdriver.until.elementLocated(webdriver.By.css('.geetest_slider_button')), 5000)
// 初始化 action
let actions = driver.actions({async: true})

console.log('開始模擬拖動')
// 把鼠標移動到滑塊上, 然後點擊
await actions.move({
	origin: button,
	duration: 1000
}).pause(100).press().move({
	origin: button,
	x: 1,
	duration: 10
}).pause(300).perform()

這裏模擬滑動了1px,可以看到缺口圖片隨機向右移動了一段距離,這時把背景隱藏,再去單獨截取“缺口圖片”。
缺口圖片
缺口圖片的邊界座標很容易獲取,因爲背景色是白色的,相對黑的像素很容易找,PS裏面取左邊陰影的RGB均爲118,所以這裏用的是固定值360,基本不會出現偏差。

		getPixels('slice.png',function(err, pixels) {
			if(err) {
				console.log("讀取初始位置錯誤")
				return
			}

			dragX = getBoundary(pixels,360)
		})


		//需要滑動的距離
		distance = bgX - dragX;

有了“缺口圖片”和“缺口背景圖片”的邊界座標,我們接下來就可以模擬鼠標拖動滑塊了。

三、模擬鼠標拖動滑塊

我剛開始採用的是直接勻速拖動:

async function move(distance){
	await driver.sleep(1000)
	let actions = driver.actions({bridge: true})
	await actions.move({
		origin: webdriver.Origin.POINTER,
		x:distance,
		duration:1000
	}).release().perform()
	console.log("滑動距離: ",distance)
}

這樣當然是不行的,太機器化了,驗證無法通過。

然後我就依照網上的思路,讓鼠標先做勻加速運動,超過缺口一段距離後,然後再拽回缺口:

function getTrack(distance){
        var track = [],
        current = 0,
        mid = distance * 4 / 5,
        t = 0.4,
        v = 0;

        while (current < distance){
        	if(current < mid) a = 2
        	else a = -3
        	v0 = v
    		v = v0 + a * t
    		move = v0 * t + 1 / 2 * a * t * t
    		current += move
    		track.push(Math.round(move))
        }
        return track
}

通過這個getTrack函數,返回單位時間運動距離的數組,按照數組去拖拽滑塊就莫得問題了。

模擬拖拽有個大坑就是,可能不同環境分辨率有差別,canvas截圖取得的圖片是325200像素的,網頁中canvas的大小是260160,呈5:4的比例,導致我從截取的圖片計算出來的距離,模擬拖拽的時候總是會拖拽過頭,我一直以爲是geetest爲了防止機器加入的隨機量,費了好大的勁寫了很多檢測方法才搞明白是比例問題,最終把計算出來的距離按比例縮小就可以得到精確值。

最終效果如下圖:
效果
由於圖片取邊界算法以及計算運動位移函數的粗略,導致模擬拖拽會產生微小的偏差,識別率還沒達到50%。還有圖片存儲過程等方法需要進一步優化,下一篇刷贊程序待續。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章