利用Puppeteer + Browsershot 实现网页截图API功能

利用Puppeteer + Browsershot 实现网页截图API功能

如何实现网页快照API功能?市面上有着不少的教程教你如何搭建,但是大部分都是纯node要么就是残缺的php代码
为什么说是残缺的php代码呢?我在这放个截图给你看看就知道了

给了,但是没完全给,我在网上找了很多篇教程就是没有找到任何一篇有用的文章,索性我就开始自己研究。
也是皇天不负有心人,终于是给我研究出来了!!!

一开始我所采用的时纯php也就是 wkhtmltoimage 来实现快照功能,实现起来也是非常的简单,但是他截图出来的效果真的时一言难尽啊!

图片效果↓

我是想着也许是css或者js没加载出来导致的?我又做了一些列的限制和优化,但还是这样的。
最后也是去bing了一下才知道是浏览器的问题导致的。

wkhtmltoimage:

  • 底层技术: 使用 WebKit 引擎(与旧版 Safari 相同)。WebKit 引擎的版本比较老,所以在某些现代网页上可能不支持最新的 HTML5 和 CSS3 特性

  • 优点: 安装简单,配置较少,适合快速生成截图。

  • 缺点: 由于 WebKit 引擎较旧,它可能无法很好地支持最新的网页技术。

这我用个 啥啊?不支持h5和c3?现在哪个站点不是h5和c3啊???我又开始找合适的库来实现我的要求
找了一下午也是找到了 Browsershot 这个php库吧?
但是 Browsershot 也不是什么好搞的东西,所以我就写下了这篇文章!那么开始吧!

1.在本地部署

为什么不直接在云端部署?
这些东西就说来话长了,光是拉取库这一步就直接给我干成傻子了,各种问题层出不穷,所以我选择先在本地搞好!
再逐步将文件移到云端进行部署/测试/运行

配置如下

PHP

7.4

Node.js

18.16.0

Npm

9.5.1

Composer

2.5.8

首先现在 Composer 的命令窗口运行指令,也就是 composer require spatie/browsershot

如何打开 Composer

图列

随后执行命令之后在cmd中和资源管理器中看到这些就代表安装成功了

然后vs中在根目录打开终端输入 npm install puppeteer

这就代表安装成功了

至此本地部分就算完成了!
当然如果嫌弃本地麻烦可以直接在云端拉取这些库了!进行环境部署。

代码部分

请在index.php文件内填写这些代码

<?php
require 'vendor/autoload.php';

use Spatie\Browsershot\Browsershot;

// 获取 URL 参数,默认为 limo-studio.com
$url = isset($_GET['url']) ? filter_var($_GET['url'], FILTER_SANITIZE_URL) : 'limoe-studio.com';

// 处理 URL 前缀
if (!preg_match('/^https?:\/\//', $url)) {
  $url = 'https://' . $url;  // 默认使用 HTTPS
}

// 获取图片宽度、高度、输出类型和全页截图参数
$desiredWidth = isset($_GET['w']) && is_numeric($_GET['w']) ? (int)$_GET['w'] : 1920;
$desiredHeight = isset($_GET['h']) && is_numeric($_GET['h']) ? (int)$_GET['h'] : 1080;
$type = isset($_GET['type']) ? $_GET['type'] : 'img';  // 默认返回图片
$all = isset($_GET['all']) && $_GET['all'] === 'long';  // 是否全页截图


// 生成唯一的文件名和目录
$host = parse_url($url, PHP_URL_HOST);
$timestamp = date('Y-m-d_H-i-s');
$randomString = bin2hex(random_bytes(8));
$filename = "{$timestamp}_{$randomString}.png";
$dir = __DIR__ . "/screenshots/$host";
$output = "$dir/$filename";

// 创建目录,如果不存在
if (!file_exists($dir)) {
  mkdir($dir, 0755, true);
}

// 删除最早的截图以限制数量为 5 张
$files = glob("$dir/*.png");
if (count($files) >= 5) {
  usort($files, function ($a, $b) {
    return filemtime($a) - filemtime($b);
  });
  unlink($files[0]);
}

