參考資料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個小圖,然后排序再組合成一張有序的原始圖。
轉換思路二:
直接去瀏覽器上通過顯示隱藏不同的圖片,然后截圖對比(目前我代碼這個思路處理的)。
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);
}
}
}
以上內容完全照搬原博主
下面是缺失的部分
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 FindElementExt(this IWebDriver driver, By by, int 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));
}
}
到此程序可以完美運行了。。






