c# Selenium 如何模拟滑动geetest 验证码

 参考资料https://www.cnblogs.com/hujunmin/p/11506958.html,原博主贴出来的关键代码,但是并不完整。我将补全部分代码,能正常运行。

      Nuget: Selenium.WebDriver,Selenium.WebDriver.ChromeDriver

思路:

一:获取原始图片,如下图:(图1)

二:获取原始图加缺口图叠加后的图片

随意拖动一次后,得到下图(图2):

通过JS控制CSS隐藏上图中红色块后,得到原始图加缺口图组合后的图,如下图:(图3)

三:对比前2步骤的图片,获取缺口位置

对比 图1 图3,获得缺口在图片的X坐标

四:减去左边偏移量,获得移动距离

减去 图2 中缺口图起点X坐标(4px)  

五:根据移动距离,计算移动轨迹

极验验证码后台对滑动轨迹有验证。若是通过代码直接匀速直接移动到指定位置,会提示:“图片被怪物吃掉了”。所以要程序模拟认为滑动操作:

离缺口位置远,移动速度快。

离缺口位置近,移动速度慢。

需要模拟不小心超过指定位置,然后再慢慢回头对缺口操作。

六:根据移动轨迹拖动滑块

调用 Actions 的 MoveByOffset,按照移动轨迹一步步移动。注意:每次移动后 Actions  要重新 new ,否则会对不上缺口。具体原因自己去找资料(重复 MoveByOffset ,每次移动是之前的累计值)

七:判断拖动滑块后是否验证通过,若不通过,重试

 拖动后,判读这个按钮是否还存在

至此,思路完毕。

注意:

原始图

https://static.geetest.com/pictures/gt/969ffa43c/969ffa43c.webp

原始图加缺口图组合后的组合图

https://static.geetest.com/pictures/gt/969ffa43c/bg/a0a1cdb4c.webp

两个图片是无序的,和我在浏览器上看到的不一致。

所以对比图片的时候,需要将无序图片转成正常的图片。

转换思路一:

经分析极验验证码是把图片分成52块小图片,按照指定顺序打乱后,通过css再重新排序显示的。

知道图片规则,我们就按照这个规则,把图片切成52个小图,然后排序再组合成一张有序的原始图。

转换思路二:

直接去浏览器上通过显示隐藏不同的图片,然后截图对比(目前我代码这个思路处理的)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Threading;
using OpenQA.Selenium;
using OpenQA.Selenium.Interactions;
using OpenQA.Selenium.Remote;
  
namespace Sniffer.VerificationCode.VerificationCodes
{
    public class GeetestSlideVerificationCode : ISlideVerificationCode
    {
        #region 属性
        /// <summary>
        /// 拖动按钮
        /// </summary>
        private string _slidButton = "gt_slider_knob";
        /// <summary>
        /// 原始图层
        /// </summary>
        private string _originalMap = "gt_fullbg";
        /// <summary>
        /// 原始图加缺口背景图
        /// </summary>
        private string _newMap = "gt_bg";
        /// <summary>
        /// 缺口图层
        /// </summary>
        private string _sliceMap = "gt_slice";
        /// <summary>
        /// 重试次数
        /// </summary>
        private int _tryTimes = 6;
        /// <summary>
        /// 缺口图默认偏移像素
        /// </summary>
        private int _leftOffset = 4;
  
        private string _fullScreenPath = AppDomain.CurrentDomain.BaseDirectory + "全屏.png";
        private string _originalMapPath = AppDomain.CurrentDomain.BaseDirectory + "原图.png";
        private string _newMapPath = AppDomain.CurrentDomain.BaseDirectory + "新图.png";
        #endregion
  