try {
  $browsershot = Browsershot::url($url)
    ->timeout(60000)  // 设置超时(秒)
    ->windowSize($desiredWidth, $desiredHeight)  // 设置浏览器视口的尺寸
    ->setChromePath('/home/www/.cache/puppeteer/chrome/linux-128.0.6613.119/chrome-linux64/chrome');//这是我的谷歌浏览器绝对定位,请确保权限为755or775,可以使用浏览器,否则会出现一系列问题

  if ($all) {
    $browsershot->fullPage();  // 截取完整页面
  }

  $browsershot->save($output);

  // 检查截图是否成功保存
  if (file_exists($output)) {
    $imageUrl = "https://{$_SERVER['HTTP_HOST']}/api/web-screenshot/screenshots/$host/$filename";

    if ($type === 'json') {
      echo json_encode([
        'status' => 'success',
        'message' => 'Screenshot saved successfully.',
        'image_url' => $imageUrl
      ]);
    } else {
      header('Content-Type: image/png');
      readfile($output);
    }
  } else {
    if ($type === 'json') {
      echo json_encode([
        'status' => 'error',
        'message' => 'Error saving screenshot.'
      ]);
    } else {
      echo "Error saving screenshot.";
    }
  }
} catch (Exception $e) {
  if ($type === 'json') {
    echo json_encode([
      'status' => 'error',
      'message' => 'An error occurred: ' . $e->getMessage()
    ]);
  } else {
    echo "An error occurred: " . $e->getMessage();
  }
}

重要代码:

->setChromePath('/home/www/.cache/puppeteer/chrome/linux-128.0.6613.119/chrome-linux64/chrome'); 这个部分是很重要的,如果你没设置正确的浏览器路径会导致无法截图,相当于废了一半了!

->timeout(60000)这个部分是超时设置建议默认60超时,如果设置太短会导致无法截图

部署云端

当如果你在本地安装了 谷歌 浏览器并且访问自己的api和带上了参数,那么是可以正常使用的,但是!!!
上传至云端之后需要在云端也安装一个对应系统的 谷歌 浏览器

对了这些是库的版本和服务器端版本【只列出重要的

运行库

运行版本

Browsershot

3.26

puppeteer

23.3.0

Chrome

128.0.6613.119

云端数据

云端版本

系统

Ubuntu 22

服务器

2H2G

面板

宝塔

将这些文件全部丢在云端你的api目录下

只需要这些文件即可

结束语

部署教程基本就到这了,但是我在这总结一下我在云端和本地遇到的问题!

1.文件引用

Fatal error: Uncaught Error: Class 'Spatie\Browsershot\Browsershot' not found in /www/wwwroot/域名/api/web/index.php:5 Stack trace: #0 {main} thrown in /www/wwwroot/域名/api/web/index.php on line 5

解决办法:尝试清除 Composer 缓存,并重新安装依赖项

composer clear-cache

composer install

2.没有安装Puppeteer

Fatal error: Uncaught Symfony\Component\Process\Exception\ProcessFailedException: The command "node ^"E:^\phpstudy_pro^\WWW^\study^\vendor^\spatie^\browsershot^\src/../bin/browser.js^" ^"^{^\^"url^\^":^\^"https:^\/^\/www.baidu.com^\^",^\^"action^\^":^\^"screenshot^\^",^\^"options^\^":^{^\^"type^\^":^\^"png^\^",^\^"path^\^":^\^"example.png^\^",^\^"args^\^":^[^],^\^"viewport^\^":^{^\^"width^\^":800,^\^"height^\^":600^}^}^}^"" failed. Exit Code: 1(General error) Working directory: E:\phpstudy_pro\WWW\study Output: ================ Error Output: ================ node:internal/modules/cjs/loader:1078 throw err; ^ Error: Cannot find module 'puppeteer' Require stack: - E:\phpstudy_pro\WWW\study\vendor\spatie\browsershot\bin\browser.js at Module._resolveFilename (node:internal/modules/cjs/loader:1075:15) at Module._load (node:internal/modules/cjs/loader:920:27) at Module.require (node:internal/modules/cjs/loader:1141:19) at require (node:internal/modules/cjs/helpers:110:18) at Object.<anon in E:\phpstudy_pro\WWW\study\vendor\spatie\browsershot\src\Browsershot.php on line 596

