Next.js と Puppeteer を使用して Web ページのスクリーンショットをキャプチャする方法

Susan Sarandon
リリース: 2024-11-04 07:44:30
オリジナル
914 人が閲覧しました

How to Capture Web Page Screenshots with Next.js and Puppeteer

Web ページのスクリーンショットをプログラムでキャプチャすると、プレビューの生成や画像ベースのレポートの作成などに非常に役立ちます。このガイドでは、URL を取得して PNG スクリーンショットを生成する Next.js API ルートを構築します。私たちのセットアップでは、Puppeteer と chrome-aws-lambda を使用してヘッドレス Chrome ブラウザを活用し、汎用性が高く本番環境に対応できるようにしています。

まず、新しい Next.js プロジェクトを設定し、API がスクリーンショットをキャプチャする方法を理解するためにコードを段階的に見ていきます。

前提条件

  • Next.js アプリのセットアップ
  • Puppeteer を使用した API ルートの構成
  • キャプチャ インターフェイスの React コンポーネントを作成する
  • Puppeteer のローカル構成とデプロイメント構成の説明

新しい Next.js プロジェクトを始める

  1. 新しい Next.js アプリを作成します。
npx create-next-app@latest capture-image-app
cd capture-image-app
ログイン後にコピー
  1. 必要な依存関係をインストールします。
npm install puppeteer puppeteer-core chrome-aws-lambda busboy
ログイン後にコピー

ステップ 2: スクリーンショットを生成するための API ルートを作成する

次に、指定された URL に基づいてスクリーンショットをキャプチャして返す API エンドポイントを設定します。

pages/api フォルダーに、generate-png.ts という名前の新しいファイルを作成し、次のコードを追加します。

import { NextApiRequest, NextApiResponse } from "next";
import busboy, { Busboy } from "busboy"; // Use busboy for multipart parsing
import chromium from "chrome-aws-lambda";
import puppeteerCore from "puppeteer-core"; // Import puppeteer-core directly
import puppeteer from "puppeteer"; // Import puppeteer directly

// Conditional import for Puppeteer based on the environment
const puppeteerModule = process.env.NODE_ENV === "production" ? puppeteerCore : puppeteer;

export const config = {
  api: {
    bodyParser: false, // Disable default body parsing to handle raw binary data (Blob)
  },
};

const delay = (ms: number): Promise<void> => new Promise((resolve) => setTimeout(resolve, ms));

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
): Promise<void> {
  try {
    if (req.method === "POST") {
      const bb: Busboy = busboy({ headers: req.headers });
      let width: number = 1920; // Default width
      let height: number = 0; // Default height
      let delayTime: number = 6000;
      const buffers: Buffer[] = [];

      bb.on("file", (_name: string, file: NodeJS.ReadableStream) => {
        file.on("data", (data: Buffer) => buffers.push(data));
      });

      bb.on("field", (name: string, value: string) => {
        if (name === "width") width = parseInt(value, 10) || 1920;
        if (name === "height") height = parseInt(value, 10) || 0;
        if (name === "delay") delayTime = parseInt(value, 10) || 6000;
      });

      bb.on("finish", async () => {
        const blobBuffer: Buffer = Buffer.concat(buffers);
        const htmlContent: string = blobBuffer.toString("utf-8");

        const browser = await puppeteerModule.launch({
          args: ["--start-maximized"],
          executablePath: process.env.NODE_ENV === "production"
            ? await chromium.executablePath || "/usr/bin/chromium-browser"
            : undefined,  // No custom executable path needed for local
          headless: true,
        });

        const page = await browser.newPage();

        // Load the HTML content directly
        await page.setContent(htmlContent, { waitUntil: "networkidle0" });

        //@ts-expect-error todo
        const bodyHeight = await page.evaluate(() => {
          return document.body.scrollHeight; // Get the full scrollable height of the body
        });

        await page.setViewport({
          width: Number(width),
          height: height || bodyHeight, // Use the provided height or fallback to the full body height
          deviceScaleFactor: 2,
        });

        await delay(delayTime);

        const screenshotBuffer = await page.screenshot({
          fullPage: !height,
          type: "png",
          omitBackground: false,
        });

        await browser.close();

        res.setHeader("Content-Type", "image/png");
        res.setHeader(
          "Content-Disposition",
          "attachment; filename=screenshot.png"
        );
        res.status(200).end(screenshotBuffer);
      });

      req.pipe(bb); // Pipe the request stream to busboy
    } else {
      res.setHeader("Allow", ["POST"]);
      res.status(405).end(`Method ${req.method} Not Allowed`);
    }
  } catch (error) {
    console.error("ERROR", error);
    res.status(500).end("Internal Server Error");
  }
}


ログイン後にコピー

*説明: ローカル環境と本番環境の Puppeteer の選択
*

このコードでは、puppeteer の動的インポートを設定しました:

  • ローカル開発: NODE_ENV が本番環境ではない場合、puppeteer が使用されます。これはセットアップが簡単で、chrome-aws-lambda を必要としません。

  • プロダクション: サーバーレスデプロイメントの場合、環境は NODE_ENV をプロダクションとして検出し、puppeteer-core を chrome-aws-lambda とともにロードします。これにより、AWS Lambda やその他の同様の環境で動作できるようになります。このセットアップでは、chrome-aws-lambda が正しい Chromium パスを提供し、サーバーレス プロバイダーとの互換性を確保します。

ステップ 3: UI 用の単純な React コンポーネントを作成する

