Capturar la página web como imagen en C#, asegurando que se vean los elementos de JavaScript renderizados
-
25-10-2019 - |
Pregunta
Estoy tratando de capturar la siguiente página usando el código C# .NET estándar. He buscado los diversos métodos de las personas, la mayoría de los cuales implican instanciar un objeto de navegador y usar un método de dibujo para mapa de bits. Sin embargo, ninguno de estos recoge el contenido de la tabla en esta página:
http://www.highcharts.com/demo/combo-dual-xes
Quizás el JavaScript no tiene tiempo para ejecutarse, pero agregar Thread.sleep (X) no ha ayudado.
Este componente comercial Lo captura correctamente, pero prefiero evitar requerir una dependencia adicional en mi proyecto y pagar $ 150 cuando las otras soluciones están tan cerca.
¿Alguien encuentra que su solución lo convierte en esto correctamente?
Solución
Posiblemente lo has intentado Iecapt. Creo que es el camino correcto a seguir. Creé una versión modificada y utilicé un timer
en vez de Thread.Sleep
Captura su sitio como se esperaba.
------EDITAR------
Aquí está la fuente fea. Solo agregue una referencia a Microsoft HTML Object Library
.
Y este es el uso:
HtmlCapture capture = new HtmlCapture(@"c:\temp\myimg.png");
capture.HtmlImageCapture += new HtmlCapture.HtmlCaptureEvent(capture_HtmlImageCapture);
capture.Create("http://www.highcharts.com/demo/combo-dual-axes");
void capture_HtmlImageCapture(object sender, Uri url)
{
this.Close();
}
Archivo1
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;
using System.IO;
namespace MyIECapt
{
public class HtmlCapture
{
private WebBrowser web;
private Timer tready;
private Rectangle screen;
private Size? imgsize = null;
//an event that triggers when the html document is captured
public delegate void HtmlCaptureEvent(object sender, Uri url);
public event HtmlCaptureEvent HtmlImageCapture;
string fileName = "";
//class constructor
public HtmlCapture(string fileName)
{
this.fileName = fileName;
//initialise the webbrowser and the timer
web = new WebBrowser();
tready = new Timer();
tready.Interval = 2000;
screen = Screen.PrimaryScreen.Bounds;
//set the webbrowser width and hight
web.Width = 1024; //screen.Width;
web.Height = 768; // screen.Height;
//suppress script errors and hide scroll bars
web.ScriptErrorsSuppressed = true;
web.ScrollBarsEnabled = false;
//attached events
web.Navigating +=
new WebBrowserNavigatingEventHandler(web_Navigating);
web.DocumentCompleted += new
WebBrowserDocumentCompletedEventHandler(web_DocumentCompleted);
tready.Tick += new EventHandler(tready_Tick);
}
public void Create(string url)
{
imgsize = null;
web.Navigate(url);
}
public void Create(string url, Size imgsz)
{
this.imgsize = imgsz;
web.Navigate(url);
}
void web_DocumentCompleted(object sender,
WebBrowserDocumentCompletedEventArgs e)
{
//start the timer
tready.Start();
}
void web_Navigating(object sender, WebBrowserNavigatingEventArgs e)
{
//stop the timer
tready.Stop();
}
void tready_Tick(object sender, EventArgs e)
{
try
{
//stop the timer
tready.Stop();
mshtml.IHTMLDocument2 docs2 = (mshtml.IHTMLDocument2)web.Document.DomDocument;
mshtml.IHTMLDocument3 docs3 = (mshtml.IHTMLDocument3)web.Document.DomDocument;
mshtml.IHTMLElement2 body2 = (mshtml.IHTMLElement2)docs2.body;
mshtml.IHTMLElement2 root2 = (mshtml.IHTMLElement2)docs3.documentElement;
// Determine dimensions for the image; we could add minWidth here
// to ensure that we get closer to the minimal width (the width
// computed might be a few pixels less than what we want).
int width = Math.Max(body2.scrollWidth, root2.scrollWidth);
int height = Math.Max(root2.scrollHeight, body2.scrollHeight);
//get the size of the document's body
Rectangle docRectangle = new Rectangle(0, 0, width, height);
web.Width = docRectangle.Width;
web.Height = docRectangle.Height;
//if the imgsize is null, the size of the image will
//be the same as the size of webbrowser object
//otherwise set the image size to imgsize
Rectangle imgRectangle;
if (imgsize == null) imgRectangle = docRectangle;
else imgRectangle = new Rectangle() { Location = new Point(0, 0), Size = imgsize.Value };
//create a bitmap object
Bitmap bitmap = new Bitmap(imgRectangle.Width, imgRectangle.Height);
//get the viewobject of the WebBrowser
IViewObject ivo = web.Document.DomDocument as IViewObject;
using (Graphics g = Graphics.FromImage(bitmap))
{
//get the handle to the device context and draw
IntPtr hdc = g.GetHdc();
ivo.Draw(1, -1, IntPtr.Zero, IntPtr.Zero,
IntPtr.Zero, hdc, ref imgRectangle,
ref docRectangle, IntPtr.Zero, 0);
g.ReleaseHdc(hdc);
}
//invoke the HtmlImageCapture event
bitmap.Save(fileName);
bitmap.Dispose();
}
catch
{
//System.Diagnostics.Process.GetCurrentProcess().Kill();
}
if(HtmlImageCapture!=null) HtmlImageCapture(this, web.Url);
}
}
}
y archivo2
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Runtime.InteropServices;
namespace MyIECapt
{
[ComVisible(true), ComImport()]
[GuidAttribute("0000010d-0000-0000-C000-000000000046")]
[InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)]
public interface IViewObject
{
[return: MarshalAs(UnmanagedType.I4)]
[PreserveSig]
int Draw(
[MarshalAs(UnmanagedType.U4)] UInt32 dwDrawAspect,
int lindex,
IntPtr pvAspect,
[In] IntPtr ptd,
IntPtr hdcTargetDev,
IntPtr hdcDraw,
[MarshalAs(UnmanagedType.Struct)] ref Rectangle lprcBounds,
[MarshalAs(UnmanagedType.Struct)] ref Rectangle lprcWBounds,
IntPtr pfnContinue,
[MarshalAs(UnmanagedType.U4)] UInt32 dwContinue);
[PreserveSig]
int GetColorSet([In, MarshalAs(UnmanagedType.U4)] int dwDrawAspect,
int lindex, IntPtr pvAspect, [In] IntPtr ptd,
IntPtr hicTargetDev, [Out] IntPtr ppColorSet);
[PreserveSig]
int Freeze([In, MarshalAs(UnmanagedType.U4)] int dwDrawAspect,
int lindex, IntPtr pvAspect, [Out] IntPtr pdwFreeze);
[PreserveSig]
int Unfreeze([In, MarshalAs(UnmanagedType.U4)] int dwFreeze);
}
}
Otros consejos
Thread.Sleep
Simplemente suspenderá el hilo en el que se está ejecutando su navegador web: ¿cómo espera que haga algo cuando esté suspendido? :)
En cambio, debe permitir que el hilo procese el trabajo. Puedes lograr esto con una combinación de Thread.Sleep(0)
y Application.DoEvents()
, con algo como lo siguiente:
DateTime finish = DateTime.Now.AddSeconds(3);
while (DateTime.Now < finish) {
Application.DoEvents();
Thread.Sleep(0);
}
@LB, ¡gracias por la ayuda!
Solo un FYI para cualquiera que quiera ejecutarlo en una biblioteca de clases, Webbrowser necesita un apartamento en un solo rostro, así que haga algo como esto:
var t = new Thread(InitAndDo); //InitAndDo would have your code creating the webbrowser object etc...
t.SetApartmentState(ApartmentState.STA);
t.Start();
Luego, el Gotcha, después de que se realice la llamada de navegación, agregue esta línea de código para que obtenga el evento de navegación completado:
web.Navigate(Url);
Application.Run();
Creé un paquete nuget para este propósitohttps://github.com/dcumin39/renderhighcharts/wiki