Получение изображения с капчей в GeckoFx [C#]

0

Бывают случаи, когда нужно получить капчу и показать ее пользователю для ввода, либо отправить в какой-то сервис для автоматического распознавания. В этой статье я опишу, как получить изображение капчи (или любого другого) в GeckoFx.

GeckoFx имеет класс ImageCreator, который позволяет создавать скриншоты указанного размера текущей открытой страницы. Т.е. мы просто передаем отступы (можно обойтись и без них, тогда начальная точка будет 0,0) по X и Y и размер изображения — на выходе получаем массив байт, который легко «трансформируется» в Image:

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:

#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:

GeckoExtensionsMethods.JsImage jsImage = browser.GetJsImage( "si_image" );

Теперь добавим класс CaptchaEnter. Он будет отвечать за загрузку страницы, получения и возвращения изображения капчи в форму и ввод капчи, которую ввел пользователь.

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

Теперь перейдем к коду главной формы.

Добавим следующие переменные:

private readonly GeckoWebBrowser _webBrowser;
private readonly CaptchaEnter _captcha;

Добавим инициализацию Gecko в конструктор главной формы:

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) двух кнопок:

private void DisEnObjs()
{
	this.btnEnter.Enabled = !this.btnEnter.Enabled;
	this.btnGetNew.Enabled = !this.btnGetNew.Enabled;
}

И напоследок, создадим два обработчика нажатия для кнопок btnEnter и btnGetNew:

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

Первая кнопка будет передавать введенную капчу пользователем, а вторая — получать новое изображение капчи.

Исходники:

0
0 0 голоса
Рейтинг статьи
Подписаться
Уведомить о
guest
0 комментариев
Межтекстовые Отзывы
Посмотреть все комментарии
Авторизация
*
*
Регистрация
*
*
*
Пароль не введен
*
Генерация пароля
0
Оставьте комментарий! Напишите, что думаете по поводу статьи.x
()
x