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

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

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

image-yepn.png

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

一开始我所采用的时纯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

面板

宝塔

image-emxe.png

将这些文件全部丢在云端你的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内也不是售卖大问题所以就总结到这了!

图片效果↓