Использование 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>"