        public bool Pass(RemoteWebDriver remoteWebDriver)
        {
            int failTimes = 0;
            bool flag = false;
            do
            {
                //#TODO 检查图层是否正常弹出
                //截图
                Console.WriteLine("开始截图...");
                ScreenMap(remoteWebDriver);
  
                Console.WriteLine("开始计算距离...");
                //获取缺口图层位移距离
                var distance = GetDistance();
  
                //获取移动轨迹
                Console.WriteLine("开始获取移动轨迹...");
                var moveEntitys = GetMoveEntities(distance);
  
                //移动
                Console.WriteLine("开始移动...");
                Move(remoteWebDriver, moveEntitys);
  
                Console.WriteLine("休眠3秒,显示等待提交验证码...");
                Thread.Sleep(3000);
  
                Console.WriteLine("开始检查认证是否通过...");
                //检查移动是否成功
                flag = CheckSuccess(remoteWebDriver);
                if (flag)
                    break;
            while (++failTimes < _tryTimes);
            return flag;
        }
        #region 内部方法
        protected  virtual bool CheckSuccess(RemoteWebDriver remoteWebDriver)
        {
            //WebDriverWait wait = new WebDriverWait(remoteWebDriver, TimeSpan.FromSeconds(5));
            //IWebElement gt_ajax_tip = null;
            //gt_ajax_tip = wait.Until<IWebElement>((d) =>
            //{
            //    try
            //    {
            //        return d.FindElement(By.CssSelector(".gt_holder .gt_ajax_tip.gt_success"));
            //    }
            //    catch (Exception ex)
            //    {
            //        return null;
            //    }
            //});
            //if (gt_ajax_tip == null)
            //{
            //    Console.WriteLine("验证失败,显示等待6秒刷新验证码...");
            //    Thread.Sleep(6000);
            //    return false;
            //}
            //else
            //{
            //    return true;
            //}
  
            var gt_slider_knob = remoteWebDriver.FindElementExt(By.ClassName(_slidButton), 10);
            if (gt_slider_knob == null)
            {
                return true;
            }
            else
            {
                Console.WriteLine("验证失败,显示等待6秒刷新验证码...");
                Thread.Sleep(6000);
                return false;
            }
        }
        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));
                //if (offset != 0 && index != moveEntities.Count)
                //    Thread.Sleep(item.MillisecondsTimeout / offset);
            }
            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 = distance - _leftOffset;
                    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);
  
            //显示新图层
            ShowNewMap(remoteWebDriver);
            //全屏截图
            FullScreen(remoteWebDriver);
            //获取新图层
            var newElement = GetNewMapElement(remoteWebDriver);
            //保存新图
            CutBitmap(_fullScreenPath, _newMapPath, newElement);
            //显示缺口图
            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)
        {
            //获取原始图
            using (var bitmap = (Bitmap)Image.FromFile(sourcePath))
            {
                var newBitmap = bitmap.Clone(new Rectangle(webElement.Location, webElement.Size), System.Drawing.Imaging.PixelFormat.DontCare);
                newBitmap.Save(targetPath);
                newBitmap.Dispose();
                bitmap.Dispose();
            }
        }
        /// <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.FindElementExt(By.ClassName(_originalMap), 10);
        }
        /// <summary>
        /// 获取原始图加缺口背景图元素
        /// </summary>
        /// <param name="remoteWebDriver"></param>
        /// <returns></returns>
        protected virtual IWebElement GetNewMapElement(RemoteWebDriver remoteWebDriver)
        {
            return remoteWebDriver.FindElementExt(By.ClassName(_newMap), 10);
        }
  
        /// <summary>
        /// 获取缺口图层元素
        /// </summary>
        /// <param name="remoteWebDriver"></param>
        /// <returns></returns>
        protected virtual IWebElement GetSliceMapElement(RemoteWebDriver remoteWebDriver)
        {
            return remoteWebDriver.FindElementExt(By.ClassName(_sliceMap), 10);
        }
  
        /// <summary>
        /// 获取拖动按钮元素
        /// </summary>
        /// <param name="remoteWebDriver"></param>
        /// <returns></returns>
        protected virtual IWebElement GetSlidButtonElement(RemoteWebDriver remoteWebDriver)
        {
            return remoteWebDriver.FindElementExt(By.ClassName(_slidButton), 10);
        }
  
        /// <summary>
        /// 显示原始图层
        /// </summary>
        /// <param name="remoteWebDriver"></param>
        protected virtual bool ShowOriginalMap(RemoteWebDriver remoteWebDriver)
        {
            remoteWebDriver.ExecuteScript("$('." + _newMap + "').hide();$('." + _originalMap + "').show();$('." + _sliceMap + "').hide();");
            Console.WriteLine("显示原始图");
            Thread.Sleep(100);
  
            //#TODO 判断JS执行后是否正确
            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(100);
  
            //#TODO 判断JS执行后是否正确
            return true;
        }
        /// <summary>
        /// 显示缺口图
        /// </summary>
        /// <param name="remoteWebDriver"></param>
        /// <returns></returns>
        protected virtual bool ShowSliceMap(RemoteWebDriver remoteWebDriver)
        {
            remoteWebDriver.ExecuteScript("$('." + _sliceMap + "').show();");
            Console.WriteLine("显示原始图加缺口背景之后的图层");
            Thread.Sleep(100);
  
            //#TODO 判断JS执行后是否正确
            return true;
        }
          
  
        #endregion
    }
}
 
 
public interface ISlideVerificationCode
   {
       bool Pass(RemoteWebDriver remoteWebDriver);
   }
 
 
