[C#]滑动拼图验证码
极验验证码需要模拟人工操作,参考了以下例子:
https://www.cnblogs.com/bat1989/p/12661153.html
主要修改了验证的部分:
环境:.net core3.1
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Threading;
using OpenQA.Selenium;
using OpenQA.Selenium.Interactions;
using OpenQA.Selenium.Remote;
using OpenQA.Selenium.Support.Extensions;
using OpenQA.Selenium.Support.UI;
namespace AgentCrawler
{
public interface ISlideVerificationCode
{
bool Pass(RemoteWebDriver remoteWebDriver);
}
public class SeleniumVertifyCode : ISlideVerificationCode
{
#region 属性
/// <summary>
/// 拖动按钮
/// </summary>
private const string SlidButton = "gt_slider_knob";
/// <summary>
/// 原始图层
/// </summary>
private const string OriginalMap = "gt_fullbg";
/// <summary>
/// 原始图加缺口背景图
/// </summary>
private const string NewMap = "gt_bg";
/// <summary>
/// 缺口图层
/// </summary>
private const string SliceMap = "gt_slice";
private const int WaitTime = 100;
/// <summary>
/// 重试次数
/// </summary>
private const int TryTimes = 6;
/// <summary>
/// 缺口图默认偏移像素
/// </summary>
private const int LeftOffset = 4;
private const string FullScreenPath = "全屏.png";
private const string OriginalMapPath = "原图.png";
private const string NewMapPath = "新图.png";
#endregion 属性
public void StartGeeTest(Uri uri)
{
const int waitTime = 500;
var options = new OpenQA.Selenium.Chrome.ChromeOptions();
//options.AddArgument("-headless");
options.AddArgument("--window-size=1920,1050");
options.AddArgument("log-level=3");
using OpenQA.Selenium.Chrome.ChromeDriver driver = new OpenQA.Selenium.Chrome.ChromeDriver(options);
driver.Navigate().GoToUrl(uri);
driver.ExecuteJavaScript("Object.defineProperty(navigator, 'webdriver', {get: () => undefined})");
driver.Navigate().GoToUrl(uri);
driver.ExecuteScript("header.loginLink(event)");
Console.WriteLine("点击《登录/注册》按钮");
Thread.Sleep(waitTime);
driver.ExecuteScript("loginObj.changeCurrent(1);");
Console.WriteLine("点击 《密码登录》按钮");
Thread.Sleep(waitTime);
driver.ExecuteScript("$('.contactphone').val('account')");
driver.ExecuteScript("$('.contactword').val('2020')");
Console.WriteLine("输入账号密码");
Thread.Sleep(waitTime);
driver.ExecuteScript("loginObj.loginByPhone(event);");
Console.WriteLine("点击《登录》按钮");
Thread.Sleep(waitTime);
SeleniumVertifyCode slideVerificationCode = new SeleniumVertifyCode();
var flag = slideVerificationCode.Pass(driver);
Console.WriteLine($"验证结果{flag}");
}
public bool Pass(RemoteWebDriver remoteWebDriver)
{
const int waitTime = 6000;
int failTimes = 0;
bool flag = false;
do
{
ScreenMap(remoteWebDriver);
var distance = GetDistance();
if (distance > 0)
{
Console.WriteLine($"开始获取移动轨迹...");
var moveEntitys = GetMoveEntities(distance);
Move(remoteWebDriver, moveEntitys);
Console.WriteLine("休眠3秒,显示等待提交验证码...");
Thread.Sleep(waitTime);
Console.WriteLine("开始检查认证是否通过...");
flag = CheckSuccess(remoteWebDriver);
if (flag)
{
break;
}
}
} while (++failTimes < TryTimes);
return flag;
}
public void GetCookie(RemoteWebDriver remoteWebDriver)
{
List<string> list = new List<string>();
foreach (var item in remoteWebDriver.Manage().Cookies.AllCookies)
{
list.Add($"{item.Name}={item.Value}");
}
System.IO.File.WriteAllLines("Cookie.txt", list);
}
protected virtual bool CheckSuccess(RemoteWebDriver remoteWebDriver)
{
const int waitTime = 6000;
try
{
remoteWebDriver.FindElement(By.ClassName(SlidButton));
Console.WriteLine("验证失败,显示等待6秒刷新验证码...");
Thread.Sleep(waitTime);
return false;
}
catch (NoSuchElementException)
{
GetCookie(remoteWebDriver);
return true;
}
}
private void Move(RemoteWebDriver remoteWebDriver, List<MoveEntity> moveEntities)
{
var slidButton = GetSlidButtonElement(remoteWebDriver);
Actions builder = new Actions(remoteWebDriver);
builder.ClickAndHold(slidButton).Perform();
int offset = 0;
int index = 0;
foreach (var item in moveEntities)
{
index++;
builder = new Actions(remoteWebDriver);
builder.MoveByOffset(item.X, item.Y).Perform();
Console.WriteLine("向右总共移动了:" + (offset = offset + item.X));
}
builder.Release().Perform();
}
private List<MoveEntity> GetMoveEntities(int distance)
{
List<MoveEntity> moveEntities = new List<MoveEntity>();
int allOffset = 0;
do
{
int offset = 0;
double offsetPercentage = allOffset / (double)distance;
if (offsetPercentage > 0.5)
{
if (offsetPercentage < 0.85)
{
offset = new Random().Next(10, 20);
}
else
{
offset = new Random().Next(2, 5);
}
}
else
{
offset = new Random().Next(20, 30);
}
allOffset += offset;
int y = (new Random().Next(0, 1) == 1 ? new Random().Next(0, 2) : 0 - new Random().Next(0, 2));
moveEntities.Add(new MoveEntity(offset, y, offset));
} while (allOffset <= distance + 5);
var moveOver = allOffset > distance;
for (int j = 0; j < Math.Abs(distance - allOffset);)
{
int step = 3;
int offset = moveOver ? -step : step;
int sleep = new Random().Next(100, 200);
moveEntities.Add(new MoveEntity(offset, 0, sleep)); ;
j = j + step;
}
return moveEntities;
}
/// <summary>
/// 比较两张图片的像素,确定阴影图片位置
/// </summary>
/// <param name="oldBmp"></param>
/// <param name="newBmp"></param>
/// <returns></returns>
private int GetArgb(Bitmap oldBmp, Bitmap newBmp)
{
//由于阴影图片四个角存在黑点(矩形1*1)
for (int i = 0; i < newBmp.Width; i++)
{
for (int j = 0; j < newBmp.Height; j++)
{
if ((i >= 0 && i <= 1) && ((j >= 0 && j <= 1) || (j >= (newBmp.Height - 2) && j <= (newBmp.Height - 1))))
{
continue;
}
if ((i >= (newBmp.Width - 2) && i <= (newBmp.Width - 1)) && ((j >= 0 && j <= 1)
|| (j >= (newBmp.Height - 2) && j <= (newBmp.Height - 1))))
{
continue;
}
//获取该点的像素的RGB的颜色
Color oldColor = oldBmp.GetPixel(i, j);
Color newColor = newBmp.GetPixel(i, j);
if (Math.Abs(oldColor.R - newColor.R) > 60 || Math.Abs(oldColor.G - newColor.G) > 60
|| Math.Abs(oldColor.B - newColor.B) > 60)
{
return i;
}
}
}
return 0;
}
/// <summary>
/// 获取实际图层缺口实际距离
/// </summary>
/// <returns></returns>
private int GetDistance()
{
using Bitmap oldBitmap = (Bitmap)Image.FromFile(OriginalMapPath);
using Bitmap newBitmap = (Bitmap)Image.FromFile(NewMapPath);
var distance = GetArgb(oldBitmap, newBitmap);
distance -= LeftOffset;
Console.WriteLine($"缺口距离{distance}");
return distance;
}
/// <summary>
/// 截图
/// </summary>
/// <param name="remoteWebDriver"></param>
private void ScreenMap(RemoteWebDriver remoteWebDriver)
{
//显示原始图
ShowOriginalMap(remoteWebDriver);
//全屏截图
FullScreen(remoteWebDriver);
//获取原始图层
var originalElement = GetOriginalElement(remoteWebDriver);
//保存原始图
CutBitmap(FullScreenPath, OriginalMapPath, originalElement, remoteWebDriver);
//显示新图层
ShowNewMap(remoteWebDriver);
//全屏截图
FullScreen(remoteWebDriver);
//获取新图层
var newElement = GetNewMapElement(remoteWebDriver);
//保存新图
CutBitmap(FullScreenPath, NewMapPath, newElement, remoteWebDriver);
//显示缺口图
ShowSliceMap(remoteWebDriver);
}
/// <summary>
/// 截图
/// </summary>
/// <param name="sourcePath"></param>
/// <param name="targetPath"></param>
/// <param name="webElement"></param>
private void CutBitmap(string sourcePath, string targetPath, IWebElement webElement, RemoteWebDriver remoteWebDriver)
{
using var bitmap = (Bitmap)Image.FromFile(sourcePath);
using var newBitmap = bitmap.Clone(new Rectangle(webElement.Location, webElement.Size),
System.Drawing.Imaging.PixelFormat.DontCare);
newBitmap.Save(targetPath);
byte[] byteArray = ((ITakesScreenshot)remoteWebDriver).GetScreenshot().AsByteArray;
System.Drawing.Bitmap screenshot = new System.Drawing.Bitmap(new System.IO.MemoryStream(byteArray));
System.Drawing.Rectangle croppedImage = new System.Drawing.Rectangle
(webElement.Location, webElement.Size);
screenshot = screenshot.Clone(croppedImage, screenshot.PixelFormat);
screenshot.Save(targetPath + ".jpg");
}
/// <summary>
/// 全屏截图
/// </summary>
/// <param name="remoteWebDriver"></param>
private void FullScreen(RemoteWebDriver remoteWebDriver)
{
remoteWebDriver.GetScreenshot().SaveAsFile(FullScreenPath);
}
/// <summary>
/// 获取原始图层元素
/// </summary>
/// <param name="remoteWebDriver"></param>
/// <returns></returns>
protected virtual IWebElement GetOriginalElement(RemoteWebDriver remoteWebDriver)
{
return remoteWebDriver.FindElementExtension(By.ClassName(OriginalMap));
}
/// <summary>
/// 获取原始图加缺口背景图元素
/// </summary>
/// <param name="remoteWebDriver"></param>
/// <returns></returns>
protected virtual IWebElement GetNewMapElement(RemoteWebDriver remoteWebDriver)
{
return remoteWebDriver.FindElementExtension(By.ClassName(NewMap));
}
/// <summary>
/// 获取缺口图层元素
/// </summary>
/// <param name="remoteWebDriver"></param>
/// <returns></returns>
protected virtual IWebElement GetSliceMapElement(RemoteWebDriver remoteWebDriver)
{
return remoteWebDriver.FindElementExtension(By.ClassName(SliceMap));
}
/// <summary>
/// 获取拖动按钮元素
/// </summary>
/// <param name="remoteWebDriver"></param>
/// <returns></returns>
protected virtual IWebElement GetSlidButtonElement(RemoteWebDriver remoteWebDriver)
{
return remoteWebDriver.FindElementExtension(By.ClassName(SlidButton));
}
/// <summary>
/// 显示原始图层
/// </summary>
/// <param name="remoteWebDriver"></param>
protected virtual bool ShowOriginalMap(RemoteWebDriver remoteWebDriver)
{
remoteWebDriver.ExecuteScript
("$('." + NewMap + "').hide();$('." + OriginalMap + "').show();$('." + SliceMap + "').hide();");
Console.WriteLine("显示原始图");
Thread.Sleep(WaitTime);
return true;
}
/// <summary>
/// 显示原始图加缺口背景之后的图层
/// </summary>
/// <param name="remoteWebDriver"></param>
/// <returns></returns>
protected virtual bool ShowNewMap(RemoteWebDriver remoteWebDriver)
{
remoteWebDriver.ExecuteScript
("$('." + NewMap + "').show();$('." + OriginalMap + "').hide();$('." + SliceMap + "').hide();");
Console.WriteLine("显示原始图加缺口背景之后的图层");
Thread.Sleep(WaitTime);
return true;
}
/// <summary>
/// 显示缺口图
/// </summary>
/// <param name="remoteWebDriver"></param>
/// <returns></returns>
protected virtual bool ShowSliceMap(RemoteWebDriver remoteWebDriver)
{
remoteWebDriver.ExecuteScript("$('." + SliceMap + "').show();");
Console.WriteLine("显示原始图加缺口背景之后的图层");
Thread.Sleep(WaitTime);
return true;
}
}
internal class MoveEntity
{
public int X;
public int Y;
public int sleep;
public MoveEntity(int offset, int v, int sleep)
{
this.X = offset;
this.Y = v;
this.sleep = sleep;
}
}
public static class WebElementExtensions
{
public static IWebElement FindElementExtension(this IWebDriver driver, By by, int timeoutInSeconds = 10)
{
var wait = new WebDriverWait(driver, TimeSpan.FromSeconds(timeoutInSeconds));
return wait.Until(d => driver.FindElement(by));
}
}
}
调用:
StartGeeTest(new Uri("https://www.tianyancha.com/"));
到这篇发布时间,通过率95%以上