本文将展示如何使用Rust语言实现极验滑动验证码的自动识别。从模拟点击到识别滑动缺口、计算位移并模拟拖动滑块。如果认证失败,则重复调用直到成功。
识别思路
模拟点击切换为滑动验证,并显示验证界面。
识别滑动缺口的位置,计算位移。
模拟拖动滑块。
若认证失败,重复调用。
详细过程及代码
初始化
首先,初始化Selenium对象和一些参数配置,极验验证码测试页面的网址如下:
rust
extern crate webdriver;
extern crate image;
use webdriver::client::sync::{Client, WebDriver};
use std::fs::File;
use std::time::Duration;
use std::thread::sleep;
const BORDER: i32 = 6;
struct CrackGeetest {
url: String,
client: Client,
}
impl CrackGeetest {
fn new() -> CrackGeetest {
let client = Client::new("http://localhost:4444").unwrap();
CrackGeetest {
url: String::from("https://www.geetest.com/type/"),
client,
}
}
fn open(&mut self) {
self.client.goto(&self.url).unwrap();
}
fn close(&mut self) {
self.client.close().unwrap();
}
}
定义了一个 CrackGeetest 结构体,初始化Selenium对象和一些参数配置,网址是极验的验证码测试页面。
模拟点击
首先模拟点击切换为滑动验证,然后模拟点击弹出验证图片。
rust
impl CrackGeetest {
fn change_to_slide(&self) {
self.client.find(Locator::Css(".products-content ul > li:nth-child(2)")).unwrap().click().unwrap();
}
fn get_geetest_button(&self) {
self.client.find(Locator::Css(".geetest_radar_tip")).unwrap().click().unwrap();
}
}
该步骤定义了两个方法,均利用显示等待的方法实现。并返回按钮对象,后用click方法模拟点击。
获取背景图
首先等待验证码加载完成(wait_pic),获取网页截图(get_screenshot),然后获取验证背景图所在的位置及大小参数(get_position)和滑块对象(get_slider)。
rust
impl CrackGeetest {
fn wait_pic(&self) {
self.client.find(Locator::Css(".geetest_popup_wrap")).unwrap();
}
fn get_screenshot(&self) -> image::DynamicImage {
let screenshot = self.client.screenshot().unwrap();
image::load_from_memory(&screenshot).unwrap()
}
fn get_position(&self) -> (u32, u32, u32, u32) {
let img = self.client.find(Locator::Css(".geetest_canvas_img")).unwrap();
sleep(Duration::from_secs(2));
let location = img.location().unwrap();
let size = img.size().unwrap();
(
location.y as u32,
(location.y + size.height) as u32,
location.x as u32,
(location.x + size.width) as u32,
)
}
fn get_slider(&self) {
self.client.find(Locator::Css(".geetest_slider_button")).unwrap();
}
}
再通过上述返回的背景图位置和大小参数,对网页截图进行切片(get_geetest_image),最后获取背景图。
rust
impl CrackGeetest {
fn get_geetest_image(&self, name: &str) -> image::DynamicImage {
let (top, bottom, left, right) = self.get_position();
println!("验证码位置: {} {} {} {}", top, bottom, left, right);
let screenshot = self.get_screenshot();
let captcha = screenshot.crop(left, top, right - left, bottom - top);
let mut output = File::create(name).unwrap();
captcha.write_to(&mut output, image::ImageOutputFormat::Png).unwrap();
captcha
}
}
到这里,已经获取了带缺口的背景图。我们需要获取不带缺口滑块的原图。这里通过改变CSS样式获得原图。
rust
impl CrackGeetest {
fn delete_style(&self) {
self.client.execute("document.querySelectorAll('canvas')[2].style=''", vec![]).unwrap();
}
}
执行js脚本之后(delete_style)获得了无缺口的原图,再调用之前的截图方法,就可以获取同大小的背景图。
识别缺口
我们得到了两张图,接下来对比它们来获取缺口位置。更多内容联系1436423940
rust
fn is_pixel_equal(img1: &image::DynamicImage, img2: &image::DynamicImage, x: u32, y: u32) -> bool {
let pixel1 = img1.get_pixel(x, y).0;
let pixel2 = img2.get_pixel(x, y).0;
let threshold = 60;
(pixel1[0] as i32 - pixel2[0] as i32).abs() < threshold
&& (pixel1[1] as i32 - pixel2[1] as i32).abs() < threshold
&& (pixel1[2] as i32 - pixel2[2] as i32).abs() < threshold
}
get_gap()方法遍历两张图片的每个像素,再利用is_pixel_equal()方法判断两张图片同一位置的像素。
rust
fn get_gap(img1: &image::DynamicImage, img2: &image::DynamicImage) -> u32 {
let mut left = 60;
for i in left..img1.width() {
for j in 0..img1.height() {
if !is_pixel_equal(img1, img2, i, j) {
left = i;
return left;
}
}
}
left
}
模拟拖动
我们获得了滑块的位置,现在只需计算距离并模拟拖动即可。
rust
fn get_track(distance: i32) -> Vec<i32> {
let mut track = Vec::new();
let mut current = 0;
let mid = distance * 3 / 5;
let t = 0.2;
let mut v = 0.0;
let distance = distance + 14;
while current < distance {
let a = if current < mid { 2.0 } else { -1.5 };
let v0 = v;
v = v0 + a * t;
let move_distance = v0 * t + 0.5 * a * t * t;
current += move_distance as i32;
track.push(move_distance as i32);
}
track
}
前3/5路程加速,后面减速,track返回的是一个列表,其中每个元素代表的是每次移动的距离。然后模拟释放鼠标时的人手抖动(shake_mouse)。
rust
fn shake_mouse(client: &Client) {
client.perform_actions().unwrap();
}
fn move_to_gap(client: &Client, tracks: Vec<i32>) {
let back_tracks = vec![-1, -1, -2, -2, -3, -2, -2, -1, -1];
client.perform_actions().unwrap();
for &x in &tracks {
client.mouse_move_by(x, 0).unwrap();
}
for &x in &back_tracks {
client.mouse_move_by(x, 0).unwrap();
}
shake_mouse(client);
sleep(Duration::from_millis(500));
client.perform_actions().unwrap();
}
最后根据之前所得到的运动轨迹拖动滑块(move_to_gap)即可。
整个控制流程
执行主体流程,若验证失败,则再次调用crack()进行识别,直至成功。
rust
impl CrackGeetest {
fn crack(&mut self) {
loop {
self.open();
self.change_to_slide();
self.get_geetest_button();
self.wait_pic();
self.get_slider();
let image1 = self.get_geetest_image("captcha1.png");
self.delete_style();
let image2 = self.get_geetest_image("captcha2.png");
let gap = get_gap(&image1, &image2);
println!("缺口位置: {}", gap);
let gap = gap as i32 - BORDER;
let track = get_track(gap);
move_to_gap(&self.client, track);
let success = self.client.find(Locator::Css(".geetest_success_radar_tip_content")).unwrap().text().unwrap() == "验证成功";
println!("{}", success);
if success {
break;
}
sleep(Duration::from_secs(5));
self.close();
}
}
}
fn main() {
let mut crack = CrackGeetest::new();
crack.crack();
}