ここでは、ユーザーが Web ページ キャプチャの値を入力できる簡単なフォームを作成します。このフォームは、スクリーンショットをキャプチャして PDF 形式でダウンロードするための生成関数をトリガーします。

import { useState } from "react";

export default function ScreenCaptureComponent() {
  const [isProcessing, setProcessing] = useState(false);
  const [width, setWidth] = useState<string>("1920");
  const [height, setHeight] = useState<string>("1000");
  const [delay, setDelay] = useState<string>("6000");

  // Function to clone HTML and prepare for capture
  function takeScreenshot() {
    const clonedElement = document.body.cloneNode(true) as HTMLElement;
    const blob = new Blob([clonedElement.outerHTML], { type: "text/html" });
    return blob;
  }

  // Function to capture screenshot by sending cloned HTML to API
  async function generateCapture() {
    setProcessing(true);

    const htmlBlob = takeScreenshot();

    if (!htmlBlob) {
      setProcessing(false);
      return;
    }

    try {
      const formData = new FormData();
      formData.append("file", htmlBlob);
      formData.append("width", width);
      formData.append("height", height);
      formData.append("delay", delay);
      const response = await fetch("/api/generate-png", {
        method: "POST",
        body: formData,
      });

      if (!response.ok) throw new Error("Capture failed");

      const blob = await response.blob();
      const downloadUrl = URL.createObjectURL(blob);
      const link = document.createElement("a");
      link.href = downloadUrl;
      link.download = "capture.png";
      link.click();
      URL.revokeObjectURL(downloadUrl);
    } catch (error) {
      console.error("Failed to capture screenshot", error);
    } finally {
      setProcessing(false);
    }
  }

  return (
    <div
      style={{
        maxWidth: "400px",
        margin: "50px auto",
        padding: "24px",
        backgroundColor: "white",
        borderRadius: "8px",
        width: "100%",
        boxShadow: "0 4px 6px rgba(0, 0, 0, 0.1)",
      }}
    >
      <h2
        style={{
          fontSize: "24px",
          fontWeight: "600",
          textAlign: "center",
          marginBottom: "16px",
        }}
      >
        Webpage Screenshot Capture
      </h2>
      <form
        onSubmit={(e) => {
          e.preventDefault();
          generateCapture();
        }}
        style={{
          display: "flex",
          flexDirection: "column",
          alignItems: "center",
          marginBottom: "16px",
        }}
      >
        <label
          style={{ marginBottom: "8px", fontWeight: "500" }}
          htmlFor="width"
        >
          Width (px)
        </label>
        <select
          id="width"
          value={width}
          onChange={(e) => setWidth(e.target.value)}
          style={{
            width: "100%",
            padding: "8px",
            marginBottom: "16px",
            borderRadius: "4px",
            border: "1px solid #ccc",
            outline: "none",
          }}
        >
          <option value="1920">1920 (Full HD)</option>
          <option value="1366">1366 (Laptop)</option>
          <option value="1280">1280 (Desktop)</option>
          <option value="1024">1024 (Tablet Landscape)</option>
          <option value="768">768 (Tablet Portrait)</option>
          <option value="375">375 (Mobile)</option>
        </select>

        <label
          style={{ marginBottom: "8px", fontWeight: "500" }}
          htmlFor="height"
        >
          Height (px)
        </label>
        <input
          type="number"
          id="height"
          value={height}
          onChange={(e) => setHeight(e.target.value)}
          required
          style={{
            width: "100%",
            padding: "8px",
            marginBottom: "16px",
            borderRadius: "4px",
            border: "1px solid #ccc",
            outline: "none",
          }}
        />

        <label
          style={{ marginBottom: "8px", fontWeight: "500" }}
          htmlFor="delay"
        >
          Delay (ms)
        </label>
        <input
          type="number"
          id="delay"
          value={delay}
          onChange={(e) => setDelay(e.target.value)}
          required
          style={{
            width: "100%",
            padding: "8px",
            marginBottom: "16px",
            borderRadius: "4px",
            border: "1px solid #ccc",
            outline: "none",
          }}
        />

        <button
          type="submit"
          disabled={isProcessing}
          style={{
            padding: "8px 16px",
            color: "white",
            borderRadius: "4px",
            transition: "background-color 0.3s",
            backgroundColor: isProcessing ? "#b0bec5" : "#2196F3",
            cursor: isProcessing ? "not-allowed" : "pointer",
          }}
        >
          {isProcessing ? "Capturing..." : "Capture Screenshot"}
        </button>
      </form>

      {/* Example HTML Element to Capture */}
      <div id="capture-area" style={{ display: "none" }}>
        <h3
          style={{
            fontSize: "20px",
            fontWeight: "600",
          }}
        >
          Content to Capture
        </h3>
        <p>This is an example of the HTML content that will be captured.</p>
      </div>
    </div>
  );
}
ログイン後にコピー

結論

このチュートリアルでは、Next.js での Web ページ キャプチャ ツールのセットアップ、Puppeteer でのスクリーンショットの処理、インタラクティブなフロントエンド コンポーネントの作成について説明します。バンドル サイズを削減し、サーバーレス環境に最適化するために、ローカルで puppeteer を使用し、運用環境では puppeteer-core に切り替えることを忘れないでください。コーディングを楽しんでください!

以上がNext.js と Puppeteer を使用して Web ページのスクリーンショットをキャプチャする方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

ソース:dev.to
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
著者別の最新記事
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート