如何在高性能环境下生成网页图片?

How to generate images of web pages in a high performance environment?

我试图在服务器端环境中在一秒钟内生成网页图像。这些请求可以同时来自网络并发。为此,我正在使用运行良好的 Puppeteer-Sharp 库。在后端,它使用 Chromium 加载页面,然后对其进行截图。

问题是需要一段时间才能开始。例如,请注意 readme.md 示例代码中的时间(来自我的电脑):

var options = new new LaunchOptions {Headless = true, ExecutablePath = @"c:\foo\chrome.exe"};
var browser = await Puppeteer.LaunchAsync(options).Result;    //  ~500ms
var page = browser.NewPageAsync().Result;                     //  ~215ms
var webPage = page.GoToAsync("http://www.google.com").Result; //  ~500ms
var screenshot = page.ScreenshotAsync(outputFile);            
screenshot.wait();                                            //  ~300ms   

如您所见,它很容易超过一秒钟。我不知道 Chromium 内部是如何工作的,所以我有几个关于我正在考虑的解决方案的问题。

  1. PuppeteerSharp.Browser 对象是线程安全的 and/or 可重入的吗?我可以使用来自不同线程的相同浏览器对象吗?我不这么认为,因为它与内存中 Chromium 的特定实例相关联。
  2. 如果我从每个请求中删除 .LaunchAsync.NetPageAsync,这将显着加快操作速度。 PuppeteerSharp.Browser 个对象池是否有效?例如,我可以预先分配其中的 5 个并在它们上执行 .NetPageAsync。然后传入的请求将使用池中的对象。这是可行的方法吗?

虽然还有 many improvements going on,但 Puppeteer-Sharp 是线程安全的。要提高加载性能,您可以采用几种方法。

启动一个浏览器然后连接到它

您可以启动一个(真正的)浏览器,然后使用 ConnectAsync 方法连接它。

await new BrowserFetcher().DownloadAsync(BrowserFetcher.DefaultRevision);
var browser = await Puppeteer.LaunchAsync(new LaunchOptions
{
    Headless = false,
});

var theBrowser1 = await Puppeteer.ConnectAsync(new ConnectOptions { BrowserWSEndpoint = browser.WebSocketEndpoint });
var theBrowser2 = await Puppeteer.ConnectAsync(new ConnectOptions { BrowserWSEndpoint = browser.WebSocketEndpoint });
var page1 = await theBrowser1.NewPageAsync();
var page2 = await theBrowser2.NewPageAsync();

await Task.WhenAll(
    page1.GoToAsync("https://www.whosebug.com"),
    page2.GoToAsync("https://serverfault.com/")
);

我知道代码不是 运行 并行的,但您会明白如何重用同一个浏览器。

在同一浏览器上创建新页面

如果您使用的是 TPL,使用同一浏览器从不同线程创建新页面应该不会有任何问题。

await new BrowserFetcher().DownloadAsync(BrowserFetcher.DefaultRevision);
var browser = await Puppeteer.LaunchAsync(new LaunchOptions
{
    Headless = false,
});

var urls = new string[]
{
    "https://www.whosebug.com",
    "https://www.whosebug.com",
    "https://www.whosebug.com",
    "https://www.whosebug.com",
    "https://www.whosebug.com",
    "https://www.whosebug.com",
    "https://www.whosebug.com",
    "https://www.whosebug.com",
    "https://www.whosebug.com",
    "https://www.whosebug.com",
    "https://www.whosebug.com"
};

await Task.WhenAll(
    urls.Select(url => Task.Factory.StartNew(async () =>
    {
        var page = await browser.NewPageAsync();
        return page.GoToAsync(url);
    })));

同样,这个例子只是为了让您了解如何实现这一点。

页面队列

有一个用户创建了一个 X 页面队列(x 从 0 到 X => NewPage),然后他会从该队列中抓取页面。你可以看到example here