Uso de CefSharp.Offscreen para recuperar una página web que requiere Javascript para renderizar

Uso de CefSharp.Offscreen para recuperar una página web que requiere Javascript para renderizar

Sé que estoy haciendo un poco de arqueología reviviendo una publicación de 2 años, pero una respuesta detallada puede ser útil para otra persona.

Así que sí, Cefsharp.Offscreen es apto para la tarea.

Aquí debajo hay una clase que manejará toda la actividad del navegador.

using System;
using System.IO;
using System.Threading;
using CefSharp;
using CefSharp.OffScreen;

namespace [whatever]
{
    public class Browser
    {

        /// <summary>
        /// The browser page
        /// </summary>
        public ChromiumWebBrowser Page { get; private set; }
        /// <summary>
        /// The request context
        /// </summary>
        public RequestContext RequestContext { get; private set; }

        // chromium does not manage timeouts, so we'll implement one
        private ManualResetEvent manualResetEvent = new ManualResetEvent(false);

        public Browser()
        {
            var settings = new CefSettings()
            {
                //By default CefSharp will use an in-memory cache, you need to     specify a Cache Folder to persist data
                CachePath =     Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "CefSharp\\Cache"),
            };

            //Autoshutdown when closing
            CefSharpSettings.ShutdownOnExit = true;

            //Perform dependency check to make sure all relevant resources are in our     output directory.
            Cef.Initialize(settings, performDependencyCheck: true, browserProcessHandler: null);

            RequestContext = new RequestContext();
            Page = new ChromiumWebBrowser("", null, RequestContext);
            PageInitialize();
        }

        /// <summary>
        /// Open the given url
        /// </summary>
        /// <param name="url">the url</param>
        /// <returns></returns>
        public void OpenUrl(string url)
        {
            try
            {
                Page.LoadingStateChanged += PageLoadingStateChanged;
                if (Page.IsBrowserInitialized)
                {
                    Page.Load(url);

                    //create a 60 sec timeout 
                    bool isSignalled = manualResetEvent.WaitOne(TimeSpan.FromSeconds(60));
                    manualResetEvent.Reset();

                    //As the request may actually get an answer, we'll force stop when the timeout is passed
                    if (!isSignalled)
                    {
                        Page.Stop();
                    }
                }
            }
            catch (ObjectDisposedException)
            {
                //happens on the manualResetEvent.Reset(); when a cancelation token has disposed the context
            }
            Page.LoadingStateChanged -= PageLoadingStateChanged;
        }

        /// <summary>
        /// Manage the IsLoading parameter
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void PageLoadingStateChanged(object sender, LoadingStateChangedEventArgs e)
        {
            // Check to see if loading is complete - this event is called twice, one when loading starts
            // second time when it's finished
            if (!e.IsLoading)
            {
                manualResetEvent.Set();
            }
        }

        /// <summary>
        /// Wait until page initialization
        /// </summary>
        private void PageInitialize()
        {
            SpinWait.SpinUntil(() => Page.IsBrowserInitialized);
        }
    }
}

Ahora en mi aplicación solo necesito hacer lo siguiente:

public MainWindow()
{
    InitializeComponent();
    _browser = new Browser();
}

private async void GetGoogleSource()
{
    _browser.OpenUrl("http://icanhazip.com/");
    string source = await _browser.Page.GetSourceAsync();
}

Y aquí está la cadena que obtengo

"<html><head></head><body><pre style=\"word-wrap: break-word; white-space: pre-wrap;\">NotGonnaGiveYouMyIP:)\n</pre></body></html>"


Si no puede obtener una versión sin interfaz de Chromium que lo ayude, puede probar con node.js y jsdom. Fácil de instalar y jugar una vez que tenga el nodo en funcionamiento. Puede ver ejemplos simples en Github README donde abren una URL, ejecutan todo javascript, incluido cualquier código de javascript personalizado (ejemplo:bits de jQuery para contar algún tipo de elementos), y luego tiene el HTML en la memoria para hacer lo que quiere . Simplemente puede hacer $('body').html() y obtener una cadena, como en su pseudocódigo. (Esto incluso funciona para cosas como generar gráficos SVG, ya que son solo más nodos de árbol XML).

Si necesita esto como parte de una aplicación C# más grande que necesita distribuir, su idea de usar CefSharp.Offscreen parece razonable. Un enfoque podría ser hacer que las cosas funcionen con CefSharp.WinForms o CefSharp.WPF primero, donde literalmente puede ver las cosas, luego intente con CefSharp.Offscreen más tarde cuando todo funcione. Incluso puede hacer que JavaScript se ejecute en el navegador en pantalla para desplegar body.innerHTML y devolverlo como una cadena al lado C# de las cosas antes de quedarse sin cabeza. Si eso funciona, el resto debería ser fácil.

Tal vez comience con CefSharp.MinimalExample y obtenga esa compilación, luego ajústela según sus necesidades. Debe poder configurar webBrowser.Address en su código C#, y necesita saber cuándo se cargó la página, luego debe llamar a webBrowser.EvaluateScriptAsync(".. Código JS ..") con su código JavaScript (como una cadena) que hará algo como se describe (devolviendo bodyElement.innerHTML como una cadena).