zoukankan      html  css  js  c++  java
  • c# Selenium 破解极验验证码

    背景:由于爬 https://www.tianyancha.com/ ,需要登录登录认证,所以来做破解 极验验证

    参考资料:https://www.cnblogs.com/ZQWelcomeIndex/p/8367202.html 破解腾讯空间滑块 (注:目前图片地址有变化,该地址代码下载不能直接使用。可以参考 https://www.cnblogs.com/hujunmin/p/11486124.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个小图,然后排序再组合成一张有序的原始图。

    转换思路二:

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

    上代码:

    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);
            }
        }
    }
    

      

  • 相关阅读:
    精算师的前世今生
    失落的C语言结构体封装艺术
    关于联合的一些介绍
    变量的声明和定义
    C/C++内存分配区
    探寻周瑜“前世今生”
    SpringBoot中使用AOP
    springBoot中的事物管理
    springBoot整合多数据源
    spingBoot整合mybatis+generator+pageHelper
  • 原文地址:https://www.cnblogs.com/hujunmin/p/11506958.html
Copyright © 2011-2022 走看看