2014-01-05 29 views
11

我已閱讀有關此主題的大多數答案都指向System.Windows.Forms.WebBrowser類或來自Microsoft HTML Object Library程序集的COM接口mshtml.HTMLDocument。如何使用.NET的WebBrowser或mshtml.HTMLDocument動態生成HTML代碼?

WebBrowser類沒有帶領我到任何地方。以下代碼未能檢索到我的網絡瀏覽器呈現的HTML代碼:

[STAThread] 
public static void Main() 
{ 
    WebBrowser wb = new WebBrowser(); 
    wb.Navigate("https://www.google.com/#q=where+am+i"); 

    wb.DocumentCompleted += delegate(object sender, WebBrowserDocumentCompletedEventArgs e) 
    { 
     mshtml.IHTMLDocument2 doc = (mshtml.IHTMLDocument2)wb.Document.DomDocument; 
     foreach (IHTMLElement element in doc.all) 
     { 
        System.Diagnostics.Debug.WriteLine(element.outerHTML); 
     }  
    }; 
    Form f = new Form(); 
    f.Controls.Add(wb); 
    Application.Run(f); 
} 

以上只是一個示例。我並不是真的有興趣找到解決我所在城鎮名稱的解決方法。我只需要了解如何以編程方式檢索那種動態生成的數據。

(調用新的System.Net.WebClient.DownloadString(「https://www.google.com/#q=where+am+i」),將結果文本保存到某處,搜索當前所在城鎮的名稱,並讓我知道您是否能夠找到它)

但是,當我從我的Web瀏覽器(即Firefox或Firefox)訪問「https://www.google.com/#q=where+am+i」時,我看到我的城鎮的名稱寫在網頁上。在Firefox中,如果我右鍵單擊城鎮的名稱並選擇「檢查元素(Q)」,我清楚地看到用HTML代碼編寫的城鎮名稱,這看起來與WebClient返回的原始HTML完全不同。

後,我厭倦了打System.Net.WebBrowser,我決定給mshtml.HTMLDocument了一槍,正好與同無用原始的HTML結束:

public static void Main() 
{ 
    mshtml.IHTMLDocument2 doc = (mshtml.IHTMLDocument2)new mshtml.HTMLDocument(); 
    doc.write(new System.Net.WebClient().DownloadString("https://www.google.com/#q=where+am+i")); 

    foreach (IHTMLElement e in doc.all) 
    { 
      System.Diagnostics.Debug.WriteLine(e.outerHTML); 
    } 
} 

我想必須有一個優雅的方式來獲取這種信息。現在我所能想到的只是將WebBrowser控件添加到表單中,讓它導航到正在討論的URL,發送密鑰「CLRL,A」,並將發生在頁面上的任何事情複製到剪貼板並嘗試解析它。雖然這是可怕的解決方案。

回答

16

我想提供一些代碼給Alexei's answer。幾點:

  • 嚴格地說,它可能並不總是能夠以100%的概率確定頁面何時完成呈現。有些頁面 非常複雜,並且使用連續的AJAX更新。但我們 可以通過輪詢頁面的當前HTML快照來獲得相當接近的變化 和檢查WebBrowser.IsBusy屬性。這就是 LoadDynamicPage下面所做的。

  • 一些超時邏輯必須出現在上面的頂部,以防頁面呈現永無止境(注意CancellationTokenSource)。

  • Async/await是一個很好的編碼工具,它爲我們的異步輪詢邏輯提供了線性的 代碼流,極大地簡化了它。

  • 使用Browser Feature Control啓用HTML5呈現非常重要,因爲WebBrowser默認情況下以IE7仿真模式運行。 這就是SetFeatureBrowserEmulation下面所做的。

  • 這是一個WinForms應用程序,但概念可以很容易地converted into a console app

  • 這個邏輯適用於你特別提到的URL:https://www.google.com/#q=where+am+i

using Microsoft.Win32; 
using System; 
using System.ComponentModel; 
using System.Diagnostics; 
using System.Threading; 
using System.Threading.Tasks; 
using System.Windows.Forms; 

namespace WbFetchPage 
{ 
    public partial class MainForm : Form 
    { 
     public MainForm() 
     { 
      SetFeatureBrowserEmulation(); 
      InitializeComponent(); 
      this.Load += MainForm_Load; 
     } 

     // start the task 
     async void MainForm_Load(object sender, EventArgs e) 
     { 
      try 
      { 
       var cts = new CancellationTokenSource(10000); // cancel in 10s 
       var html = await LoadDynamicPage("https://www.google.com/#q=where+am+i", cts.Token); 
       MessageBox.Show(html.Substring(0, 1024) + "..."); // it's too long! 
      } 
      catch (Exception ex) 
      { 
       MessageBox.Show(ex.Message); 
      } 
     } 

     // navigate and download 
     async Task<string> LoadDynamicPage(string url, CancellationToken token) 
     { 
      // navigate and await DocumentCompleted 
      var tcs = new TaskCompletionSource<bool>(); 
      WebBrowserDocumentCompletedEventHandler handler = (s, arg) => 
       tcs.TrySetResult(true); 

      using (token.Register(() => tcs.TrySetCanceled(), useSynchronizationContext: true)) 
      { 
       this.webBrowser.DocumentCompleted += handler; 
       try 
       {   
        this.webBrowser.Navigate(url); 
        await tcs.Task; // wait for DocumentCompleted 
       } 
       finally 
       { 
        this.webBrowser.DocumentCompleted -= handler; 
       } 
      } 

      // get the root element 
      var documentElement = this.webBrowser.Document.GetElementsByTagName("html")[0]; 

      // poll the current HTML for changes asynchronosly 
      var html = documentElement.OuterHtml; 
      while (true) 
      { 
       // wait asynchronously, this will throw if cancellation requested 
       await Task.Delay(500, token); 

       // continue polling if the WebBrowser is still busy 
       if (this.webBrowser.IsBusy) 
        continue; 

       var htmlNow = documentElement.OuterHtml; 
       if (html == htmlNow) 
        break; // no changes detected, end the poll loop 

       html = htmlNow; 
      } 

      // consider the page fully rendered 
      token.ThrowIfCancellationRequested(); 
      return html; 
     } 

     // enable HTML5 (assuming we're running IE10+) 
     // more info: https://stackoverflow.com/a/18333982/1768303 
     static void SetFeatureBrowserEmulation() 
     { 
      if (LicenseManager.UsageMode != LicenseUsageMode.Runtime) 
       return; 
      var appName = System.IO.Path.GetFileName(System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName); 
      Registry.SetValue(@"HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_BROWSER_EMULATION", 
       appName, 10000, RegistryValueKind.DWord); 
     } 
    } 
} 
+1

我不得不添加到CancellationTokenSource用於我的目的,工作的時間間隔,但除此之外,該解決方案很好工作!我嘗試了很多其他的「解決方案」,但這是唯一真正解決我的問題的方案。 – majestzim

6

您的網頁瀏覽器代碼看起來很合理 - 等待一些東西,抓住當前的內容。不幸的是,沒有官方的「我已經完成了JavaScript的執行,可以隨意從瀏覽器或JavaScript中竊取內容」通知。

某種有效的等待(不是Sleep而是Timer)可能是必需的並且是頁面特定的。即使你使用無頭瀏覽器(即PhantomJS),你也會遇到同樣的問題。