Geckofx使用心得(一)

寫這篇的目的很簡單,從萌新到熟悉,我花了2個月時間,甚至花錢買過一個網站的垃圾demo,深受其煩,希望可以讓入坑 Geckofx 的新手少走一些彎路,

做WebBrowser目前有3中方案,

1、基於 IE 的WebBrowser控件

2、Geckofx

3、CefSharp

如果你只是做最簡單基礎的功能,就直接用自帶的WebBrowser控件既可(比如簡單的訪問、取值)

如果你要做的功能比較多,比較複雜,請用 Geckofx 或 CefSharp

Geckofx (firefox)

優點:cookie可以單獨管理,可以每個進程單獨設置一個cookie地址(我目前直接把cookie放在程序運行目錄下,就是有點大)

缺點:使用的人很少,不僅僅是國內,就連國外用的人我感覺也不多,搜出來的文檔都是v22時代,甚至更早,最新的v45討論的更少,中文文檔就更不用想了。

CefSharp(chrome)

優點:國內、國外用的人都很多,國內也能搜出很多詳細的中文文檔,最安逸的是國內有專門的QQ羣 235651002

缺點:我由於先入了 Geckofx 的坑,所以實在不想推翻再入 CefSharp 的坑。

建議:如果你還沒有入過坑,建議直接 CefSharp 上路

//*************************************** 正文 ****************************************

Geckofx 安裝:

首先下載NuGet並安裝,NuGet下載的都是編譯好的庫,如非必要,真不建議下源碼來自己編譯,即便是你想把dll打包到exe裏面,我建議還不如用加殼工具,直接打包。

NuGet使用 ->菜單->工具->NuGet包管理器->管理解決方案的NuGet包程序

打開後搜索並安裝



