Бывают случаи, когда нужно получить капчу и показать ее пользователю для ввода, либо отправить в какой-то сервис для автоматического распознавания. В этой статье я опишу, как получить изображение капчи (или любого другого) в GeckoFx.
GeckoFx имеет класс ImageCreator, который позволяет создавать скриншоты указанного размера текущей открытой страницы. Т.е. мы просто передаем отступы (можно обойтись и без них, тогда начальная точка будет 0,0) по X и Y и размер изображения — на выходе получаем массив байт, который легко «трансформируется» в Image:
1 2 3 4 5 6 7 8 9 10 |
private static Image ByteArrayToImage( byte[] byteArrayIn ) { var ms = new MemoryStream( byteArrayIn ); var returnImage = Image.FromStream( ms ); return returnImage; } ... var ic = new ImageCreator( browser ); Image image = ByteArrayToImage( ic.CanvasGetPngImage( xOffset, yOffset, imgWidth, imgHeight ) ); |
Как видите, ничего сложного 🙂
В качестве примера напишем приложение, которое будет заходить на этот сайт, брать оттуда капчу, отображать пользователя для ввода и проверять корректность ввода капчи.
Создаем новое WinForms приложение, называем его GeckoFxImage, размеры формы 195; 181, имя формы — FrmMain.
Список компонентов на форме:
- PictureBox (Name = pbImage, Size = 175; 60, Location = 0;0);
- TextBox (Name = tbInput, Size = 175; 20, Location = 0; 66);
- Button (Name = btnEnter, Size = 175; 23, Location = 0; 92, Text = Enter);
- Button (Name = btnGetNew, Size = 175; 23, Location = 0; 121, Text = Get new);
Устанавливаем Async Targeting Pack (нужен для возможности использования «асинхронных фич» в .NET 4).
Теперь приступим к написанию кода.
Добавим новый класс GeckoExtensionsMethods:
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 |
#region Using using System.Drawing; using System.IO; using System.Xml; using Gecko; #endregion namespace GeckoFxImage { public static class GeckoExtensionsMethods { public class JsImage { public int Left { get; set; } public int Top { get; set; } public int Right { get; set; } public int Bottom { get; set; } public int Width { get; set; } public int Height { get; set; } public Image Image { get; set; } } /// <summary> /// Конвертирует массив байт в изображение /// </summary> /// <param name="byteArrayIn">Массив байт изображения</param> /// <returns></returns> private static Image ByteArrayToImage( byte[] byteArrayIn ) { var ms = new MemoryStream( byteArrayIn ); var returnImage = Image.FromStream( ms ); return returnImage; } /// <summary> /// Создает скриншот страницы /// </summary> /// <param name="browser">Ссылка на браузер</param> /// <param name="imageInfo">Ссылка на данные об изображении</param> /// <returns></returns> public static Image MakeImage( GeckoWebBrowser browser, JsImage imageInfo ) { var ic = new ImageCreator( browser ); return ByteArrayToImage( ic.CanvasGetPngImage( (uint)imageInfo.Left, (uint)imageInfo.Top, (uint)imageInfo.Width, (uint)imageInfo.Height ) ); } /// <summary> /// Возвразает информацию о указанном изображении /// </summary> /// <param name="browser">Ссылка на браузер</param> /// <param name="elementId">Id элемента на странице</param> /// <returns></returns> public static JsImage GetJsImage( this GeckoWebBrowser browser, string elementId ) { var jsImage = new JsImage(); using ( var context = new AutoJSContext( browser.Window.JSContext ) ) { string js = @" function getElementInfo( elementId ) { var resArr = new Array(); var element = document.getElementById( elementId ); var rect = element.getBoundingClientRect(); resArr.push( rect.top ); resArr.push( rect.right ); resArr.push( rect.bottom ); resArr.push( rect.left ); resArr.push( element.offsetWidth ); resArr.push( element.offsetHeight ); return resArr; } getElementInfo(""" + elementId + @""");"; string result; // выполняем простой js, который возвращает массив с нужными для нас данными context.EvaluateScript( js, (nsISupports)browser.Document.DomObject, out result ); if ( result == "undefined" ) { return null; } string[] dataArr = result.Split( ',' ); jsImage.Top = (int)XmlConvert.ToDouble( dataArr[ 0 ] ); jsImage.Right = (int)XmlConvert.ToDouble( dataArr[ 1 ] ); jsImage.Bottom = (int)XmlConvert.ToDouble( dataArr[ 2 ] ); jsImage.Left = (int)XmlConvert.ToDouble( dataArr[ 3 ] ); jsImage.Width = (int)XmlConvert.ToDouble( dataArr[ 4 ] ); jsImage.Height = (int)XmlConvert.ToDouble( dataArr[ 5 ] ); jsImage.Image = MakeImage( browser, jsImage ); } return jsImage; } } } |
Класс содержит статические методы, среди которых метод расширения GetJsImage для GeckoWebBrowser, который упрощает получение изображения с указанным Id:
1 |
GeckoExtensionsMethods.JsImage jsImage = browser.GetJsImage( "si_image" ); |
Теперь добавим класс CaptchaEnter. Он будет отвечать за загрузку страницы, получения и возвращения изображения капчи в форму и ввод капчи, которую ввел пользователь.
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 |
#region Using using System; using System.Drawing; using System.Linq; using System.Threading.Tasks; using System.Windows.Forms; using Gecko; #endregion namespace GeckoFxImage { public class CaptchaEnter { private readonly GeckoWebBrowser _browser; private readonly Timer _loadingTimer; // таймер загрузки private bool _loading; // указывает на статус загрузки public CaptchaEnter( GeckoWebBrowser browser ) { this._browser = browser; this._loadingTimer = new Timer { Interval = 4000 }; this._loadingTimer.Tick += this._loadingTimer_Tick; } private void _loadingTimer_Tick( object sender, EventArgs e ) { this._loadingTimer.Stop(); this._loading = false; } /// <summary> /// Ожидает загрузки страницы /// </summary> private async Task WaitForLoading() { while ( this._loading ) { await TaskEx.Delay( 200 ); } } /// <summary> /// Загружает указанную страницу и ждет завершения загрузки /// </summary> /// <param name="url">Url страницы</param> private async Task Navigate( string url ) { this._loading = true; this._browser.Navigate( url ); this._loadingTimer.Start(); await this.WaitForLoading(); } /// <summary> /// Возвращает изображение с капчей /// </summary> /// <returns></returns> public async Task<Image> GetCaptcha() { await this.Navigate( "http://www.unitedway-pdx.org/contact-files/captcha/test/captcha_test.php" ); GeckoExtensionsMethods.JsImage jsImage = this._browser.GetJsImage( "si_image" ); return jsImage == null ? null : jsImage.Image; } /// <summary> /// Вводит капчу и проверяет корректность ввода /// </summary> /// <param name="input">Текст капчи</param> /// <returns>Возвращает значение, указывающее на корректность ввода</returns> public async Task<bool> InputCaptcha( string input ) { // получаем элемент (input) с указанным id var inputEl = this._browser.Document.GetElementById( "code" ); if ( inputEl == null ) { return false; } // устанавливаем input'у текст, который ввел пользователь inputEl.SetAttribute( "value", input ); // выбираем первый input, значение (value) которого равно submit var btnEl = this._browser.Document.GetElementsByTagName( "input" ) .FirstOrDefault( b => b.GetAttribute( "value" ) == "submit" ); if ( btnEl == null ) { return false; } btnEl.Click(); await TaskEx.Delay( 2500 ); // выбираем первый p, который содержить указанный текст var pEl = this._browser.Document.GetElementsByTagName( "p" ) .FirstOrDefault( p => p.InnerHtml.Contains( "Test Passed." ) ); // если такой элемент получен - тест пройден успешно return pEl != null; } } } |
Думаю, все понятно, если прочесть комменты в коде 🙂
Теперь перейдем к коду главной формы.
Добавим следующие переменные:
1 2 |
private readonly GeckoWebBrowser _webBrowser; private readonly CaptchaEnter _captcha; |
Добавим инициализацию Gecko в конструктор главной формы:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
public FrmMain() { // инициализация Xulrunner Xpcom.Initialize( Application.StartupPath + "\\xulrunner\\" ); this.InitializeComponent(); this._webBrowser = new GeckoWebBrowser { Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Top | AnchorStyles.Bottom, Width = this.Width, Height = this.Height - 75, Top = 0, AutoSize = false }; // маскируемся под Firefox 22 GeckoPreferences.User[ "general.useragent.override" ] = "Mozilla/5.0 (Windows NT 6.1; rv:22.0) Gecko/20130405 Firefox/22.0"; var frm = new Form(); frm.Controls.Add( this._webBrowser ); this._captcha = new CaptchaEnter( this._webBrowser ); } |
Напишем метод, который будет изменять активность (свойство enabled) двух кнопок:
1 2 3 4 5 |
private void DisEnObjs() { this.btnEnter.Enabled = !this.btnEnter.Enabled; this.btnGetNew.Enabled = !this.btnGetNew.Enabled; } |
И напоследок, создадим два обработчика нажатия для кнопок btnEnter и btnGetNew:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
private async void btnEnter_Click( object sender, EventArgs e ) { this.DisEnObjs(); if ( await this._captcha.InputCaptcha( this.tbInput.Text ) ) { MessageBox.Show( "Капча введена верно!", "Информация", MessageBoxButtons.OK, MessageBoxIcon.Information ); } else { MessageBox.Show( "Капча введена неверно!", "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error ); } this.DisEnObjs(); } private async void btnGetNew_Click( object sender, EventArgs e ) { this.DisEnObjs(); Image image = await this._captcha.GetCaptcha(); this.pbImage.Image = image; this.pbImage.Refresh(); this.DisEnObjs(); this.tbInput.Focus(); } |
Первая кнопка будет передавать введенную капчу пользователем, а вторая — получать новое изображение капчи.
Вот и всё.
Исходники можно найти на GitHub.
Спасибо, очень помогли Ваши статьи!
Здравствуйте!
Очень познавательная статья!
Еще бы такую же статью на примере отправки и получения (и выбора ответов) reCaptcha — было бы очень круто.
Дело в том, что при отгадывании reCaptcha бывает что даже не нужно выбирать картинки (сразу галочка зеленой становится если IP «чистая») — а это в свою очередь существенное уменьшение затрат на отгадывание.
Документация библиотеки Antigate дает пример использования. Но, как его использовать в реальных условиях не имея опыта — сложновато разобраться.
Вот как раз опыт набирается, когда разбираешься во всяких «сложных» штуках 🙂
А чем отличаются «реальные условия» от «нереальных условий», которые описаны в доках? Методы остаются такими же, параметры методов — тоже. С помощью их либы все в пару строк кода решается (вызвать конструктор класса и потом синхронно отправить изображение и получить ответ).
Статья великолепная, мне понравилась — очень познавательно.
И сразу у меня возник вопрос. Как получить доступ к изображения с капчей в ?
Для наглядности вот с такой https://www.google.com/recaptcha/api2/demo
Проверить не могу, т.к. капча мне не показывается. Но принцип должен быть тот же — по css-селектору получить элемент изображения и сделать скриншот этой области.