Playwright是微软最新推出的一款Web自动化工具,个人感觉相比Selenium要更好用一些,不用去管chromedriver和浏览器版本对应的问题了,当然还有很多强大的地方可以去我之前写过的一篇文章简单进行了解。
在网上也看了很多过滑块验证的例子,一直没有找到C#版的Playwright过滑块的案例,其它语言的修改拿来用全都不行因此干脆自己写了一个。京东滑块验证亲测可过,理论这个逻辑所有类似滑块验证都可以过(自行测试),牵扯到滑动轨迹逻辑有时候可能要很多次才能过(只是简单的实现了随机滑动轨迹没有写的很复杂可自行修改完善)
1.实现思路
- 分别拿到大图和小图
- 两张图进行灰度处理降低颜色偏差,然后在大图中找小图的位置从而得到缺口的位置
- 模拟人工进行滑动滑块直到验证通过
2.代码实现
过滑块逻辑
using var playwright = await Playwright.CreateAsync();
await using var browser = await playwright.Chromium.LaunchAsync(new BrowserTypeLaunchOptions
{
Headless = false,
Args = new[] { "--disable-blink-features=AutomationControlled" },
});
await using var context = await browser.NewContextAsync(new BrowserNewContextOptions()
{
//1920*1080窗口大小启动
ViewportSize = new ViewportSize()
{
Height = 1080,
Width = 1920
}
});
var page = await context.NewPageAsync();
//打开网页
await page.GotoAsync("https://passport.jd.com/new/login.aspx");
//切换到账户登录
await page.ClickAsync("//*[contains(text(),'账户登录')]");
//输入账号
await page.FillAsync("//input[contains(@placeholder,'邮箱/账号名/登录手机')]", "username");
//输入密码
await page.FillAsync("//input[contains(@placeholder,'密码')]", "password");
//点击登录
await page.ClickAsync("//a[@id='loginsubmit']");
//等待出滑块验证
await page.WaitForSelectorAsync("//div[@class='JDJRV-bigimg']");
//获取滑块按钮的尺寸
var slidebtn = await page.Locator("//div[@class='JDJRV-slide-inner JDJRV-slide-btn']").BoundingBoxAsync();
if (slidebtn == null)
throw new Exception("没有找到滑块按钮!");
int slidey = (int)(slidebtn.Y + slidebtn.Height / 2);
//滑块验证没有通过一直重试
while (await page.QuerySelectorAsync("//div[@class='JDJRV-slide-center']") != null)
{
//获取大图及尺寸
var bigImgObj = page.Locator("//div[@class='JDJRV-bigimg']/img");
var bigImgSize = await page.EvaluateAsync<dynamic>(@"()=>{
var obj=$('div[class=JDJRV-bigimg]')
return {width:$(obj).width(),height:$(obj).height()}
}");
var bigImgBase64 = bigImgObj.GetAttributeAsync("src").Result;
var smallImgObj = page.Locator("//div[@class='JDJRV-smallimg']/img");
//获取缺口应填充小图及尺寸
var smallImgSize = await page.EvaluateAsync<dynamic>(@"()=>{
var obj=$('div[class=JDJRV-smallimg]')
return {width:$(obj).width(),height:$(obj).height()}
}");
var smallImgBase64 = smallImgObj.GetAttributeAsync("src").Result;
if (string.IsNullOrWhiteSpace(bigImgBase64) || string.IsNullOrWhiteSpace(smallImgBase64))
{
throw new Exception("滑块图片未找到!");
}
//大小图分别转换为Bitmap
var bigImg = Helper.Base64ToImage(bigImgBase64, new System.Drawing.Size((int)bigImgSize.width, (int)bigImgSize.height));
var smallImg = Helper.Base64ToImage(smallImgBase64, new System.Drawing.Size((int)smallImgSize.width, (int)smallImgSize.height));
//计算缺口的位置
var point = Helper.FindSliderNotch(bigImg, smallImg);
//移动到滑块按钮位置并按下
await page.Mouse.MoveAsync(slidebtn.X + slidebtn.Width / 2, slidey);
await page.Mouse.DownAsync();
//模仿人工滑动
Random random = new Random();
//已滑动长度
int offset = 0;
const int minOffset = 10;
const int maxOffset = 30;
//当滑动的距离小于缺口的x坐标一直向前滑动
while (point.X > offset)
{
//随机滑动长度
offset += random.Next(minOffset, maxOffset);
float moveX = slidebtn.X + offset;
//滑动(因为我们一开始是从滑块中间按下拖动的 所以这里移动的时候x要加上滑块按钮长度的一半)
await page.Mouse.MoveAsync(moveX + slidebtn.Width / 2, slidey, new MouseMoveOptions { Steps = 10 });
//随机等待一段时间
await page.WaitForTimeoutAsync(offset * minOffset);
}
//上面循环最终会滑动超过一点距离 这里精准往后退回来(模拟人工)
await page.Mouse.MoveAsync(slidebtn.X + point.X + slidebtn.Width / 2, slidey, new MouseMoveOptions { Steps = 10 });
//松开滑块按钮
await page.Mouse.UpAsync();
}
Console.ReadKey();
Console.WriteLine("执行完毕!");
辅助方法
查找缺口位置用到了OpenCvSharp 需要安装以下包
/// <summary>
/// 查找滑块缺口位置
/// 实现原理:
/// 两张图进行灰度处理降低颜色偏差,然后大图中找小图的位置
/// </summary>
/// <param name="big">背景图</param>
/// <param name="small">缺口图</param>
/// <returns></returns>
public static OpenCvSharp.Point FindSliderNotch(Bitmap big, Bitmap small)
{
var bigImg = BitmapConverter.ToMat(big);//Cv2.ImRead(big);
var smallImg = BitmapConverter.ToMat(small); //Cv2.ImRead(small);
var big_edge = new Mat();
Cv2.Canny(bigImg, big_edge, 50, 150);
var small_edge = new Mat();
Cv2.Canny(smallImg, small_edge, 50, 150);
var bigPic = new Mat();
Cv2.CvtColor(big_edge, bigPic, ColorConversionCodes.GRAY2BGR);
var smallPic = new Mat();
Cv2.CvtColor(small_edge, smallPic, ColorConversionCodes.GRAY2BGR);
var res = new Mat();
Cv2.MatchTemplate(bigPic, smallPic, res, TemplateMatchModes.CCoeffNormed);
double minval, maxval;
OpenCvSharp.Point minloc, maxloc;
Cv2.MinMaxLoc(res, out minval, out maxval, out minloc, out maxloc);
#region 绘制最佳匹配矩形并展示匹配效果
//Rect r = new Rect(new OpenCvSharp.Point(maxloc.X, maxloc.Y), new OpenCvSharp.Size(smallImg.Width, smallImg.Height));
//Console.WriteLine(maxloc.X);
////Draw a rectangle of the matching area
//Cv2.Rectangle(bigImg, r, Scalar.LimeGreen, 2);
////Fill in the res Mat so you don't find the same area again in the MinMaxLoc
//Rect outRect;
//Cv2.FloodFill(res, maxloc, new Scalar(0), out outRect, new Scalar(0.1), new Scalar(1.0));
//Cv2.ImShow("Matches", bigImg);
//Cv2.WaitKey();
#endregion
return maxloc;
}
/// <summary>
/// Base64转Bitmap
/// </summary>
/// <param name="dataURL"></param>
/// <param name="size"></param>
/// <returns></returns>
public static Bitmap Base64ToImage(string dataURL, System.Drawing.Size size)
{
try
{
dataURL = dataURL.Replace("data:image/png;base64,", "").Replace("data:image/jgp;base64,", "").Replace("data:image/jpg;base64,", "").Replace("data:image/jpeg;base64,", "");//将base64头部信息替换
byte[] bytes = Convert.FromBase64String(dataURL);
MemoryStream memStream = new MemoryStream(bytes);
Image mImage = Image.FromStream(memStream);
Bitmap bp = new Bitmap(mImage, size);
return bp;
}
catch (Exception)
{
throw;
}
}
评论区