Использование Gecko в C# [GeckoFx]

geckoНе всех устраивает стандартный движок IE в Винде. Но, к счастью, есть много альтернативных движков для C#, Gecko входит в их число и в этом посте я опишу, как использовать GeckoFx в своих проектах.

Исходники можно найти в конце статьи.

На момент написания статьи последняя версия GeckoFx для C# это 22. Давненько не было обновлений и, возможно, проект умер, но даже 22 версия лучше IE. Так что начнем 🙂

Напишем приложение, которое будет заходить на главную станицу гугла, вводить какой-то запрос и переходить по случайному сайту в выдаче.
Создаем новый проект (Приложение Windows Forms), называем его «GeckoExample«, переименовываем главную форму в «FrmMain«, размеры главной формы = 514; 399.

Для работы нам понадобиться:

  1. Скачать библиотеки здесь. Распаковать;
  2. Скачать сам движок с офф. фтп Мозиллы. Распаковать;
  3. Установить Async Targeting Pack для .NET 4.0 в проект.

Теперь в созданном проекте добавляем ссылки на Geckofx-Core.dll и Geckofx-Winforms.dll.
1Бросаем на форму 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.
2Теперь приступим к написанию кода.
В using класса главной формы подключаем:

Добавляем приватное поле _webBrowser типа GeckoWebBrowser:

В конструкторе класса главной формы пишем следующее:

Т.е. мы инициализируем движок, указывая путь к движку (у меня он находится в папке с программой). Далее создаем экземпляр класса GeckoWebBrowser (т.е. наш браузер), создаем обработчик события DocumentCompleted (который будет изменять текущий адрес страницы в tbUrl), меняем наш юа и добавляем контрол браузера на форму.
Кстати, вот реализация метода _webBrowser_DocumentCompleted:

Настало время написать класс, который будет иметь следующие методы:

  • загрузка главной страницы гугла;
  • написание произвольного поискового запроса в текстовое поле;
  • клик по случайному сайту на странице выдачи.

Добавляем новый класс GoogleExample и следующие поля:

Поле _webBrowser — это ссылка на наш браузер, _loadingTimer через 2000 (т.е. две секунды) миллисекунд будет изменять состояние загрузки страницы и _loading — статус загрузки (этому полю наш таймер будет присваивать значение false).

Незабываем в using подключить следующее:

Теперь напишем конструктор класса GoogleExample:

Конструктор принимает ссылку на браузер, инициализирует таймер. Также создается обработчик события CreateWindow2. Это событие вызывается перед созданием окна. Нужно нам это событие для того, чтобы «глушить» открытие новых окон при нажатии на ссылку результата на странице выдачи гугла.

В этом событии мы отменяем открытие нового окна и заставляем главное окно перейти на ссылку, которую хотело открыть новое окно.

Реализация события Tick для _loadingTimer:

Все просто: останавливаем таймер и изменяем значение переменной _loading, это значение указывает на статус загрузки страницы.

Реализация метода WaitForLoading:

Обычный цикл, каждые 200 миллисекунд проверяется статус загрузки страницы.

Реализация метода Navigate:

Открываем браузером страницу, запускаем таймер и асинхронно ждем загрузки страницы.

Основные методы готовы. Начнем открывать гугл 🙂

Метод OpenGoogle загружает главную страницу гугла. Но есть один момент: гугл может быть на английском, потому цикл foreach нужен для того, чтобы пройтись по всем ссылкам на главной странице и найти ту ссылку (если она есть), при нажатии на которую можно переключить язык на русский.

Следующий в очереди метод ввода поискового запроса — WriteQuery.

Этот метод получает все input‘ы, далее выбирает первый input, который имеет имя «q». Далее запускается цикл, с помощью которого поисковый запрос вводиться побуквенно (типа, реальный пользователь набирает текст :D). После цикла выбираем все button‘ы и кликаем по первому, у которого имя равно «btnG» (это синяя кнопка для поиска) и ждем загрузки страницы.

Ну и последний метод в нашем классе — ClickRandomLink.

