【问题标题】:How to "manually" go back with a WebBrowser?如何“手动”使用 Web 浏览器返回?
【发布时间】:2011-07-21 21:10:37
【问题描述】:

我正在开发一个网络爬虫,它有时需要记住某个特定页面,然后转到其他一些页面,然后再返回该页面。目前我只保存页面的 URL,但这不适用于像谷歌地图这样的页面,其中 URL 总是相同的。

我可以看到GoBack 方法确实返回到前一页,所以WebBrowser 不知何故记得前一页是什么。我该如何手动执行此操作?我可以计算自从我想返回的页面以来已经访问了多少页面,然后根据需要多次调用GoBack,但这是非常不可靠和不优雅的。所以我想知道如何实现GoBackToAParticularPage 方法。

我认为有一件事会让我更接近解决方案:保存所有框架的 URL,然后在返回该页面时将它们放回去。我认为这至少可以解决谷歌地图的问题。我还没有测试过。我不知道这样做的正确方法是什么。在设置它们的 URL 之前,我需要等待框架存在。

【问题讨论】:

    标签: c# winforms browser


    【解决方案1】:

    通过javascript Location对象你可以完成你的任务。

    <FORM><INPUT TYPE="BUTTON" VALUE="Go Back" 
    ONCLICK="history.go(-1)"></FORM>
    

    也检查一下

    JavaScript History Object

    历史信息

    【讨论】:

    • 这可行,但它意味着计算导航,这是我想要避免的。
    • @jsoldi - 我认为您需要检查历史对象的文档 .. 但我认为它不允许您在不计算导航的情况下返回
    【解决方案2】:

    试试这个!

    javascript:history.go(-1)"

    【讨论】:

    • 我所做的是前后获取history.length,然后调用history.go(before - after)。一个大乱子。顺便说一句,所有这些都是通过注入的 JavaScript 实现的。
    • @jsoldi:您不必注入 JS 来执行此操作。 C# 中的 Document.Window.History.Length 属性应该会更好。
    【解决方案3】:

    你可以使用

    webBrowser1.Document.Window.History.Go(x);
    

    其中 x 是一个 int,表示浏览器历史记录中的相对位置。

    x=-2 将向后导航两页。

    更新:有关HtmlHistory.Go()的更多信息

    【讨论】:

    • 出于好奇,这个答案在什么情况下不能正常工作,或者它如何不能产生预期的行为?据我所知,它适用于谷歌地图示例。您真的希望访问 History 对象本身的数据吗?
    • 没有。我不想计算导航,因为您如何真正知道 DocumentComplete 何时算作历史记录?我没有可靠的方法知道x 应该是什么。当我在那个页面时,我知道我想回到哪个页面,但是一旦它导航离开,我不知道中间有多少页面。
    • 浏览器设置为不设置历史记录会怎样?
    • @jsoldi 好吧,我的代码中没有使用DocumentComplete。当我找到“我想回到哪个页面”(当我按下按钮时)时,我只是保存历史长度。这最终适用于具有不同框架和诸如此类的相当多的复杂导航。然而,我不得不承认它在非常特殊的情况下不可靠的,我相信这对你来说有点没用。 :(
    • @marshal 如果您通过将 CLng(2)(或数字 2)传递给导航方法来取消历史记录,那么您将无法返回该页面,它会从 WB 中消失也控制历史记录(就像它从未发生过一样),但您从该链接访问的任何链接仍将具有历史记录信息,因为 .navigate 历史记录取消仅适用于该导航页面请求。但是,也有一些方法可以取消点击链接的历史记录,方法是拦截 @BeforeNavigate、取消和重新导航并取消历史记录(重新导航发生在任何内容开始加载之前)。
    【解决方案4】:

    浏览器历史设计是不透明的;否则它会打开一个安全漏洞:您真的希望您访问的每个页面都可以查看您访问过的页面/站点吗?应该不会吧。

    要做你想做的事,你需要实现自己的 URI 堆栈,跟踪需要重新访问的内容。

    【讨论】:

    • 这就是我所做的,但它在谷歌地图和其他一些网站上搞砸了,因为每个页面上的 URL 都是相同的。但正如我所说,当通过调用 WebBrowser 的 GoBack 返回时,它确实可以工作,因此 IE 会做一些事情,而不仅仅是导航到以前的 URL.vAndrhats 我想要手动执行的操作。
    • 当您通过历史对象返回时它可以工作,因为浏览器也在缓存结果。对于 URL 保持不变而返回的内容发生变化的页面(由于 AJAX 调用或表单帖子),除了 URI 之外,您可能还需要为该特定 URI 请求返回的内容实现自己的缓存。
    • 知道怎么做吗?我实际上尝试保存要返回的页面的 DocumentText,然后导航到该页面,然后使用保存的文本设置 DocumentText,但这会将 URL 更改为大约空白并弄乱相关链接。我认为您的解决方案可行,但不知道如何实现该缓存系统。
    【解决方案5】:

    如果您不需要直观地看到正在发生的事情,那么可能有更优雅的方法可以使用 WebClient 类导航和解析 url,也许详细说明您的特定程序会产生更清晰的结果。

    【讨论】:

      【解决方案6】:

      假设您在表单上有一个网络浏览器控件,并且您正在尝试实现返回。

      以下是解决方案。 (假设有误,请指正)

      将网页浏览器、文本框、按钮添加为 btnBack

      历史变量也有导航的url数据(但目前没有使用)。

      C#解决方案

      using System;
      using System.Collections.Generic;
      using System.ComponentModel;
      using System.Data;
      using System.Drawing;
      using System.Linq;
      using System.Text;
      using System.Windows.Forms;
      
      namespace WindowsFormsApplication1
      {
      public partial class Form1 : Form
      {
          public Form1()
          {
              InitializeComponent();
          }
          private void Form1_Load(object sender, EventArgs e)
          {
               WebBrowser1.Url = new Uri("http://maps.google.com");
          }
          Stack< String> History = new Stack<String>();
      
          private void WebBrowser1_Navigating(object sender, WebBrowserNavigatingEventArgs e)
          {
                  TextBox1.Text = e.Url.ToString();
                  History.Push(e.Url.ToString());
          }
      
          private void btnBack_Click(object sender, EventArgs e)
          {
              if(WebBrowser1.CanGoBack) 
              {
                  WebBrowser1.GoBack();
              }
      
          }
      
      }
      }
      

      Vb解决方案

      Public Class Form1
      Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
          WebBrowser1.Url = New Uri("http://maps.google.com")
      End Sub
      
      Private Sub WebBrowser1_Navigating(ByVal sender As Object, ByVal e As System.Windows.Forms.WebBrowserNavigatingEventArgs) Handles WebBrowser1.Navigating
          TextBox1.Text = e.Url.ToString
          History.Push(e.Url.ToString)
      End Sub
      Dim History As New Stack(Of String)
      Private Sub btnBack_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnBack.Click
          If WebBrowser1.CanGoBack Then
              WebBrowser1.GoBack()
          End If
      End Sub
      
      End Class
      

      【讨论】:

        【解决方案7】:

        您不想使用history.go(-1),因为它不可靠。但是,您不能使用 URL,因为在 GoogleMaps 之类的页面中,URL 总是相同的。

        如果 URL 相同但内容不同,则意味着确定页面内容的值是从 URL 以外的地方提取的。

        这可能在哪里?

        您最可能的怀疑是已发布的表单集合,但数据也可能来自 cookie。

        我认为索引绝对位置比索引相对位置更有意义,因为正如您所指出的,相对位置可能不可靠。问题是您需要获取发送到 Web 服务器的所有数据,以了解其实际绝对位置是什么(因为 URI 不够用)。

        执行此操作的方法是创建页面的本地副本,并将提交 url(可以是链接、表单或 javascript)替换为您服务器上的 URL。然后,当您单击 GoogleMaps 页面上的某些内容以触发更改(这似乎不会影响 URL)时,您将在您的服务器上收到该数据,并且能够确定实际位置。

        把它想象成一个查询字符串。

        如果我有

        <form action="http://myhost.com/page.html" method="get">
           <input type="hidden" name="secret_location_parameter" value="mrbigglesworth" />
           <input type="submit" />
        </form>
        

        然后我点击提交按钮,我被带到了 url

         http://myhost.com/page.html?secret_location_parameter=mrbigglesworth
        

        但是,如果我有

        <form action="http://myhost.com/page.html" method="post">
           <input type="hidden" name="secret_location_parameter" value="mrbigglesworth" />
           <input type="submit" />
        </form>
        

        然后我点击提交按钮,然后我被带到 url

         http://myhost.com/page.html
        

        服务器仍然接收secret_location_parameter=mrbigglesworth,但它以表单值而不是查询字符串值的形式获取它,因此从 url 中看不到它。服务器可能会根据secret_location_parameter 的值呈现不同的页面,但不会更改 url,如果使用 post 方法,则会出现多个页面驻留在同一个 url。

        我的意思是,您可能从错误的角度解决问题,因为您不了解幕后发生的事情。我当然是在做出假设,但根据您提出问题的方式,我认为这可能对您有所帮助

        【讨论】:

          【解决方案8】:

          以编程方式将标记元素添加到 DOM 中,用于您稍后想要返回的页面。回溯浏览器历史记录时,在每个 history.go(-1) 之后检查该标记,并在遇到它时停止。这在某些情况下可能被证明是不可靠的,在这种情况下,记住深度级别可以作为一种备用方法。

          您可能需要试验插入元素的正确时间,以确保它被正确记录在历史记录中。

          【讨论】:

            【解决方案9】:

            如果其他人可以从中受益,这就是我最终的做法。唯一需要注意的是,如果旅行日志之间的页面过多,则该条目可能不再存在。可能有一种方法可以增加历史记录大小,但是由于必须有一些限制,所以我使用TravelLog.GetTravelLogEntries 方法来查看条目是否仍然存在,如果不存在,则使用 URL。

            大部分代码来自PInvoke

            using System;
            using System.Runtime.InteropServices;
            using System.Windows.Forms;
            using System.Collections.Generic;
            
            namespace TravelLogUtils
            {
                [ComVisible(true), ComImport()]
                [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
                [GuidAttribute("7EBFDD87-AD18-11d3-A4C5-00C04F72D6B8")]
                public interface ITravelLogEntry
                {
                    [return: MarshalAs(UnmanagedType.I4)]
                    [PreserveSig]
                    int GetTitle([Out] out IntPtr ppszTitle); //LPOLESTR LPWSTR
            
                    [return: MarshalAs(UnmanagedType.I4)]
                    [PreserveSig]
                    int GetURL([Out] out IntPtr ppszURL); //LPOLESTR LPWSTR
                }
            
                [ComVisible(true), ComImport()]
                [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
                [GuidAttribute("7EBFDD85-AD18-11d3-A4C5-00C04F72D6B8")]
                public interface IEnumTravelLogEntry
                {
                    [return: MarshalAs(UnmanagedType.I4)]
                    [PreserveSig]
                    int Next(
                        [In, MarshalAs(UnmanagedType.U4)] int celt,
                        [Out] out ITravelLogEntry rgelt,
                        [Out, MarshalAs(UnmanagedType.U4)] out int pceltFetched);
            
                    [return: MarshalAs(UnmanagedType.I4)]
                    [PreserveSig]
                    int Skip([In, MarshalAs(UnmanagedType.U4)] int celt);
            
                    void Reset();
            
                    void Clone([Out] out ITravelLogEntry ppenum);
                }
            
                public enum TLMENUF
                {
                    /// <summary>
                    /// Enumeration should include the current travel log entry.
                    /// </summary>
                    TLEF_RELATIVE_INCLUDE_CURRENT = 0x00000001,
                    /// <summary>
                    /// Enumeration should include entries before the current entry.
                    /// </summary>
                    TLEF_RELATIVE_BACK = 0x00000010,
                    /// <summary>
                    /// Enumeration should include entries after the current entry.
                    /// </summary>
                    TLEF_RELATIVE_FORE = 0x00000020,
                    /// <summary>
                    /// Enumeration should include entries which cannot be navigated to.
                    /// </summary>
                    TLEF_INCLUDE_UNINVOKEABLE = 0x00000040,
                    /// <summary>
                    /// Enumeration should include all invokable entries.
                    /// </summary>
                    TLEF_ABSOLUTE = 0x00000031
                }
            
                [ComVisible(true), ComImport()]
                [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
                [GuidAttribute("7EBFDD80-AD18-11d3-A4C5-00C04F72D6B8")]
                public interface ITravelLogStg
                {
                    [return: MarshalAs(UnmanagedType.I4)]
                    [PreserveSig]
                    int CreateEntry([In, MarshalAs(UnmanagedType.LPWStr)] string pszUrl,
                        [In, MarshalAs(UnmanagedType.LPWStr)] string pszTitle,
                        [In] ITravelLogEntry ptleRelativeTo,
                        [In, MarshalAs(UnmanagedType.Bool)] bool fPrepend,
                        [Out] out ITravelLogEntry pptle);
            
                    [return: MarshalAs(UnmanagedType.I4)]
                    [PreserveSig]
                    int TravelTo([In] ITravelLogEntry ptle);
            
                    [return: MarshalAs(UnmanagedType.I4)]
                    [PreserveSig]
                    int EnumEntries([In] int TLENUMF_flags, [Out] out IEnumTravelLogEntry ppenum);
            
                    [return: MarshalAs(UnmanagedType.I4)]
                    [PreserveSig]
                    int FindEntries([In] int TLENUMF_flags,
                    [In, MarshalAs(UnmanagedType.LPWStr)] string pszUrl,
                    [Out] out IEnumTravelLogEntry ppenum);
            
                    [return: MarshalAs(UnmanagedType.I4)]
                    [PreserveSig]
                    int GetCount([In] int TLENUMF_flags, [Out] out int pcEntries);
            
                    [return: MarshalAs(UnmanagedType.I4)]
                    [PreserveSig]
                    int RemoveEntry([In] ITravelLogEntry ptle);
            
                    [return: MarshalAs(UnmanagedType.I4)]
                    [PreserveSig]
                    int GetRelativeEntry([In] int iOffset, [Out] out ITravelLogEntry ptle);
                }
            
                [ComImport, ComVisible(true)]
                [Guid("6d5140c1-7436-11ce-8034-00aa006009fa")]
                [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
                public interface IServiceProvider
                {
                    [return: MarshalAs(UnmanagedType.I4)]
                    [PreserveSig]
                    int QueryService(
                        [In] ref Guid guidService,
                        [In] ref Guid riid,
                        [Out] out IntPtr ppvObject);
                }
            
                public class TravelLog
                {
                    public static Guid IID_ITravelLogStg = new Guid("7EBFDD80-AD18-11d3-A4C5-00C04F72D6B8");
                    public static Guid SID_STravelLogCursor = new Guid("7EBFDD80-AD18-11d3-A4C5-00C04F72D6B8");
            
                    //public static void TravelTo(WebBrowser webBrowser, int 
                    public static ITravelLogEntry GetTravelLogEntry(WebBrowser webBrowser)
                    {
                        int HRESULT_OK = 0;
            
                        SHDocVw.IWebBrowser2 axWebBrowser = (SHDocVw.IWebBrowser2)webBrowser.ActiveXInstance;
                        IServiceProvider psp = axWebBrowser as IServiceProvider;
                        if (psp == null) throw new Exception("Could not get IServiceProvider.");
            
                        IntPtr oret = IntPtr.Zero;            
                        int hr = psp.QueryService(ref SID_STravelLogCursor, ref IID_ITravelLogStg, out oret);            
                        if ((oret == IntPtr.Zero) || (hr != HRESULT_OK)) throw new Exception("Failed to query service.");
            
                        ITravelLogStg tlstg = Marshal.GetObjectForIUnknown(oret) as ITravelLogStg;
                        if (null == tlstg) throw new Exception("Failed to get ITravelLogStg");            
                        ITravelLogEntry ptle = null;
            
                        hr = tlstg.GetRelativeEntry(0, out ptle);
            
                        if (hr != HRESULT_OK) throw new Exception("Failed to get travel log entry with error " + hr.ToString("X"));
            
                        Marshal.ReleaseComObject(tlstg);
                        return ptle;
                    }
            
                    public static void TravelToTravelLogEntry(WebBrowser webBrowser, ITravelLogEntry travelLogEntry)
                    {
                        int HRESULT_OK = 0;
            
                        SHDocVw.IWebBrowser2 axWebBrowser = (SHDocVw.IWebBrowser2)webBrowser.ActiveXInstance;
                        IServiceProvider psp = axWebBrowser as IServiceProvider;
                        if (psp == null) throw new Exception("Could not get IServiceProvider.");
            
                        IntPtr oret = IntPtr.Zero;
                        int hr = psp.QueryService(ref SID_STravelLogCursor, ref IID_ITravelLogStg, out oret);
                        if ((oret == IntPtr.Zero) || (hr != HRESULT_OK)) throw new Exception("Failed to query service.");
            
                        ITravelLogStg tlstg = Marshal.GetObjectForIUnknown(oret) as ITravelLogStg;
                        if (null == tlstg) throw new Exception("Failed to get ITravelLogStg");
            
                        hr = tlstg.TravelTo(travelLogEntry);
            
                        if (hr != HRESULT_OK) throw new Exception("Failed to travel to log entry with error " + hr.ToString("X"));
            
                        Marshal.ReleaseComObject(tlstg);
                    }
            
                    public static HashSet<ITravelLogEntry> GetTravelLogEntries(WebBrowser webBrowser)
                    {
                        int HRESULT_OK = 0;
            
                        SHDocVw.IWebBrowser2 axWebBrowser = (SHDocVw.IWebBrowser2)webBrowser.ActiveXInstance;
                        IServiceProvider psp = axWebBrowser as IServiceProvider;
                        if (psp == null) throw new Exception("Could not get IServiceProvider.");
            
                        IntPtr oret = IntPtr.Zero;
                        int hr = psp.QueryService(ref SID_STravelLogCursor, ref IID_ITravelLogStg, out oret);
                        if ((oret == IntPtr.Zero) || (hr != HRESULT_OK)) throw new Exception("Failed to query service.");
            
                        ITravelLogStg tlstg = Marshal.GetObjectForIUnknown(oret) as ITravelLogStg;
                        if (null == tlstg) throw new Exception("Failed to get ITravelLogStg");
            
                        //Enum the travel log entries
                        IEnumTravelLogEntry penumtle = null;
                        tlstg.EnumEntries((int)TLMENUF.TLEF_ABSOLUTE, out penumtle);
                        hr = 0;
                        ITravelLogEntry ptle = null;
                        int fetched = 0;
                        const int MAX_FETCH_COUNT = 1;
            
                        hr = penumtle.Next(MAX_FETCH_COUNT, out ptle, out fetched);
                        Marshal.ThrowExceptionForHR(hr);
            
                        HashSet<ITravelLogEntry> results = new HashSet<ITravelLogEntry>();
            
                        for (int i = 0; 0 == hr; i++)
                        {
                            if (ptle != null) results.Add(ptle);
                            hr = penumtle.Next(MAX_FETCH_COUNT, out ptle, out fetched);
                            Marshal.ThrowExceptionForHR(hr);
                        }
            
                        Marshal.ReleaseComObject(penumtle);
                        Marshal.ReleaseComObject(tlstg);
            
                        return results;
                    }
                }
            }
            

            【讨论】:

              【解决方案10】:

              我知道已经说过一些事情,所以我不会重写,但是,如果你真的想使用 JavaScript 方法(即:如果你想使用 javascript 历史对象而不是 webbrowser 控件历史对象)并且想知道如何,有办法做到这一点。您可以在 .NET WB 控件中使用 .InvokeScript,或者如果您希望 pre-.NET 和 .NET 兼容,您可以使用:

              您可以在 .NET 之前版本的 WB 控件和当前/.NET 版本的 WB 控件中使用 .execScript。您还可以选择要执行的脚本的语言,即:“JScript”或“VBScript”。这是一个班轮:

              WebBrowser1.Document.parentWindow.execScript "alert('hello world');", "JScript" 
              

              使用 JavaScript 历史对象的好处是,如果您通过将数字“2”发送到 .navigate 方法来终止 webbrowser 控件中的历史信息,则无法转到在 WB 控件中取消历史的页面,但它会在 JavaScript 的历史对象中工作,这是一个优势。

              再一次,这只是对本文中讨论的想法的向后兼容补充,包括其他一些未提及的花絮。

              让我知道我是否可以为您提供进一步的帮助,因为答案已被接受。

              【讨论】:

                猜你喜欢
                • 2018-06-06
                • 2016-05-31
                • 1970-01-01
                • 2021-12-31
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多