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