解决办法:尝试安装Puppeteer为Browsershot通过依赖

npm install puppeteer

3.没有进行超时限制

Fatal error: Uncaught Symfony\Component\Process\Exception\ProcessFailedException: The command "node ^"E:^\phpstudy_pro^\WWW^\study^\vendor^\spatie^\browsershot^\src/../bin/browser.js^" ^"^{^\^"url^\^":^\^"https:^\/^\/www.baidu.com^\^",^\^"action^\^":^\^"screenshot^\^",^\^"options^\^":^{^\^"type^\^":^\^"png^\^",^\^"path^\^":^\^"example.png^\^",^\^"args^\^":^[^],^\^"viewport^\^":^{^\^"width^\^":800,^\^"height^\^":600^}^}^}^"" failed. Exit Code: 1(General error) Working directory: E:\phpstudy_pro\WWW\study Output: ================ Error Output: ================ Error: net::ERR_TIMED_OUT at https://www.baidu.com at navigate (E:\phpstudy_pro\WWW\study\node_modules\puppeteer-core\lib\cjs\puppeteer\cdp\Frame.js:183:27) at async Deferred.race (E:\phpstudy_pro\WWW\study\node_modules\puppeteer-core\lib\cjs\puppeteer\util\Deferred.js:36:20) at async CdpFrame.goto (E:\phpstudy_pro\WWW\study\node_modules\puppeteer-core\lib\cjs\puppeteer\cdp\Frame.js:149:25) at async CdpPage.goto (E:\phpstudy_pro\WWW\study\node_mod in E:\phpstudy_pro\WWW\study\vendor\spatie\browsershot\src\Browsershot.php on line 596

解决办法:在php中添加超时设置

Browsershot::url('https://www.baidu.com')
    ->timeout(60000) // 设置超时时间为 60 秒

4.网址填写错误

PS E:\phpstudy_pro\WWW\study> php "e:\phpstudy_pro\WWW\study\index.php"

Fatal error: Uncaught Symfony\Component\Process\Exception\ProcessFailedException: The command "node ^"E:^\phpstudy_pro^\WWW^\study^\vendor^\spatie^\browsershot^\src/../bin/browser.js^" ^"^{^\^"url^\^":^\^"www.baidu.com^\^",^\^"action^\^":^\^"screenshot^\^",^\^"options^\^":^{^\^"type^\^":^\^"png^\^",^\^"path^\^":^\^"example.png^\^",^\^"args^\^":^[^],^\^"viewport^\^":^{^\^"width^\^":800,^\^"height^\^":600^},^\^"delay^\^":3000,^\^"timeout^\^":60000^}^}^"" failed.

Exit Code: 1(General error)

Working directory: E:\phpstudy_pro\WWW\study

Output:
================


Error Output:
================
ProtocolError: Protocol error (Page.navigate): Cannot navigate to invalid URL
    at <instance_members_initializer> (E:\phpstudy_pro\WWW\study\node_modules\puppeteer-core\lib\cjs\puppeteer\common\CallbackRegistry.js:93:14)
    at new Callback (E:\phpstudy_pro\WWW\study\node_modules\puppeteer-core\lib\cjs\puppeteer\common\CallbackRegistry.js:97:16)
    at CallbackRegistry.create (E:\phpstudy_pro\WWW\study\node_modules\puppeteer-core\lib\cjs\puppeteer\common\CallbackRegistry.js:22:26)
    at Connection._rawSend (E:\phpstudy_pro\WWW\study\node_modules\puppeteer-core\lib\cjs\puppeteer\cdp\Connection.js:89:26)
    at CdpCDPSession.send (E:\phpstudy_pro\WWW\study\node_modules\puppeteer-core\lib\cjs\puppeteer\cdp\CDPSession.js:66:33)
    at navigate (E:\phpstudy_pro\WWW\study\node_modules\puppeteer-core\lib\cjs\puppeteer\cdp\Frame.js:172:51)
    at CdpFrame.goto (E:\phpstudy_pro\WWW\study\node_modules\puppeteer-core\lib\cjs\puppeteer\cdp\Frame.js:150:17)
    at CdpFrame.<anonymous> (E:\phpstudy_pro\WWW\study\node_modules\puppeteer-core\lib\cjs\puppeteer\util\decorators.js:98:27)
    at CdpPage.goto (E:\phpstudy_pro\WWW\study\node_modules\puppeteer-core\lib\cjs\puppeteer\api\Page.js:567:43)
    at callChrome (E:\phpstudy_pro\WWW\study\vendor\spatie\browsershot\bin\browser.js:74:20)
 in E:\phpstudy_pro\WWW\study\vendor\spatie\browsershot\src\Browsershot.php on line 596