tabpage.Controls.Add(browser);//把瀏覽器加入到選擇夾控件內,如果加主窗口,直接this.Controls.Add(browser)
        /// <summary>
        /// 瀏覽器主對象
        /// </summary>
        GeckoWebBrowser browser;
	/// <summary>
        /// Geckofx 初始化各項設置
        /// </summary>
        private void GeckoInit()
            {
            #region 設置瀏覽器各屬性,先後順序不能變
            var app_dir = Environment.CurrentDirectory;//程序目錄
            /* 關鍵代碼  */  //出處:https://www.cnblogs.com/huangcong/p/5796695.html
            string directory = Path.Combine(app_dir, "Cookies", 自定義文件夾名);//cookie目錄
            if (!Directory.Exists(directory))
                Directory.CreateDirectory(directory);//檢測目錄是否存在
            Gecko.Xpcom.ProfileDirectory = directory;//綁定cookie目錄
            /* 關鍵代碼 結束 */




            Xpcom.Initialize(Path.Combine(app_dir, "FireFox"));//初始化 Xpcom
            browser = new GeckoWebBrowser() { Dock = DockStyle.Fill }; //創建瀏覽器實例
            this.browser.Name = "browser";
            GeckoPreferences.User["gfx.font_rendering.graphite.enabled"] = true;//設置偏好:字體
            GeckoPreferences.User["privacy.donottrackheader.enabled"] = true;//設置瀏覽器不被追蹤
            GeckoPreferences.User["general.useragent.override"] = "User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:59.0) Gecko/20100101 Firefox/59.0";
            GeckoPreferences.User["intl.accept_languages"] = "zh-CN,zh;q=0.9,en;q=0.8";//不設置的話默認是英文區
            //GeckoPreferences.User["permissions.default.image"] = 2; //  block  image  禁止加載圖片
            //GeckoPreferences.User["plugin.state.flash"] = 0;  // bloack flash禁止加載flash 
            //註冊事件
            Gecko.CertOverrideService.GetService().ValidityOverride += geckoWebBrowser1_ValidityOverride;//好像是證書設置回調等
            //browser.DocumentCompleted += Browser_DocumentCompleted;//文檔加載完成時間,但js動態生成的這個不準確,據說用狀態欄的文字最好
            browser.CreateWindow += Browser_CreateWindow;//打開新窗口事件,全部設爲在同一窗口打開
            browser.DomClick += browser_DomClick;
            browser.UseHttpActivityObserver = true;//開啓攔截請求
            browser.ObserveHttpModifyRequest += Browser_ObserveHttpModifyRequest;//攔截請求(在創建窗口之前就攔截。)同時取消創建創建,在主窗口打開
            //Gecko.LauncherDialog.Download += GeckoDownload;//註冊下載事件
            #endregion
            /*  備用     
                GeckoPreferences.User["places.history.enabled"] = false;
                GeckoPreferences.User["security.warn_viewing_mixed"] = false;
                GeckoPreferences.User["plugin.state.flash"] = 0;
                GeckoPreferences.User["browser.cache.disk.enable"] = false;
                GeckoPreferences.User["browser.cache.memory.enable"] = false;
                GeckoPreferences.User["browser.xul.error_pages.enabled"] = false;
                GeckoPreferences.User["dom.max_script_run_time"] = 0; //let js run as long as it needs to; prevents timeout errors
                GeckoPreferences.User["browser.download.manager.showAlertOnComplete"] = false;
                GeckoPreferences.User["privacy.popups.showBrowserMessage"] = false;
             */
            }
		/// <summary>
        /// 請求監測,post數據且不打開新窗口,直接在本窗口打開
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Browser_ObserveHttpModifyRequest(object sender, GeckoObserveHttpModifyRequestEventArgs e)
            {
            if (State >= States.SubmitOrder && State < States.ConfirmPay)
                {
                try
                    {
                    if (e.RequestMethod != "POST")
                        return;
                    string url = e.Uri.ToString();
                    string targetUrl = @"https://";
                    if (!url.Contains(targetUrl) && url != targetUrl)
                        return;




                    #region 打印log
                    //bool print = true;
                    //if (print)
                    //    {
                    //    string str = "";
                    //    str += $"\r\n活動觀察:Uri = {e.Uri}\r\n";
                    //    if (e.ReqBodyContainsHeaders ?? false)
                    //        e.RequestHeaders.ForEach(h => { str += $"活動觀察:{h.Key}:{h.Value}\r\n"; });
                    //    str += $"活動觀察:RequestMethod = {e.RequestMethod}\r\n";
                    //    str += $"活動觀察:Referrer = {e.Referrer}\r\n";
                    //    if (e.RequestBody.Length > 0)
                    //        str += $"活動觀察:RequestBody = {Encoding.Default.GetString(e.RequestBody)}\r\n";
                    //    Logger.Log(str);
                    //    }
                    #endregion




                    #region 獲取Post數據,轉到主窗口,取消新建窗口
                    MimeInputStream headers = MimeInputStream.Create(); //複製 header
                    if (e.ReqBodyContainsHeaders ?? false)
                        {
                        foreach (var h in e.RequestHeaders)
                            {
                            if (h.Key != "Accept")
                                headers.AddHeader(h.Key, h.Value);
                            }
							//下面幾個Header根據實際情況自己添加刪除
                        headers.AddHeader("Upgrade-Insecure-Requests", "1");
                        headers.AddHeader("Origin", "https://");
                        headers.AddHeader("Cache-Control", "max-age=0");
                        headers.AddHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8");
                        }




                    if (e.RequestBody.Length <= 0)//取post數據
                        return;
                    MimeInputStream postData = MimeInputStream.Create();
                    string ContentCharset = Encoding.Default.GetString(e.RequestBody);
					//下面這條Split是因爲的用的項目post時裏面有多餘的長度信息,由於長度信息瀏覽器會自動添加,所以我需要去掉,其他人如果不是這種情況,刪除這句
                    ContentCharset = ContentCharset.Split("\r\n".ToCharArray()).Where(s => !string.IsNullOrEmpty(s)).First(s => !s.Contains("Content-"));//把包含的header去掉,取出純post內容
                    postData.AddHeader("Content-Type", "application/x-www-form-urlencoded");
                    postData.AddContentLength = true;
                    postData.SetData(ContentCharset);
                    //用本地窗口跳轉
                    bool Navigateable = browser.Navigate($"{e.Uri}", GeckoLoadFlags.BypassCache, $"{e.Referrer}", postData, headers);
					
                    browser.UseHttpActivityObserver = false;
                    browser.ObserveHttpModifyRequest -= Browser_ObserveHttpModifyRequest;//取消時間監測,因爲跳轉到新窗口的時候又會觸發這個造成死循環
                    #endregion
                    e.Cancel = true;//取消在新窗口打開
                    }
                catch (Exception ex)
                    {
                    //Logger.Error($"ObserveHttpModifyRequest異常 = {ex}");
                    }
                }




            }




	/// <summary>
        /// 打開新窗口事件,全部設爲在同一窗口打開
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
	private void Browser_CreateWindow(object sender, GeckoCreateWindowEventArgs e)
            {
            browser.Navigate(e.Uri);
            e.Cancel = true;
            //e.InitialHeight = 1;
            //e.InitialWidth = 1;
            }








	/// <summary>  
        /// 設置代理 GeckoFx  
        /// </summary>  
        private void GeckoFxSetting()
            {
            var ip = "192.168.1.1";
            var port = "3128";
            Gecko.GeckoPreferences.User["network.proxy.http"] = ip;
            Gecko.GeckoPreferences.User["network.proxy.http_port"] = int.Parse(port);
            Gecko.GeckoPreferences.User["network.proxy.type"] = 1;
            // network.proxy.type 取值  
            //0 – Direct connection, no proxy. (Default)  
            //1 – Manual proxy configuration.  
            //2 – Proxy auto-configuration (PAC).  
            //4 – Auto-detect proxy settings.  
            //5 – Use system proxy settings (Default in Linux).      
            }




        /// <summary>  
        /// 文檔單擊事件  
        /// </summary>  
        /// <param name="sender"></param>  
        /// <param name="e"></param>  
        private void browser_DomClick(object sender, DomMouseEventArgs e)
            {
            var ele = e.CurrentTarget.CastToGeckoElement();
            ele = e.Target.CastToGeckoElement();
            //短xpath  
            var xpath1 = GetSmallXpath(ele);
            Logger.Log("xpath1:" + xpath1);
            //長xpath  
            var xpath2 = GetXpath(ele);
            Logger.Log("xpath2:" + xpath2);
            }
        /// <summary>
        /// 獲取短 xpath
        /// </summary>
        /// <param name="node"></param>
        /// <returns></returns>
        private string GetSmallXpath(GeckoNode node)
            {
            if (node == null)
                return "";
            if (node.NodeType == NodeType.Attribute)
                {
                return String.Format("{0}/@{1}", GetSmallXpath(((GeckoAttribute)node).OwnerDocument), node.LocalName);
                }
            if (node.ParentNode == null)
                {
                return "";
                }
            string elementId = ((GeckoHtmlElement)node).Id;
            if (!String.IsNullOrEmpty(elementId))
                {
                return String.Format("//*[@id=\"{0}\"]", elementId);
                }
            int indexInParent = 1;
            GeckoNode siblingNode = node.PreviousSibling;
            while (siblingNode != null)
                {
                if (siblingNode.LocalName == node.LocalName)
                    {
                    indexInParent++;
                    }
                siblingNode = siblingNode.PreviousSibling;
                }
            return String.Format("{0}/{1}[{2}]", GetSmallXpath(node.ParentNode), node.LocalName, indexInParent);
            }
        /// <summary>
        /// 獲取長xpath
        /// </summary>
        /// <param name="node"></param>
        /// <returns></returns>
        private string GetXpath(GeckoNode node)
            {
            if (node == null)
                return "";
            if (node.NodeType == NodeType.Attribute)
                {
                return String.Format("{0}/@{1}", GetXpath(((GeckoAttribute)node).OwnerDocument), node.LocalName);
                }
            if (node.ParentNode == null)
                {
                return "";
                }
            int indexInParent = 1;
            GeckoNode siblingNode = node.PreviousSibling;
            while (siblingNode != null)
                {
                if (siblingNode.LocalName == node.LocalName)
                    {
                    indexInParent++;
                    }
                siblingNode = siblingNode.PreviousSibling;
                }
            return String.Format("{0}/{1}[{2}]", GetXpath(node.ParentNode), node.LocalName, indexInParent);
            }