using OpenQA.Selenium.Chrome;
using Sniffer.VerificationCode.VerificationCodes;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
  
namespace Sniffer.VerificationCode.Tests
{
    class Program
    {
        static void Main(string[] args)
        {
            ChromeDriver driver = new ChromeDriver();
            driver.Navigate().GoToUrl("https://www.tianyancha.com/");
            //driver.Manage().Window.Maximize();//窗口最大化,便于脚本执行
            driver.Manage().Window.Size = new Size(800, 800);
  
            //Console.WriteLine("ChromeDriver 设置超时等待(隐式等待)时间设置10秒");
            //设置超时等待(隐式等待)时间设置10秒
            //driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(10);
  
            //点击《登录/注册》按钮
            driver.ExecuteScript("header.loginLink(event)");
            Console.WriteLine("点击《登录/注册》按钮");
            Thread.Sleep(500);
  
            //点击 《密码登录》
            driver.ExecuteScript("loginObj.changeCurrent(1);");
            Console.WriteLine("点击 《密码登录》按钮");
            Thread.Sleep(500);
  
            //输入账号密码
            driver.ExecuteScript("$('.contactphone').val('18620800677')");
            driver.ExecuteScript("$('.contactword').val('******')");
            Console.WriteLine("输入账号密码");
            Thread.Sleep(500);
  
            //点击登录按钮
            driver.ExecuteScript("loginObj.loginByPhone(event);");
            Console.WriteLine("点击《登录》按钮");
            Thread.Sleep(1000);
  
            GeetestSlideVerificationCode slideVerificationCode = new GeetestSlideVerificationCode();
            var flag = slideVerificationCode.Pass(driver);
            Console.WriteLine("过验证 " + flag);
        }
    }
}

 以上内容完全照搬原博主

 


 

 下面是缺失的部分

1
2
3
4
5
6
7
8
9
10
11
12
13
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;
    }
}

 

1
2
3
4
5
6
7
8
9
10
11
12
13
public static class WebElementExtensions
   {
       public static IWebElement FindElementExt(this IWebDriver driver, By byint timeoutInSeconds)
       {
            
               var wait = new DefaultWait<IWebDriver>(driver);
               wait.IgnoreExceptionTypes(typeof(StaleElementReferenceException), typeof(NoSuchElementException));
               wait.Timeout = TimeSpan.FromSeconds(timeoutInSeconds);
               return wait.Until(d => driver.FindElement(by));
            
 
       }
   }

  

到此程序可以完美运行了。。

posted @ 2022-02-11 12:27  左正  阅读(388)  评论(0编辑  收藏  举报