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)
            {
                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);

 

 

注明:转载请注明出处,谢谢。

 

  • 4
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 15
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 15
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值