概述
写这篇的目的很简单,从萌新到熟悉,我花了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 += $"rn活动观察:Uri = {e.Uri}rn";
// if (e.ReqBodyContainsHeaders ?? false)
// e.RequestHeaders.ForEach(h => { str += $"活动观察:{h.Key}:{h.Value}rn"; });
// str += $"活动观察:RequestMethod = {e.RequestMethod}rn";
// str += $"活动观察:Referrer = {e.Referrer}rn";
// if (e.RequestBody.Length > 0)
// str += $"活动观察:RequestBody = {Encoding.Default.GetString(e.RequestBody)}rn";
// 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("rn".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);
注明:转载请注明出处,谢谢。
最后
以上就是鲜艳面包为你收集整理的Geckofx使用心得(一)的全部内容,希望文章能够帮你解决Geckofx使用心得(一)所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复