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

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

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

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

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

string html = CefSharp.Get(url);

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


person Robert Harvey    schedule 18.02.2016    source источник
comment
Для начала см. gist.github.com/amaitland/9d8897067bdff5b999a1.   -  person amaitland    schedule 18.02.2016
comment
@amaitland: Спасибо. Каков текущий способ дождаться выполнения Javascript и полной визуализации страницы, прежде чем получить результирующий HTML? NavStateChangedEventArgs больше не существует.   -  person Robert Harvey    schedule 18.02.2016
comment
NavStateChanged = LoadingStateChanged. Нет события, ожидающего javascript to finish executing, лучшее, что вы получаете из коробки, это завершение загрузки страницы. Я видел, как люди просто ждали какое-то время, что, я думаю, работает в некоторых случаях. Возможно, вам будет проще внедрить какой-нибудь javascript, проверить некоторые условия на странице.   -  person amaitland    schedule 19.02.2016


Ответы (2)


Если вы не можете получить безголовую версию Chromium, вы можете попробовать node.js и 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 и скомпилируйте его, а затем настройте под свои нужды. Вам нужно иметь возможность установить webBrowser.Address в свой код C#, и вам нужно знать, когда страница загружена, затем вам нужно вызвать webBrowser.EvaluateScriptAsync(".. JS code ..") с вашим кодом JavaScript (как строка), которая сделает что-то, как описано (возвращая bodyElement.innerHTML в виде строки).

person Jared Updike    schedule 18.02.2016

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

Так что да, 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>"

person samuel guedon    schedule 14.08.2018
comment
Есть ли причина, по которой вы используете SpinWait? Вы можете использовать TaskCompletionSource и просто ждать загрузки в браузере. см. github.com/cefsharp/CefSharp /blob/cefsharp/67/ - person amaitland; 15.08.2018
comment
Я использую то, что знаю ;) Я посмотрю на это (когда у меня будет время). В любом случае мне не очень нравится spinwait, но, поскольку инициализация занимает несколько десятков миллисекунд, это не слишком беспокоит мое приложение. - person samuel guedon; 15.08.2018
comment
Взгляните на строку 67. github.com/cefsharp/CefSharp.MinimalExample/blob/ - person Ali Bahrami; 20.10.2018