Использование CefSharp.Offscreen для извлечения веб-страницы, для которой требуется рендеринг Javascript

У меня есть, надеюсь, простая задача, но для ее решения потребуется кто-то, кто разбирается в CefSharp.

У меня есть URL, из которого я хочу получить HTML. Проблема в том, что этот конкретный URL не распространяет страницу в GET. Вместо этого он помещает кучу Javascript в браузер, который затем выполняет и создает фактическую визуализированную страницу. Это означает, что обычные подходы с участием HttpWebRequest а также HttpWebResponse не собираюсь работать.

Я рассмотрел несколько различных "безголовых" вариантов, и тот, который, по моему мнению, лучше всего отвечает моим потребностям по ряду причин, - это CefSharp.Offscreen. Но я в растерянности относительно того, как эта штука работает. Я вижу, что есть несколько событий, на которые можно подписаться, и некоторые параметры конфигурации, но мне не нужно ничего, как встроенный браузер.

Все, что мне действительно нужно, это способ сделать что-то вроде этого (псевдокод):

string html = CefSharp.Get(url);

У меня нет проблем с подпиской на события, если это то, что нужно, чтобы подождать, пока Javascript запустится и создаст визуализированную страницу.

2 ответа

Решение

Если вы не можете получить безрезультатную версию Chromium, вы можете попробовать node.js и jsdom ( https://github.com/tmpvar/jsdom). Легко установить и играть, когда у вас есть узел и работает. Вы можете увидеть простые примеры на Github README, где они опускают URL, запускают весь javascript, включая любой пользовательский код javascript (пример: биты jQuery для подсчета элементов определенного типа), а затем у вас есть HTML в памяти, чтобы делать то, что вы хотите, Вы можете просто сделать $('body'). Html() и получить строку, как в вашем псевдокоде. (Это даже работает для таких вещей, как генерация SVG-графики, поскольку это просто больше узлов дерева XML.)

Если вам нужно это как часть более крупного приложения на C#, которое нужно распространять, ваша идея использовать CefSharp.Offscreen звучит разумно. Один из подходов может заключаться в том, чтобы сначала заставить работать вещи с CefSharp.WinForms или CefSharp.WPF, где вы можете буквально видеть вещи, а затем попробовать CefSharp.Offscreen позже, когда все это работает. Вы даже можете запустить некоторый JavaScript в экранном браузере, чтобы вытащить body.innerHTML и вернуть его в виде строки на сторону C#, прежде чем идти без головы. Если это работает, остальное должно быть легко.

Возможно, начните с CefSharp.MinimalExample ( https://github.com/cefsharp/CefSharp.MinimalExample) и получите эту компиляцию, а затем настройте ее для своих нужд. Вы должны иметь возможность установить webBrowser.Address в своем коде C#, и вам нужно знать, когда страница загружена, тогда вам нужно вызвать webBrowser.EvaluateScriptAsync(".. JS code ..") с вашим кодом JavaScript (как строка), которая будет делать что-то, как описано (возвращая bodyElement.innerHTML в виде строки).

Я знаю, что занимаюсь археологией, возрождая 2-летнюю статью, но подробный ответ может пригодиться кому-то еще.

Так что да, Cefsharp.Offscreen подходит для этой задачи.

Здесь под классом, который будет обрабатывать все действия браузера.

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

Теперь в моем приложении мне просто нужно сделать следующее:

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

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

И вот строка, которую я получаю

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

Другие вопросы по тегам