Не всех устраивает стандартный движок IE в Винде. Но, к счастью, есть много альтернативных движков для C#, Gecko входит в их число и в этом посте я опишу, как использовать GeckoFx в своих проектах.
Исходники можно найти в конце статьи.
На момент написания статьи последняя версия GeckoFx для C# это 22. Давненько не было обновлений и, возможно, проект умер, но даже 22 версия лучше IE. Так что начнем 🙂
Напишем приложение, которое будет заходить на главную станицу гугла, вводить какой-то запрос и переходить по случайному сайту в выдаче.
Создаем новый проект (Приложение Windows Forms), называем его «GeckoExample«, переименовываем главную форму в «FrmMain«, размеры главной формы = 514; 399.
Для работы нам понадобиться:
- Скачать библиотеки здесь. Распаковать;
- Скачать сам движок с офф. фтп Мозиллы. Распаковать;
- Установить Async Targeting Pack для .NET 4.0 в проект.
Теперь в созданном проекте добавляем ссылки на Geckofx-Core.dll и Geckofx-Winforms.dll.
Бросаем на форму Panel (Name = pnl1, Location = 0;0, Size = 495; 25), на эту панель добавляем Button (Name = «btnStart», Location = 0;0, Size = 75; 23, Text = Начнем), рядом с кнопкой бросаем TextBox (Name = «tbUrl», Location = 84; 2, Size = 244; 20).
В свойствах проекта переключаем конечную платформу на x86.
Теперь приступим к написанию кода.
В using класса главной формы подключаем:
1 |
using Gecko; |
Добавляем приватное поле _webBrowser типа GeckoWebBrowser:
1 |
private readonly GeckoWebBrowser _webBrowser; |
В конструкторе класса главной формы пишем следующее:
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 - this.pnl1.Height - 5, Top = this.pnl1.Bottom + 5 }; // после загрузки страницы будем менять значение tbUrl, в котором находится текущий урл this._webBrowser.DocumentCompleted += this._webBrowser_DocumentCompleted; // устанавливаем UserAgent браузера в Firefox 22 GeckoPreferences.User[ "general.useragent.override" ] = "Mozilla/5.0 (Windows NT 6.1; rv:22.0) Gecko/20130405 Firefox/22.0"; // добавляем контрол браузера на форму this.Controls.Add( this._webBrowser ); } |
Т.е. мы инициализируем движок, указывая путь к движку (у меня он находится в папке с программой). Далее создаем экземпляр класса GeckoWebBrowser (т.е. наш браузер), создаем обработчик события DocumentCompleted (который будет изменять текущий адрес страницы в tbUrl), меняем наш юа и добавляем контрол браузера на форму.
Кстати, вот реализация метода _webBrowser_DocumentCompleted:
1 2 3 4 |
private void _webBrowser_DocumentCompleted( object sender, EventArgs e ) { this.tbUrl.Text = this._webBrowser.Url.ToString(); } |
Настало время написать класс, который будет иметь следующие методы:
- загрузка главной страницы гугла;
- написание произвольного поискового запроса в текстовое поле;
- клик по случайному сайту на странице выдачи.
Добавляем новый класс GoogleExample и следующие поля:
1 2 3 |
private readonly GeckoWebBrowser _webBrowser; // ссылка на браузер private readonly Timer _loadingTimer; // таймер загрузки private bool _loading; // указывает на статус загрузки |
Поле _webBrowser — это ссылка на наш браузер, _loadingTimer через 2000 (т.е. две секунды) миллисекунд будет изменять состояние загрузки страницы и _loading — статус загрузки (этому полю наш таймер будет присваивать значение false).
Незабываем в using подключить следующее:
1 2 3 |
using System.Threading.Tasks; using System.Windows.Forms; using Gecko; |
Теперь напишем конструктор класса GoogleExample:
1 2 3 4 5 6 7 |
public GoogleExample( GeckoWebBrowser webBrowser ) { this._webBrowser = webBrowser; this._webBrowser.CreateWindow2 += this._webBrowser_CreateWindow2; this._loadingTimer = new Timer { Interval = 2000 }; this._loadingTimer.Tick += this._loadingTimer_Tick; } |
Конструктор принимает ссылку на браузер, инициализирует таймер. Также создается обработчик события CreateWindow2. Это событие вызывается перед созданием окна. Нужно нам это событие для того, чтобы «глушить» открытие новых окон при нажатии на ссылку результата на странице выдачи гугла.
1 2 3 4 5 |
private async void _webBrowser_CreateWindow2( object sender, GeckoCreateWindow2EventArgs e ) { e.Cancel = true; await this.Navigate( e.Uri ); } |
В этом событии мы отменяем открытие нового окна и заставляем главное окно перейти на ссылку, которую хотело открыть новое окно.
Реализация события Tick для _loadingTimer:
1 2 3 4 5 |
private void _loadingTimer_Tick( object sender, EventArgs e ) { this._loadingTimer.Stop(); this._loading = false; } |
Все просто: останавливаем таймер и изменяем значение переменной _loading, это значение указывает на статус загрузки страницы.
Реализация метода WaitForLoading:
1 2 3 4 5 6 7 8 9 10 |
/// <summary> /// Ожидает загрузки страницы /// </summary> private async Task WaitForLoading() { while ( this._loading ) { await TaskEx.Delay( 200 ); } } |
Обычный цикл, каждые 200 миллисекунд проверяется статус загрузки страницы.
Реализация метода Navigate:
1 2 3 4 5 6 7 8 9 10 11 |
/// <summary> /// Загружает указанную страницу и ждет завершения загрузки /// </summary> /// <param name="url">Url страницы</param> private async Task Navigate( string url ) { this._loading = true; this._webBrowser.Navigate( url ); this._loadingTimer.Start(); await this.WaitForLoading(); } |
Открываем браузером страницу, запускаем таймер и асинхронно ждем загрузки страницы.
Основные методы готовы. Начнем открывать гугл 🙂
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
/// <summary> /// Загружает страницу гугла и ждет загрузки /// </summary> public async Task OpenGoogle() { await this.Navigate( "http://www.google.ru/" ); // если гугл выдает нам страницу не на русском и предлагает включить русский, то включаем var aElements = this._webBrowser.Document.GetElementsByTagName( "a" ); this._loading = true; this._loadingTimer.Start(); foreach ( var element in aElements ) { if ( element.InnerHtml.IndexOf( "русском", StringComparison.Ordinal ) >= 0 ) { this._loading = true; element.Click(); this._loadingTimer.Start(); await this.WaitForLoading(); break; } } } |
Метод OpenGoogle загружает главную страницу гугла. Но есть один момент: гугл может быть на английском, потому цикл foreach нужен для того, чтобы пройтись по всем ссылкам на главной странице и найти ту ссылку (если она есть), при нажатии на которую можно переключить язык на русский.
Следующий в очереди метод ввода поискового запроса — WriteQuery.
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 |
/// <summary> /// Пишет поисковый запрос /// </summary> /// <param name="query">Поисковый запрос</param> public async Task WriteQuery( string query ) { // получаем все элементы с тегом input var inputsElements = this._webBrowser.Document.GetElementsByTagName( "input" ); // получаем первый input, имя (атрибут "name") которого равно q var queryElement = inputsElements.First( i => i.GetAttribute( "name" ) == "q" ); // "плавный" набор поискового запроса Random random = new Random(); for ( int i = 0; i < query.Length; i++ ) { queryElement.SetAttribute( "value", query.Substring( 0, i + 1 ) ); await TaskEx.Delay( random.Next( 100, 350 ) ); } // получаем все элементы с тегом button var buttonsElements = this._webBrowser.Document.GetElementsByTagName( "button" ); // указываем, что началась загрузка, кликаем по первой кнопке с именем "btnG, запускаем таймер и ждем загрузки страницы this._loading = true; buttonsElements.First( i => i.GetAttribute( "name" ) == "btnG" ).Click(); this._loadingTimer.Start(); await this.WaitForLoading(); } |
Этот метод получает все input‘ы, далее выбирает первый input, который имеет имя «q». Далее запускается цикл, с помощью которого поисковый запрос вводиться побуквенно (типа, реальный пользователь набирает текст :D). После цикла выбираем все button‘ы и кликаем по первому, у которого имя равно «btnG» (это синяя кнопка для поиска) и ждем загрузки страницы.
Ну и последний метод в нашем классе — ClickRandomLink.
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 |
/// <summary> /// Эмулирует нажатие на случайной ссылке /// </summary> public async Task ClickRandomLink() { // получаем все элементы с тегом input var aElements = this._webBrowser.Document.GetElementsByTagName( "a" ).ToList(); // получаем все ссылки, у которых значение атрибута "onmousedown" начинается с "return rwt(" List<GeckoHtmlElement> needleLinks = new List<GeckoHtmlElement>(); foreach ( var element in aElements ) { try { if ( element.GetAttribute( "onmousedown" ).StartsWith( "return rwt(" ) && element.GetAttribute( "href" ) .IndexOf( "webcache.googleusercontent.com", StringComparison.Ordinal ) < 0 ) { needleLinks.Add( element ); } } catch { continue; } } Random random = new Random(); this._loading = true; // нажимаем по случайной ссылке needleLinks[ random.Next( needleLinks.Count ) ].Click(); this._loadingTimer.Start(); await this.WaitForLoading(); } |
Собственно, получаем все ссылки, инициализируем список «нужных ссылок» и запускаем цикл по списку всех ссылок, в котором ищем ссылки, атрибут «onmousedown» которых начинается с «return rwt(» и сама ссылка не содержит «webcache.googleusercontent.com» (иначе получим ссылку на сохраненную копию). дофига ща раз было слово «ссылка». Конструкцию try..catch нужно использовать обязательно, т.к. если попытаться получить доступ к несуществующему атрибуту элемента, то движок выбросит исключение. Например, в том же HtmlAgilityPack все по-другому (и, как мне кажется, удобнее): в методе получения атрибута элемента мы заранее указываем, что нам возвращать, если у элемента нет такого атрибута. Ну да ладно 🙂
Возвращаемся к классу формы. Создаем обработчик события Click кнопки btnStart, добавляем к этому методу модификатор async и пишем следующее:
1 2 3 4 5 6 7 |
private async void btnStart_Click( object sender, EventArgs e ) { var testClass = new GoogleExample( this._webBrowser ); await testClass.OpenGoogle(); await testClass.WriteQuery( "создание приложений с поддержкой плагинов c#" ); await testClass.ClickRandomLink(); } |
Ну вот и всё. Можно компилить, тыкать на кнопку и наблюдать результат 🙂
Кстати, исходники можно скачать на GitHub.
А примерчик на почту можно?
В блоге статья будет.
Доброе утро.
Интересно как скоро выйдет на свет Ваша статья по эмуляции кликов мыши, может хоть бы подскажите направление где покопать? Привязка к объекту обработки идет так же по координатам рабочего стола или привязки к координатам, нет вообще?
Ориентировочно в пятницу.
«Привязка к объекту обработки» что?
Объект обработки — это ссылка, кнопка и т.д, по которой необходимо кликнуть мышкой
Я же писал выше, что разобрался, как нативно с помощью GeckoFx эмулировать события мыши, то какой может быть рабочий стол?
Сори за не корректный ворос. Ждем Вашу статью
Добрый день!
Получаю вот такие ошибки в output. Gecko установил в VS2012 через Nuget. Подскажите, куда копать?
Gecko.Xpcom.DirectoryServiceProvider.GetFile: not implemented: permissionDBPDir
A first chance exception of type ‘System.IO.FileNotFoundException’ occurred in mscorlib.dll
‘Gramsquare.vshost.exe’ (Managed (v4.0.30319)): Loaded ‘Microsoft.GeneratedCode’
Gecko.Xpcom.DirectoryServiceProvider.GetFile: not implemented: LclSt
Gecko.Xpcom.DirectoryServiceProvider.GetFile: not implemented: WinD
Gecko.Xpcom.DirectoryServiceProvider.GetFile: not implemented: AppData
Gecko.Xpcom.DirectoryServiceProvider.GetFile: not implemented: plugin.scan.Quicktime
Рекомендую перечитать статью заново.
Подскажите пожалуйста, где вы нашли мануал по GeckoFX?
Сам разбирался, исходники открыты же 🙂