Собственно, получаем все ссылки, инициализируем список «нужных ссылок» и запускаем цикл по списку всех ссылок, в котором ищем ссылки, атрибут «onmousedown» которых начинается с «return rwt(» и сама ссылка не содержит «webcache.googleusercontent.com» (иначе получим ссылку на сохраненную копию). дофига ща раз было слово «ссылка». Конструкцию try..catch нужно использовать обязательно, т.к. если попытаться получить доступ к несуществующему атрибуту элемента, то движок выбросит исключение. Например, в том же HtmlAgilityPack все по-другому (и, как мне кажется, удобнее): в методе получения атрибута элемента мы заранее указываем, что нам возвращать, если у элемента нет такого атрибута. Ну да ладно 🙂

Возвращаемся к классу формы. Создаем обработчик события Click кнопки btnStart, добавляем к этому методу модификатор async и пишем следующее:

Ну вот и всё. Можно компилить, тыкать на кнопку и наблюдать результат 🙂

Кстати, исходники можно скачать на GitHub.

result

Запись опубликована в рубрике Статьи по C# с метками , , , . Добавьте в закладки постоянную ссылку.

66 комментариев на «Использование Gecko в C# [GeckoFx]»

  1. Gakk говорит:

    Исходники на гитхабе, не рабочие, замените, пожалуйста

    • admin говорит:

      Чего в них не рабочего?

      • Gakk говорит:

        Уже разобрался, не было в сборке xullrunnera.
        Остался вопрос про контрол. Добавляется он на форму кодом this.Controls.Add( this._webBrowser ); как я понял. Но это очень не гибко. Как можно сделать, чтобы строго определенное место было для него выделено? Есть ли возможность масштабирования содержимого под эти размеры?

      • Gakk говорит:

        Так же интересно, как быть с тегом select. Как выбрать нужный элемент?

      • admin говорит:

        Можно попробовать так:
        https://gist.github.com/dredei/69af2f47b6c36a48fc70
        Код, который выше, должен работать и для тех select‘ов, у которых нет атрибута value в option.

      • admin говорит:

        Это пример, так что ожидать от него какой-то гибкости (чтобы сразу скопировал и работало, как нужно), не стоит 😉
        На счет размеров, то я, думаю, не сложно поставить точку после this._webBrowser и глянуть, какие у этого объекта есть поля и методы.
        Среди них есть такие поля, как Height (высота) и Width (ширина), с помощью которых можно настроить размеры контролла. Также следует установить свойство Anchor в AnchorStyles.Left | AnchorStyles.Top , т.к. в примере с помощью якорей (AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Top | AnchorStyles.Bottom) контролл «цепляется» ко всем краям формы.

      • Gakk говорит:

        Точно. Спасибо!
        Документации, как я понял, нет на него, даже на английском?

      • admin говорит:

        Документации нет. Когда-то находил примеры (для 16 версии, вроде) на англ.
        Так что метод научного тыка рулит 🙂

  2. Faxel79 говорит:

    Я вижу что Вы очень неплохо разобрались с Geckofx. Подскажите если знаете. По свойству geckoWebBrowser1.Document мы получаем доступ к документу, который был уже распарсен движком. Но бывают случаи когда оригинал исходника веб страницы с сервера, не соответствует OuterHtml. Хотя движок и верно отображает страницу, свойство geckoWebBrowser1.Document уже потеряло некоторые данные и к ним не получить доступ. если сделать geckoWebBrowser1.Navigate («view-source:» + url); то код без потерь, но во первых он отображается на странице, что не всегда нужно, во вторых запрашивается с сервера ещё раз, а в третьих он только выглядит верно, а получить доступ к html документу как? OuterHtml это будет уже не оригинал, туда добавится разметка оформления для вывода кода. Надеюсь вы поняли, что я хочу узнать. Заранее спасибо.

    • admin говорит:

      Я не в курсе, как получить оригинал кода, который отдает сервак, с помощью GeckoFx. Т.к. какой смысл таскать за собой такое двигло, если нужно получить просто код (без рендеринга и т.д.), который отдает сервак? Для таких целей есть WebClient 🙂

  3. Faxel79 говорит:

    Спасибо за ответ. Таскать двигло имеет смысл в определенных ситуациях. Цель его использования в конкретной проблеме была не в получении исходного кода, движок используется по назначению. Просто параллельно и такой вопрос возник, чтоб программно обработать данные со страницы. К сожалению в связи со скудным документированием сам ответа не нашел. Согласитесь, раз движок все же используется, то глупо городить костыли и обращаться через WebClient, когда эту конкретную страницу уже получил и отобразил движок. Ладно, будем искать, возможно не все потерянно.

    • admin говорит:

      Городить костыли в программировании нужно чуть ли не постоянно 😉 Т.к. не все либы имеют нужный функционал/работают так, как нужно.
      Ну, либо портировать (допиливать) этот движок самому с преферансом и поэтессами. Но на это уйдет намного больше времени, чем соорудить костыль.

      Также можно попробовать задать вопрос тем, кто портировал двигло здесь.
      П.С. На счет view-source. Можно схитрить и получить доступ к коду так:
      _webBrowser.Navigate( "view-source: someurl" );
      string html = _webBrowser.Document.GetElementsByTagName( "html" )[ 0 ].TextContent;

      В теории, должно сработать 🙂

  4. Александр говорит:

    Здравствуйте, не подскажете как в gecko 29 версии изменить дерикторию куки?
    Xpcom.ProfileDirectory — меняет только папку кеша а куки остаются стандартно C:\Users\Админ\AppData\Local\Geckofx\DefaultProfile

    Спасибо…

    • admin говорит:

      Здравствуйте! У меня отлично все меняет. Пишет все данные (кеш, куки и т.д.) профиля туда, куда я и указал.

  5. Александр говорит:

    У версии geckoFX 29,0,6 и выше — куки остаются в стандартной папке!
    И еще прошу помощи с авторизацией Proxy по паролю..
    Буду ждать ваших коментариев по этому поводу. Спасибо..

    • admin говорит:

      Специально только что проверил на 29.0.11 — все данные профиля сохраняются там, где я и указал.
      На счет прокси — смотрите пример здесь.

  6. Александр говорит:

    А можно примерчик на почту или ссылку на пример? только без браузера! что не делаю всеравно куки сохраняет в стандартную папку!
    сижу уже пару дней, ничего толком не выходит(
    Спасибо за быстрые, супер быстрие отзывы…
    Спасибо..

    • admin говорит:

      В общем, ничего особенного. Делаю так, как вы и писали 🙂
      Пример отправил на почту (указываю профиль в конструкторе главной формы).

      • Александр говорит:

        Низкий поклон вам, все, разобрался)

        так работает
        Xpcom.ProfileDirectory = Application.StartupPath + «\\bin\\cookie»;
        Xpcom.Initialize(Application.StartupPath + «\\xulruner\\»);

        так неработало у меня
        Xpcom.Initialize(Application.StartupPath + «\\xulruner\\»);
        Xpcom.ProfileDirectory = Application.StartupPath + «\\bin\\cookie»;

        в чем разница такого события? раньше все работало до обновления версии.

        Сасибо, ваш пример решил мою проблему…

      • admin говорит:

        Фиг знает, почему раньше работало. Может, те, кто портировали новую версию что-то изменили или это изменения в самом движке.
        В принципе, логично, что сначала указывает профиль, а потом инициализируем движок (так как раз Лиса и работает: сначала выбираем профиль, а потом уже запускается браузер).

      • Александр говорит:

        Хорошо что хорошо все решилось. Для этого буду знать куда вертеть))

        Спасибо за ответы. буду внимательно читать ваш блог.

        с автоматической авторизацией прокси по паролю разобрался. если нужно будет кому-то пример то через админа будет можно взять пример.
        Спасибо еще раз..

      • admin говорит:

        Не за что, удачного кодинга 😉

  7. Александр говорит:

    Здравствуйте, столкнулся с проблемой. Открытия окон в новой вкладки, неработает с некоторыми сайтами, в основном с многими. Вот навожу пример с одним из сайтов — http://123.sovetam.ru Во время подписки выскакивает новая вкладка, но сайт сообщает об ошибке. Если не блокировать окно то подписка проходит нормально! Пробовал подставлять реферер но толку мало, хоча на некоторых из кликовых спонсоров, клики по ссылкам, открытие в новом окне, реферер помог! Без реферера клики тоже непроходили!!!
    Прошу помощи, так как это является большой проблемой с использованием геско вкладки. Спасибо.. Буду ждать ответа.

    • admin говорит:

      Просто происходит не просто открытие окна, а еще и отправка post-запроса, потому и сообщает об ошибке.
      Здесь уже нужно сидеть и копать (и гуглить :)), у меня сейчас времени на это нет, занят другим проектом.

  8. Artem говорит:

    У меня такой вопрос странный. Не могу разобраться как работает ваша синхронизация? Как узнать что страница полностью прогрузилась и с ней можно дальше работать?
    с обычным компонентом webBrowser я проверял на ReadyState, если он completed то идем дальше.
    Но тут как то не могу разобраться, подскажите, а?)

    • admin говорит:

      Здесь таймер, который меняет состояние булевой переменной (_loading) по истечению 2 секунд после начала загрузки страницы. Переменные подписаны комментариями, не сложно в студии выбрать нужную переменную и юзануть «Find Usages», чтобы увидеть места, где она используется/изменяется ее состояние.
      Ни в одном из компонентов нельзя четко определить полную загрузку страницы, т.к. сейчас сайты напичканы кучей js скриптов, которые после загрузки документа могут много чего изменить на странице и это фиг отловишь. С одними сайтами DocumentCompleted работает идеально, с другими — вечная загрузка, а третьи вообще на одном аяксе. Так что лучше делать всякие «комбо» из таймеров, метода DocumentCompleted и свойства IsBusy.

      • Artem говорит:

        я просто не нашел зависимости таймера и загрузки. он просто меняет переменную и говорит, что страница загрузилась через 2 секунды? а если она не успела загрузиться за это время?
        или я что то упустил?

      • admin говорит:

        В данном примере таймер решает: загрузилась страницы или нет 🙂 Это же пример, лень было что-то «мега-крутое» для простого примера делать.
        А если она не успела загрузится, то работа продолжиться с не загруженной страницей.

      • Artem говорит:

        ага, я понял. Спасибо.
        а может подскажите еще какие сторонние компоненты, в которых это решается более просто, при мопощи методов и свойств?

        проблема стандартного веб-браузера в том, что он почему то не перегружает содержимое документа, вообще никак. (как загрузил после первой навигации, так и все)
        я так понял в Gecko с этим все ок, вот только нужно костылить с синхронизацией?

      • admin говорит:

        Я ниже описал, что ни в одном компоненте (с которым я сталкивался 🙂 ) нельзя одним методом/событием определить полную загрузку страницы (еще в Делфи 7 и выше с этой фигней сталкивался и перепробовал еще тогда кучу компонент). Даже обычный браузер (Хром, Лиса) не может этого сделать 🙂
        А костыль с загрузкой достаточно простой: используем таймер и проверку свойства IsBusy. Таймер «заводим» на, например, 10 секунд, при срабатывании таймера меняется значение переменной _loading в false.
        А в методе открытия ссылки, где происходит ожидание загрузки страницы, делаем простой цикл:
        while (_wb.IsBusy && _loading)
        {
        // ждем
        }
        _loadingTimer.Stop();
        // работаем со страницей

        Использовать таймер в любом случае нужно, т.к. может какой-то скрипт какого-то нафиг ненужного счетчика, который загружается синхронно, «повешать» страницу (т.е. будет грузиться вечно или очень долго) и в итоге программа будет ждать, как Хатико 🙂

        П.С. Считаю GeckoFX самым нормальным двиглом для C#. Сталкивался с портом Хромиума (уже не помню, как называется), но там всё печально. Нужно лепить «обертку» для работы с элементами страницы на JavaScript, а в GeckoFX уже из коробки реализованы классы веб-элементов, сделана удобная работа с ними и т.п.

  9. Artem говорит:

    Хорошо. Спасибо за помощь=)

  10. Anton говорит:

    Доброе утро.
    Спасибо за прекрасную статью. Прошу прощения за мои вопросы (так как моих познаний не хватило решить проблему) 1. не подскажите как вывести ScrollBar, а то приходиться активировать приватное поле, потом листать клавишами? 2. Еще не плохо было бы для полного счастья, увидеть код для прокрутки открытой страницы программно.

    • admin говорит:

      1. Никаких публичных полей, для активации скролла, я не видел. Возможно, в новых версиях добавили;
      2. Знаю несколько возможностей скроллить программно: введите _webBrowser.Window.Scr и выберите нужный метод, который предложит студия; также у класса GeckoHtmlElement есть метод ScrollIntoView.

  11. Anton говорит:

    Спасибо за подсказку, получилось сделать прокрутку страницы программно по Вашему совету. К стати вывод скролла можно решить с помощью Вашей статьи (http://www.softez.pp.ua/2014/02/23/tabs-in-geckofx-c-sharp/#comment-1352) путем добавления TabControl и размещения на него компонента GeckoWebBrowser.

  12. Anton говорит:

    Еще, при переходе с поисковика на сайт в данном Вашем примере, фиксируется прямой заход на сайт, а не переход с поисковика. Так вот как можно с эмитировать нажатие кнопкой мышки или выполнение js «onmousedown=»return rwt(this,…)»

    • admin говорит:

      Вот здесь же эмуляция нажатия по ссылке:
      needleLinks[ random.Next( needleLinks.Count ) ].Click();

      • Anton говорит:

        Да совершенно верно вы правильно указали переход по случайной ссылки, даже переходит как требуется на сайт, но вот проблема при проверки перехода на нужный сайт, сам переход в счетчиках ну типа яндекс метрика, не зачитывается как переход с поисковика, а идет как прямой заход на сайт.

      • admin говорит:

        Передачу реферера при эмулировании события нажатия на ссылку не проверял (на досуге проверю). Если действительно не передает — тогда писать разработчикам порта 🙂 Возможно, исправят. Или самому разбираться в коде и исправлять.

  13. Александр говорит:

    Здравствуйте, Вот пытаюсь решить проблему с эмуляцией событий от мыши в геско. побродил по форумах но так походящей инфы под новые версии геско ненашел(. Вот собствено вопрос, как эмулировать клик мыши по ссылкам которые генерируются только реальной мышкой? буду благодарен за любые направления в этом.
    Вот пример, нужно кликнуть на ссылку прослушать — https://translate.google.com/#ru/ru/Привет%20мир!

    • admin говорит:

      Не в курсе, было желание разобраться (или, хотя бы, сделать попытку :)), но так руки и не дошли. Если найду время и разберусь — напишу отдельную статью. Думаю, копать нужно в сторону WinAPI’шных функций типа SendMessage и т.п.

    • admin говорит:

      Наткнулся на какой-то исходник на Java, что-то похожее на эмуляцию мыши есть. На досуге посмотрю, самому интересно 🙂

      • Александр говорит:

        Приветсвую, вот на яве рабочий пример:

        function d ()
        {

        var event = document.createEvent(‘MouseEvents’);
        event.initMouseEvent(‘click’, true, true, window, 1, 10, 10, 10, 10, false, false, false, false, 0, null);
        document.getElementById(‘idid’).dispatchEvent(event);
        }

        Вот думаю пробовать вшить в геско. Пробовал слать SendMessage все получилось, правда когда сворачиваю приложение то кликает не на всех сайтах. Причина пока неизвесна, но сделал через нулевую прозрачность формы, так то все работает отлично, кликает без реальной мыши. Правда, скрывать форму не очень правильно. Хочу разобратся в чем собсвенно дело. Если есть время подключайтесь к поиску решения этой проблемы..

      • admin говорит:

        JavaScript это, а не Java 🙂
        Метрика как реагирует на движение мышью?
        Кстати, код, который выше на js, удалось мне «нативно» в GeckoFx реализовать, даже метрика фиксирует, но пока есть еще пару багов.
        Чтобы разобраться в чем дело, то нужно, хотя бы, на код посмотреть.

  14. Александр говорит:

    Сообшение было обрезано!!!! код хтмл не отобразился в сообщение!!! ссылки были обрезаны, прошу дать скайп для общения или включите аську!!!

    • admin говорит:

      В аське мне постоянно мозги парят по разному поводу, а времени у меня не так много. Потому туда захожу редко.
      Для передачи кода есть классная штука: https://gist.github.com/

  15. Александр говорит:

    Еще одно, я когда то спрашивал как правильно перенаправить окно, потому что правильно оно не перенаправляет. Вы говорили что нада еще и пост-запросы! но оказалось все намного проще. Я сделал вот так:

    private void webBrowser_CreateWindow2(object sender, GeckoCreateWindow2EventArgs e)
    {

    if (checkBox1.Checked == false)
    {
    this.AddTab();
    GeckoWebBrowser webBrowser = this.GetWebBrowserByActiveTab();
    e.WebBrowser = webBrowser;
    }
    if (checkBox1.Checked == true)
    {
    GeckoWebBrowser webBrowser = this.GetWebBrowserByActiveTab();
    e.WebBrowser = webBrowser;
    }
    }

    Окно блокировать не нада! его нада просто было перенаправить так — e.WebBrowser = webBrowser

    в моем примере я ставлю птичку и страничка отображается в том же окне, если птичка снята то страничка открывается в новой вкладке! Все писано по вашим примерах «вкладки геско».

  16. Александр говорит:

    вот код по SendMessage клика

    https://gist.github.com/anonymous/d35b6d7e680cdc9a64f4

    Смысл того что оно то кликает, но т а к . р у клик блокирует. значит не все там гладко, нужно разобраться в этом. Буду ждать ответы по этому поводу. Спасибо…

  17. Александр говорит:

    Все, разобрался). нужно добавить еще MOUSEMOVE. все заработало на ура. только осталось решить почему не срабатывает в свернутом состоянии!

    • admin говорит:

      Вообще не срабатывает, когда свернуто? Возможно, просто приостанавливается поток браузера, когда приложение свернуто.

  18. Anton говорит:

    Спасибо за направление мыслей с кликом мышки разобрался, даже с перемещением курсора мыши, осталось узнать как найти координаты нужного объекта по которой необходимо кликнуть мышкой.

    • admin говорит:

      Ну если вы разобрались, то поделитесь с людьми. Сюда часто захаживают пользователи, которые интересуются работой с геко.
      И вопрос: как метрика реагирует на движение мышью, реализованное на WINAPI?
      П.С. Возможно, скоро напишу статью, как работать с мышью (только там другой алгоритм), если тесты покажут положительный результат.

  19. Anton говорит:

    Я думаю чужие идеи как за свои не стоит выдавать, всю работу мышкой брал от сюда http://www.cyberforum.ru/csharp-beginners/thread290438.html, движение курсора мышки «mouse_event(MouseFlags.Absolute | MouseFlags.Move, x, y, 0, UIntPtr.Zero);» задал тупо в цикле — изменением координат. Метрику еще не проверил, так как нужно привязаться к нужному объекту(ссылке по координатам монитора). Да еще программная работа мышки ни как не привязана к окну самой программы(браузера), она выполняет действие пользователя компа, т.е. если свернуть окно браузера — мышь продолжает свою работу на активировавшимся новом окне(к примеру если был ранее открыт Ворд, значит в окне Ворда).

  20. Александр говорит:

    admin
    23.04.2015 at 13:45
    Так это же не виртуальная мышь. На такое метрика реагировать будет нормально.
    Александр
    Ага, тут вопрос поднят как кликать мышкой в свернтом состоянии!!!
    Кликал по ссылкам, которые четко генерируются жава-скрипт — все работает как часики). В свернутом состоянии не работает! Буду ломать голову дадее.. Видел програмы написанны на геско в которых применяется такая функция как реальный клик мыши в свернутом состоянии, вот и решил покопать. Это нужная вещь, правда не хочется чтобы этим зловредсвували! Админ, Спасибо за ответы!

    • admin говорит:

      Вы почитайте, что это за функция и сразу отпадет вопрос, как имитировать события мыши на свернутых окнах.
      П.С. В общем, походу, я разобрался, как «нативно» с помощью GeckoFx эмулитовать события мыши (клики, движения). Работает даже здесь https://translate.google.com/#ru/ru/%D0%9F%D1%80%D0%B8%D0%B2%D0%B5%D1%82%20%D0%BC%D0%B8%D1%80 (кнопка «Прослушать»), в свернутом состоянии и даже метрика фиксирует все это. Думаю, напишу статью когда-то 😀
      2П.С. Название движка читается, как геко 🙂

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *