使用 CefSharp.Offscreen 检索需要 Javascript 呈现的网页

本文关键字:网页 Javascript CefSharp Offscreen 检索 使用 | 更新日期: 2023-09-27 18:35:11

我希望这是一项简单的任务,但这需要精通CefSharp的人来解决它。

我有一个要从中检索 HTML 的网址。 问题是这个特定的 url 实际上并没有在 GET 上分发页面。 相反,它将一堆Javascript推送到浏览器,然后浏览器执行并生成实际呈现的页面。 这意味着涉及HttpWebRequestHttpWebResponse的常规方法行不通。

我已经研究了许多不同的"无头"选项,出于多种原因,我认为最能满足我需求的选项是CefSharp.Offscreen。 但我对这个东西是如何工作的感到茫然。 我看到有几个事件可以订阅,还有一些配置选项,但我不需要像嵌入式浏览器这样的东西。

我真正需要的只是一种方法来做这样的事情(伪代码):

string html = CefSharp.Get(url);

订阅事件没有问题,如果这是等待 Javascript 执行并生成呈现页面所需要的。

使用 CefSharp.Offscreen 检索需要 Javascript 呈现的网页

我知道

我正在做一些考古学来恢复一个 2yo 帖子,但详细的答案可能对其他人有用。

所以是的,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>"

如果你不能得到一个无头版本的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开始并完成编译,然后根据您的需要进行调整。您需要能够在 C# 代码中设置 webBrowser.Address,并且需要知道页面何时加载,然后您需要调用 webBrowser.EvaluateScriptAsync("..JS代码..")使用您的JavaScript代码(作为字符串),它将执行所描述的操作(将bodyElement.innerHTML作为字符串返回)。