Symfony\Component\Process\Exception\ProcessFailedException: The command "node ^"E:^\phpstudy_pro^\WWW^\study^\vendor^\spatie^\browsershot^\src/../bin/browser.js^" ^"^{^\^"url^\^":^\^"www.baidu.com^\^",^\^"action^\^":^\^"screenshot^\^",^\^"options^\^":^{^\^"type^\^":^\^"png^\^",^\^"path^\^":^\^"example.png^\^",^\^"args^\^":^[^],^\^"viewport^\^":^{^\^"width^\^":800,^\^"height^\^":600^},^\^"delay^\^":3000,^\^"timeout^\^":60000^}^}^"" failed.

Exit Code: 1(General error)

Working directory: E:\phpstudy_pro\WWW\study

Output:
================


Error Output:
================
ProtocolError: Protocol error (Page.navigate): Cannot navigate to invalid URL
    at <instance_members_initializer> (E:\phpstudy_pro\WWW\study\node_modules\puppeteer-core\lib\cjs\puppeteer\common\CallbackRegistry.js:93:14)
    at new Callback (E:\phpstudy_pro\WWW\study\node_modules\puppeteer-core\lib\cjs\puppeteer\common\CallbackRegistry.js:97:16)
    at CallbackRegistry.create (E:\phpstudy_pro\WWW\study\node_modules\puppeteer-core\lib\cjs\puppeteer\common\CallbackRegistry.js:22:26)
    at Connection._rawSend (E:\phpstudy_pro\WWW\study\node_modules\puppeteer-core\lib\cjs\puppeteer\cdp\Connection.js:89:26)
    at CdpCDPSession.send (E:\phpstudy_pro\WWW\study\node_modules\puppeteer-core\lib\cjs\puppeteer\cdp\CDPSession.js:66:33)
    at navigate (E:\phpstudy_pro\WWW\study\node_modules\puppeteer-core\lib\cjs\puppeteer\cdp\Frame.js:172:51)
    at CdpFrame.goto (E:\phpstudy_pro\WWW\study\node_modules\puppeteer-core\lib\cjs\puppeteer\cdp\Frame.js:150:17)
    at CdpFrame.<anonymous> (E:\phpstudy_pro\WWW\study\node_modules\puppeteer-core\lib\cjs\puppeteer\util\decorators.js:98:27)
    at CdpPage.goto (E:\phpstudy_pro\WWW\study\node_modules\puppeteer-core\lib\cjs\puppeteer\api\Page.js:567:43)
    at callChrome (E:\phpstudy_pro\WWW\study\vendor\spatie\browsershot\bin\browser.js:74:20)
 in E:\phpstudy_pro\WWW\study\vendor\spatie\browsershot\src\Browsershot.php on line 596

Call Stack:
    0.0001     399472   1. {main}() E:\phpstudy_pro\WWW\study\index.php:0
    0.0034     657784   2. Spatie\Browsershot\Browsershot->save($targetPath = 'example.png') E:\phpstudy_pro\WWW\study\index.php:9      
    0.0034     658568   3. Spatie\Browsershot\Browsershot->callBrowser($command = ['url' => 'www.baidu.com', 'action' => 'screenshot', 'options' => ['type' => 'png', 'path' => 'example.png', 'args' => [...], 'viewport' => [...], 'delay' => 3000, 'timeout' => 60000]]) E:\phpstudy_pro\WWW\study\vendor\spatie\browsershot\src\Browsershot.php:410

