>웹 프론트엔드 >JS 튜토리얼 >Next.js와 Puppeteer를 사용하여 웹 페이지 스크린샷을 캡처하는 방법

Next.js와 Puppeteer를 사용하여 웹 페이지 스크린샷을 캡처하는 방법

Susan Sarandon
Susan Sarandon원래의
2024-11-04 07:44:30956검색

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

프로그래밍 방식으로 웹 페이지의 스크린샷을 캡처하는 것은 미리보기 생성, 이미지 기반 보고서 생성 등에 매우 유용할 수 있습니다. 이 가이드에서는 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");
  }
}


*설명: 로컬 환경과 프로덕션 환경에 따른 인형극 선택
*

이 코드에서는 인형극에 대한 동적 가져오기를 설정했습니다.

  • 로컬 개발: NODE_ENV가 프로덕션이 아닌 경우 설정이 더 간단하고 chrome-aws-lambda가 필요하지 않은 puppeteer를 사용합니다.

  • 프로덕션: 서버리스 배포의 경우 환경은 NODE_ENV를 프로덕션으로 감지하고 chrome-aws-lambda와 함께 puppeteer-core를 로드하므로 AWS Lambda 및 기타 유사한 환경에서 작동할 수 있습니다. 이 설정에서 chrome-aws-lambda는 올바른 Chromium 경로를 제공하여 서버리스 공급자와의 호환성을 보장합니다.

3단계: UI용 간단한 React 구성요소 생성

여기에서는 사용자가 웹페이지 캡처 값을 입력할 수 있는 간단한 양식을 만들어 보겠습니다. 이 양식은 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에서 웹페이지 캡처 도구를 설정하고, Puppeteer로 스크린샷을 처리하고, 대화형 프런트엔드 구성 요소를 만드는 방법을 다룹니다. 번들 크기를 줄이고 서버리스 환경에 맞게 최적화하려면 로컬에서 puppeteer를 사용하고 프로덕션에서는 puppeteer-core로 전환해야 합니다. 즐거운 코딩하세요!

위 내용은 Next.js와 Puppeteer를 사용하여 웹 페이지 스크린샷을 캡처하는 방법의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.