上面是我所用到和收集到的 Geckofx 配置。另外還寫了個擴展類,但是由於並不通用,只有3個重要的寫在下面

1、Exten_SetInputValue 設置 Input 值(主要用於填表),

2、Exten_GetWindowByFrames 獲取IFrame內容並轉爲GeckoWindow窗口

 是因爲IFrame框架下的其他html元素和node節點不能在主窗口下通過GetElementById等方法獲取

3、Exten_ExecuteJQuery 注入js到網頁並執行(可以根據需求自行修改)

如果無效的話還可以用這種方法執行,這種感覺更好一些更直接一些,只不過最早用的注入式,所以懶得改

using (AutoJSContext context = new AutoJSContext(window))
                        {

                        string result;

                       context.EvaluateScript(@"CommonConstants.IS_EMPTY_USERNAME_ME", out result);

//取IS_EMPTY_USERNAME_ME的值

                        }
        private delegate void SetInputValueHandle(GeckoWebBrowser _browser, string id, string value, GeckoWindow _window = null);
    /// <summary>
    /// 設置 Input 值,主要是輸入框
    /// 原型:GeckoInputElement username = new GeckoInputElement(browser.Window.Frames[0].Document.GetElementById("userName").DomObject);
    /// return username.Value = "設置的值";
    /// </summary>
    /// <param name="_browser"></param>
    /// <param name="id"></param>
    public static void Exten_SetInputValue(this GeckoWebBrowser _browser, string id, string value, GeckoWindow _window = null)
        {
        if (_browser.InvokeRequired)
            {
            SetInputValueHandle methon = new SetInputValueHandle(Exten_SetInputValue);//委託的方法參數應和SetCalResult一致
            IAsyncResult syncResult = _browser.BeginInvoke(methon, new object[] { _browser, id, value, _window }); //此方法第二參數用於傳入方法,代替形參result
            _browser.EndInvoke(syncResult);
            }
        else
            {
            if (_window == null)
                _window = _browser.Window;
            var inputElement = _window.Document.GetElementById(id);
            if (inputElement == null)
                return;


            GeckoInputElement input = new GeckoInputElement(inputElement.DomObject);
            if (input == null)
                return;


            input.Value = value;
            }
        }
	private delegate GeckoWindow GetWindowByFramesHandler(GeckoWebBrowser _browser, string UrlContains, string title);
    /// <summary>
    /// 遍歷主窗口下的框架窗口(IFrame),尋找出 url 和 title 符合要求的窗口
    /// 原型:return _browser.Window.Frames.FirstOrDefault(f => f.Document.Uri.Contains(UrlContains) && f.Document.Title.Contains(title));
    /// </summary>
    /// <param name="_browser"></param>
    /// <param name="UrlContains"></param>
    /// <param name="title"></param>
    /// <returns></returns>
    public static GeckoWindow Exten_GetWindowByFrames(this GeckoWebBrowser _browser, string UrlContains, string title)
        {
        try
            {
            if (_browser.InvokeRequired)
                {
                GetWindowByFramesHandler methon = new GetWindowByFramesHandler(Exten_GetWindowByFrames);//委託的方法參數應和SetCalResult一致
                IAsyncResult syncResult = _browser.BeginInvoke(methon, new object[] { _browser, UrlContains, title }); //此方法第二參數用於傳入方法,代替形參result
                return (GeckoWindow)_browser.EndInvoke(syncResult);
                }
            else
                {
                if (_browser == null)
                    return null;
                if (_browser.Document == null)
                    return null;


                var frames = _browser.Document.GetElementsByTagName("iframe");
                if (frames == null)
                    return null;


                foreach(GeckoHtmlElement frame in frames)
                    {
                    GeckoIFrameElement IFrameElement = new GeckoIFrameElement(frame.DomObject);
                    if (IFrameElement == null)
                        continue;


                    if (IFrameElement.ContentWindow == null)
                        continue;
                    GeckoWindow window = IFrameElement.ContentWindow;


                    if (window.Document.Uri.Contains(UrlContains) && window.Document.Title.Contains(title))
                        return window;
                    }
                return null;
                }
            }
        catch { return null; }
        }
		    private delegate void ExecuteJQueryHandle(GeckoWebBrowser _browser, string _JsCode, bool deleteCode = false, GeckoWindow _window = null);
    /// <summary>
    /// 執行js代碼,參考資料:https://www.cnblogs.com/huangcong/p/6075723.html
    /// </summary>
    /// <param name="_browser"></param>
    /// <param name="_scriptStr"></param>
    /// <param name="_window"></param>
    public static void Exten_ExecuteJQuery(this GeckoWebBrowser _browser, string _JsCode, bool deleteCode = false, GeckoWindow _window = null)
        {
        if (_browser.InvokeRequired)
            {
            ExecuteJQueryHandle methon = new ExecuteJQueryHandle(Exten_ExecuteJQuery);//委託的方法參數應和SetCalResult一致
            IAsyncResult syncResult = _browser.BeginInvoke(methon, new object[] { _browser, _JsCode, deleteCode, _window }); //此方法第二參數用於傳入方法,代替形參result
            _browser.EndInvoke(syncResult);
            }
        else
            {
            if (_window == null)
                _window = _browser.Window;


            if (string.IsNullOrWhiteSpace(_JsCode))
                return;
            
            var script = _window.Document.CreateElement("script");//創建一個js節點
            script.TextContent = _JsCode;
            _window.Document.GetElementsByTagName("body").First().AppendChild(script);//把節點加入到 body 以便實時執行
            if (deleteCode)
                {
                Thread.Sleep(500);
                var node = _window.Document.GetElementsByTagName("body").First().ChildNodes.FirstOrDefault(n => n.TextContent == _JsCode);//從body取出剛剛加進去的節點
                _window.Document.GetElementsByTagName("body").First().RemoveChild(node);//刪除,不然加多少次就會存在多少個節點,有可能衝突
                }
            //本來用這個最好,但是 jquery 取不到對象
            //JQueryExecutor jquery = new JQueryExecutor(browser.Window);
            ////checkCardAmount()
            //if (jquery.ExecuteJQuery("typeof jQuery == 'undefined'").ToBoolean())
            //    {
            //    jquery.ExecuteJQuery(@"jQuery(""#phoneNo"").blur();");
            //    }
            }
        }

關於多線程使用:不建議多線程多窗口使用,但可以多進程,因爲 Geckofx 的初始化是進程通用的,也就是說初始化的cookie目錄以及其他信息在本進程內所有線程是共用的。如果你想多開,就多開幾個進程吧。

如果想在線程內操作 GeckoWebBrowser ,那就要用到我上面擴展的寫法了,要用 BeginInvoke,並且最好是用 EndInvoke等待結果。你需要用到的任何操作都必須這樣(否則程序會崩潰,同時千萬要做null值檢查)。

關於cookie操作:

只能用 CookieManager 來添加和刪除,獲取 cookie 用 browser.Document.Cookie(直接修改這個無效)

CookieManager.Add(Host, Path, Name, Value, IsSecure, IsSession, IsHttpOnly, Expiry);



註明:轉載請註明出處,謝謝。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章