解决办法:在php中的url参数中添加http{s}前缀

Browsershot::url('https://www.baidu.com')
    ->timeout(60000) // 设置超时时间为 60 秒

5.访问超时

Fatal error: Uncaught Symfony\Component\Process\Exception\ProcessFailedException: The command "node ^"E:^\phpstudy_pro^\WWW^\study^\vendor^\spatie^\browsershot^\src/../bin/browser.js^" ^"^{^\^"url^\^":^\^"https:^\/^\/www.baidu.com^\^",^\^"action^\^":^\^"screenshot^\^",^\^"options^\^":^{^\^"type^\^":^\^"png^\^",^\^"path^\^":^\^"example.png^\^",^\^"args^\^":^[^],^\^"viewport^\^":^{^\^"width^\^":1920,^\^"height^\^":1080^},^\^"timeout^\^":30000,^\^"waitUntil^\^":^\^"networkidle0^\^"^}^}^"" failed. Exit Code: 1(General error) Working directory: E:\phpstudy_pro\WWW\study Output: ================ Error Output: ================ Error: net::ERR_TIMED_OUT at https://www.baidu.com at navigate (E:\phpstudy_pro\WWW\study\node_modules\puppeteer-core\lib\cjs\puppeteer\cdp\Frame.js:183:27) at async Deferred.race (E:\phpstudy_pro\WWW\study\node_modules\puppeteer-core\lib\cjs\puppeteer\util\Deferred.js:36:20) at async CdpFrame.goto (E:\phpstudy_pro\WWW\study\node_modules\puppeteer-core\lib\cjs\puppeteer\cdp\Frame.js:149:25) in E:\phpstudy_pro\WWW\study\vendor\spatie\browsershot\src\Browsershot.php on line 596

解决办法:检查自己的网络是否正常

6.未找到浏览器

Fatal error: Uncaught Symfony\Component\Process\Exception\ProcessFailedException: The command "PATH=$PATH:/usr/local/bin NODE_PATH=npm root -g node '/www/wwwroot/域名/api/web/vendor/spatie/browsershot/src/../bin/browser.js' '{"url":"https:\/\/www.baidu.com","action":"screenshot","options":{"type":"png","path":"example.png","args":[],"viewport":{"width":1920,"height":1080},"timeout":60000000}}'" failed. Exit Code: 1(General error) Working directory: /www/wwwroot/域名/api/web Output: ================ Error Output: ================ npm WARN config init.module Use --init-module instead. Error: Could not find Chrome (ver. 128.0.6613.119). This can occur if either 1. you did not perform an installation before running the script (e.g. npx puppeteer browsers install ${browserType}) or 2. your cache path is incorrectly configured (which is: /home/www/.cache/puppeteer). For (2), check out our guide on configuring puppeteer at https://pptr.dev/guides/configuration. at ChromeLauncher.resolveExecutablePath in /www/wwwroot/域名/api/web/vendor/spatie/browsershot/src/Browsershot.php on line 596

解决办法:检查自己的php中设置的浏览器路径是否正确,是否能正常使用?我采用的路径为 /home/www/.cache/puppeteer/chrome/linux-128.0.6613.119/chrome-linux64/chrome

如果用终端获取到浏览器的默认为/home/ubuntu/.cache/而非/home/www/.cache/需要自己手动移到www文件夹下路径要与原来的ubuntu路径一致!

当然你也可以选择安装到api的路径下,具体是否可用我没去尝试,请自行尝试

$browsershot = Browsershot::url($url)
        ->setChromePath('/home/www/.cache/puppeteer/chrome/linux-128.0.6613.119/chrome-linux64/chrome');

其他的问题基本都是出在php内也不是售卖大问题所以就总结到这了!

图片效果↓