c# javascript html webbrowser-control dynamic-html

c# - ¿Cómo generar código HTML de forma dinámica utilizando WebBrowser de.NET o mshtml.HTMLDocument?



javascript webbrowser-control (2)

El código de su navegador web parece razonable: espere algo, que capte el contenido actual. Lamentablemente, no existe una notificación oficial "He terminado de ejecutar JavaScript, siéntete libre de robar contenido" del navegador ni de JavaScript.

Algún tipo de espera activa (no Sleep pero Timer ) puede ser necesaria y específica de la página. Incluso si usa un navegador sin cabeza (es decir, PhantomJS), tendrá el mismo problema.

La mayoría de las respuestas que he leído sobre este tema apuntan a la clase System.Windows.Forms.WebBrowser o a la interfaz COM mshtml.HTMLDocument del ensamblado Biblioteca de objetos HTML de Microsoft.

La clase WebBrowser no me llevó a ningún lado. El siguiente código no puede recuperar el código HTML tal como lo representa mi buscador web:

[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); }

Lo anterior es solo un ejemplo. No estoy realmente interesado en encontrar una solución para averiguar el nombre de la ciudad donde me encuentro. Simplemente necesito entender cómo recuperar ese tipo de datos generados dinámicamente mediante programación.

(Llame al nuevo System.Net.WebClient.DownloadString (" https://www.google.com/#q=where+am+i "), guarde el texto resultante en alguna parte, busque el nombre de la ciudad donde se encuentra actualmente ubicado, y avíseme si pudo encontrarlo).

Sin embargo, cuando accedo a " https://www.google.com/#q=where+am+i " desde mi navegador web (es decir, o Firefox), veo el nombre de mi ciudad escrito en la página web. En Firefox, si hago clic derecho en el nombre de la ciudad y selecciono "Inspeccionar Elemento (Q)", veo claramente el nombre de la ciudad escrito en el código HTML que se ve bastante diferente del HTML sin procesar que devuelve WebClient. .

Después de que me cansé de jugar a System.Net.WebBrowser, decidí darle a mshtml.HTMLDocument una oportunidad, solo para terminar con el mismo HTML crudo e inútil:

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

Supongo que debe haber una forma elegante de obtener este tipo de información. En este momento, todo lo que puedo pensar es agregar un control WebBrowser a un formulario, hacer que navegue a la URL en cuestión, enviar las claves "CLRL, A" y copiar lo que sucede para mostrarse en la página al portapapeles e intentar analizarlo Esa es una solución horrible, sin embargo.


Me gustaría contribuir con algún código a la respuesta de Alexei . Algunos puntos:

  • Estrictamente hablando, no siempre es posible determinar cuándo la página ha terminado de renderizar con un 100% de probabilidad. Algunas páginas son bastante complejas y usan actualizaciones continuas de AJAX. Pero podemos acercarnos bastante, al sondear la instantánea HTML actual de la página para ver los cambios y verificar la propiedad WebBrowser.IsBusy . Eso es lo que LoadDynamicPage hace a continuación.

  • Alguna lógica de tiempo de espera debe estar presente además de la anterior, en caso de que la representación de la página sea interminable (observe CancellationTokenSource ).

  • Async/await es una gran herramienta para codificar esto, ya que brinda el flujo de código lineal a nuestra lógica asincrónica de sondeo, que lo simplifica enormemente.

  • Es importante habilitar la representación de HTML5 mediante el Control de funciones del navegador , ya que WebBrowser ejecuta en modo de emulación IE7 de forma predeterminada. Eso es lo que hace SetFeatureBrowserEmulation continuación.

  • Esta es una aplicación de WinForms, pero el concepto se puede convertir fácilmente en una aplicación de consola .

  • Esta lógica funciona bien en la URL que ha mencionado específicamente: 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://.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); } } }