我们将展示如何使用Crystal语言实现自动识别极验滑动验证码的全过程,从模拟点击到识别滑动缺口、计算位移并模拟拖动滑块。如果认证失败,则重复调用直到成功。
识别思路
模拟点击切换为滑动验证,并显示验证界面。
识别滑动缺口的位置,计算位移。更多内容联系1436423940
模拟拖动滑块。
若认证失败,重复调用。
详细过程及代码
初始化
首先,初始化selenium对象和一些参数配置,极验验证码测试页面的网址如下:
crystal
require "selenium-webdriver"
require "file_utils"
require "image_magick"
class CrackGeetest
BORDER = 6
def initialize
@url = "https://www.geetest.com/type/"
@browser = Selenium::WebDriver.for :chrome
@wait = Selenium::WebDriver::Wait.new(timeout: 10)
end
def open
@browser.get(@url)
end
def close
@browser.quit
end
end
定义了一个 CrackGeetest 类,初始化selenium对象和一些参数配置,网址是极验的验证码测试页面。
模拟点击
首先模拟点击切换为滑动验证,然后模拟点击弹出验证图片。
crystal
def change_to_slide
huadong = @wait.until { @browser.find_element(css: ".products-content ul > li:nth-child(2)") }
huadong
end
def get_geetest_button
button = @wait.until { @browser.find_element(css: ".geetest_radar_tip") }
button
end
该步骤定义了两个方法,均利用显示等待的方法实现。并返回按钮对象,后用click方法模拟点击。
获取背景图
首先等待验证码加载完成(wait_pic),获取网页截图(get_screenshot),然后获取验证背景图所在的位置及大小参数(get_position)和滑块对象(get_slider)。
crystal
def wait_pic
@wait.until { @browser.find_element(css: ".geetest_popup_wrap") }
end
def get_screenshot
screenshot = @browser.screenshot_as(:png)
File.open("screenshot.png", "wb") { |f| f.write(screenshot) }
screenshot
end
def get_position
img = @wait.until { @browser.find_element(class: "geetest_canvas_img") }
sleep 2
location = img.location
size = img.size
{ top: location.y, bottom: location.y + size.height, left: location.x, right: location.x + size.width }
end
def get_slider
slider = @wait.until { @browser.find_element(class: "geetest_slider_button") }
slider
end
再通过上述返回的背景图位置和大小参数,对网页截图进行切片(get_geetest_image),最后获取背景图。
crystal
def get_geetest_image(name = "captcha.png")
position = get_position
puts "验证码位置: #{position[:top]}, #{position[:bottom]}, #{position[:left]}, #{position[:right]}"
get_screenshot
image = Magick::Image.read("screenshot.png").first.crop(position[:left], position[:top], position[:right] - position[:left], position[:bottom] - position[:top])
image.write(name)
image
end
到这里,已经获取了带缺口的背景图。我们需要获取不带缺口滑块的原图。这里通过改变CSS样式获得原图。
crystal
def delete_style
js = 'document.querySelectorAll("canvas")[2].style=""'
@browser.execute_script(js)
end
执行js脚本之后(delete_style)获得了无缺口的原图,再调用之前的截图方法,就可以获取同大小的背景图。
识别缺口
我们得到了两张图,接下来对比它们来获取缺口位置。
crystal
def is_pixel_equal(img1, img2, x, y)
pix1 = img1.pixel_color(x, y)
pix2 = img2.pixel_color(x, y)
threshold = 60
if (pix1.red - pix2.red).abs < threshold &&
(pix1.green - pix2.green).abs < threshold &&
(pix1.blue - pix2.blue).abs < threshold
true
else
false
end
end
get_gap()方法遍历两张图片的每个像素,再利用is_pixel_equal()方法判断两张图片同一位置的像素。
crystal
def get_gap(img1, img2)
left = 60
(left...img1.columns).each do |i|
(0...img1.rows).each do |j|
unless is_pixel_equal(img1, img2, i, j)
return i
end
end
end
left
end
模拟拖动
我们获得了滑块的位置,现在只需计算距离并模拟拖动即可。
crystal
def get_track(distance)
track = [] of Int32
current = 0
mid = distance * 3 / 5
t = 0.2
v = 0
distance += 14
while current < distance
a = current < mid ? 2 : -1.5
v0 = v
v = v0 + a * t
move = v0 * t + 0.5 * a * t * t
current += move
track << move.round
end
track
end
前3/5路程加速,后面减速,track返回的是一个列表,其中每个元素代表的是每次移动的距离。然后模拟释放鼠标时的人手抖动(shake_mouse)。
crystal
def shake_mouse
action = Selenium::WebDriver::ActionBuilder.new(@browser)
action.move_by(x: -3, y: 0).perform
action.move_by(x: 2, y: 0).perform
end
def move_to_gap(slider, tracks)
action = Selenium::WebDriver::ActionBuilder.new(@browser)
action.click_and_hold(slider).perform
tracks.each do |x|
action.move_by(x: x, y: 0).perform
end
[-1, -1, -2, -2, -3, -2, -2, -1, -1].each do |x|
action.move_by(x: x, y: 0).perform
end
shake_mouse
sleep 0.5
action.release.perform
end
最后根据之前所得到的运动轨迹拖动滑块(move_to_gap)即可。
整个控制流程
执行主体流程,若验证失败,则再次调用crack()进行识别,直至成功。
crystal
def crack
begin
open
change_to_slide.click
get_geetest_button.click
wait_pic
slider = get_slider
image1 = get_geetest_image("captcha1.png")
delete_style
image2 = get_geetest_image("captcha2.png")
gap = get_gap(image1, image2)
puts "缺口位置: #{gap}"
gap -= BORDER
track = get_track(gap)
move_to_gap(slider, track)
success = @wait.until { @browser.find_element(class: "geetest_success_radar_tip_content").text == "验证成功" }
puts success
sleep 5
close
rescue
puts "Failed-Retry"
crack
end
end
if __FILE__ == $0
crack_geetest = CrackGeetest.new
crack_geetest.crack
end