JavaScript >> Javascript 文檔 >  >> Node.js

如何在 Node.js 中使用 Puppeteer 將 HTML 轉換為圖像

如何在 Node.js 中設置 Puppeteer 以使用 HTML 和 CSS 動態生成圖像,以及如何將生成的圖像寫入磁盤和 Amazon S3。

開始使用

對於本教程,我們將使用 CheatCode Node.js 樣板作為起點。這將為我們奠定堅實的基礎,無需大量自定義代碼。

首先,從 Github 克隆樣板:

終端

git clone https://github.com/cheatcode/nodejs-server-boilerplate.git

然後,cd 進入目錄並安裝依賴項:

終端

cd nodejs-server-boilerplate && npm install

接下來,安裝puppeteer 包裝:

終端

npm i puppeteer

最後,安裝完所有依賴項後,使用以下命令啟動服務器:

終端

npm run dev

完成所有這些後,我們的第一步將是設置一條路徑,我們將在其中顯示我們的圖像以進行測試。

在服務器上添加路由進行測試

在克隆項目內部,打開 /api/index.js 項目根目錄下的文件:

/api/index.js

import graphql from "./graphql/server";

export default (app) => {
  graphql(app);

  // We'll add our test route here.
};

這裡,app 表示在 /index.js 的樣板中為我們設置的 Express.js 應用程序實例 .我們將使用它來創建我們的測試路線:

/api/index.js

import graphql from "./graphql/server";

export default (app) => {
  graphql(app);

  app.use("/graphic", (req, res) => {
    res.send("Testing 123");
  });
};

十分簡單。為了測試它,在你的服務器運行的情況下,打開你的瀏覽器並前往 http://localhost:5001/graphic 你應該會看到“Testing 123”的顯示。

使用 Puppeteer 連接圖像生成器

接下來,我們需要連接我們的圖像生成。為此,我們將創建一個單獨的模塊,我們可以將它導入任何我們想在應用程序中將 HTML 轉換為圖像的位置:

/lib/htmlToImage.js

import puppeteer from "puppeteer";

export default async (html = "") => {
 // We'll handle our image generation here.
};

首先,我們導入 puppeteer 從我們之前安裝的包中。接下來,我們設置我們的 htmlToImage() 函數,接受單個 html 字符串作為參數。

/lib/htmlToImage.js

import puppeteer from "puppeteer";

export default async (html = "") => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
};

首先,我們需要創建一個 Puppeteer 實例。為此,我們使用 puppeteer.launch() .請注意,這裡我們使用 JavaScript async/await 語法,因為我們期望 puppeteer.launch() 給我們一個承諾。通過使用 await 這裡的關鍵字,我們告訴 JavaScript——以及擴展的 Node.js——等待 直到收到 puppeteer.launch() 的響應 .

接下來,使用我們的 browser 創建後,我們創建一個 page 使用 browser.newPage() (想想這就像在你自己的瀏覽器中打開一個標籤,但處於“無頭”狀態,這意味著沒有用戶界面——瀏覽器只存在於內存中)。同樣,我們預計會返回一個 Promise,所以我們 await 在繼續之前打這個電話。

/lib/htmlToImage.js

import puppeteer from "puppeteer";

export default async (html = "") => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();

  await page.setContent(html);

  const content = await page.$("body");
  const imageBuffer = await content.screenshot({ omitBackground: true });
};

接下來,我們進入重要的部分。在這裡,使用 page.setContent() 我們告訴 Puppeteer 用 html 填充瀏覽器頁面 我們作為參數傳遞給函數的字符串。這相當於您在瀏覽器中加載一個網站,並將服務器響應中的 HTML 加載到內存中。

接下來,我們使用 Puppeteer 的內置 DOM(文檔對像模型)API 來訪問內存中瀏覽器的 HTML。在這裡,在我們的 content 變量,我們存儲調用await page.$("body");的結果 .這樣做的目的是獲取我們的 HTML 的內存渲染版本並提取 content <body></body> 的 標籤(我們渲染的 HTML)。

作為響應,我們返回一個 Puppeteer ElementHandle 這是一種說法,即“Puppeteer 在內存中表示的元素”,或者,我們渲染的 HTML 作為 Puppeteer 友好的對象。

接下來,使用那個 content ,我們使用 Puppeteer .screenshot() 方法來截取我們在內存中呈現的 HTML 頁面的屏幕截圖。為了完全控製圖像中呈現的內容,我們傳遞 omitBackgroundtrue 以確保我們使頁面背景完全透明。

作為回應,我們期望得到一個 imageBuffer .這是原始圖像文件內容 ,但不是實際圖像本身(這意味著您將看到一堆隨機二進制數據,而不是圖像)。在我們了解如何獲取實際圖像之前,我們需要做一些清理工作:

/lib/htmlToImage.js

import puppeteer from "puppeteer";

export default async (html = "") => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();

  await page.setContent(html);

  const content = await page.$("body");
  const imageBuffer = await content.screenshot({ omitBackground: true });

  await page.close();
  await browser.close();

  return imageBuffer;
};

在這裡,我們添加了兩個調用:page.close()browser.close() .可以預見的是,這些會關閉我們在內存和瀏覽器中打開的頁面(或瀏覽器選項卡)。 這樣做非常重要,因為如果不這樣做,您最終會將未關閉的瀏覽器留在內存中,這會耗盡您的服務器資源(並且可能由於內存溢出而導致潛在的崩潰) .

最後,我們返回我們檢索到的 imageBuffer 來自函數。

在我們的路線上渲染圖像

更進一步。從技術上講,此時,我們還沒有將任何 HTML 傳遞給我們的函數。讓我們導入 htmlToImage() 回到我們的 /api/index.js 文件並從我們的路由中調用它:

/api/index.js

import graphql from "./graphql/server";
import htmlToImage from "../lib/htmlToImage";

export default (app) => {
  graphql(app);

  app.use("/graphic", async (req, res) => {
    const imageBuffer = await htmlToImage(`<!-- Our HTML will go here. -->`);

    res.set("Content-Type", "image/png");
    res.send(imageBuffer);
  });
};

在這裡,我們已經導入了我們的 htmlToImage /lib/htmlToImage 中的函數 .在我們路由的回調中,我們添加了 async 標記,因為現在我們使用的是 await htmlToImage() 之前的關鍵字 功能。請記住,這是必要的,因為我們需要等待 Puppeteer 完成其工作之前 我們可以依靠它向我們返回數據。

除了調用之外,我們還修改了響應路由請求的方式。在這裡,我們添加了對 res.set() 的調用 ,設置Content-Type image/png 的標頭 .還記得我們提到過 imageBuffer 我們從 content.screenshot() 收到 技術上 圖像了嗎?這就是改變這一點的原因。這裡,image/png 被稱為 MIME 類型;一種被瀏覽器識別的數據類型,上面寫著“我給你的原始數據應該呈現為___。”在這種情況下,我們說的是“將此原始數據渲染為 .png 圖像。”

最後,作為我們請求的響應體,我們傳遞 imageBufferres.send() .有了這個,現在,讓我們在混合中添加一些 HTML,然後進行測試:

/api/index.js

import graphql from "./graphql/server";
import htmlToImage from "../lib/htmlToImage";

export default (app) => {
  graphql(app);

  app.use("/graphic", async (req, res) => {
    const imageBuffer = await htmlToImage(`
      <html>
        <head>
          <style>
            * {
              margin: 0;
              padding: 0;
            }

            *,
            *:before,
            *:after {
              box-sizing: border-box;
            }

            html,
            body {
              background: #0099ff;
              width: 1200px;
              height: 628px;
              font-family: "Helvetica Neue", "Helvetica", "Arial", sans-serif;
            }

            div {
              width: 1200px;
              height: 628px;
              padding: 0 200px;
              display: flex;
              align-items: center;
              justify-content: center;
            }
            
            h1 {
              font-size: 48px;
              line-height: 56px;
              color: #fff;
              margin: 0;
              text-align: center;
            }
          </style>
        </head>
        <body>
          <div>
            <h1>How to Convert HTML to an Image Using Puppeteer in Node.js</h1>
          </div>
        </body>
      </html>
    `);

    res.set("Content-Type", "image/png");
    res.send(imageBuffer);
  });
};

在這裡,我們傳遞一個包含一些 HTML 的純 JavaScript 字符串。我們已經設置了一個由 <html></html> 組成的基本 HTML 樣板文件 用 <head></head> 填充的標籤 標籤和 <body></body> 標籤。在 <head></head> 標籤,我們添加了一個 <style></style> 標籤包含一些 CSS 來設置我們的 HTML 內容的樣式。

<body></body> ,我們添加了一些簡單的 HTML:<div></div><h1></h1> 填充的標籤 標籤。現在,如果我們回到 http://localhost:5001/graphic 的測試路線 你應該會看到這樣的東西:

很酷,對吧?如果您右鍵單擊圖像並下載它,您就可以像打開任何其他圖像一樣在您的計算機上打開它。

在我們結束之前,最好了解如何永久存儲這些數據,而不是僅僅在瀏覽器中渲染並手動下載。接下來,我們將看兩種方法:將生成的圖像保存到磁盤和將生成的圖像保存到 Amazon S3。

將生成的圖像寫入磁盤

幸運的是,將我們的文件寫入磁盤非常簡單。讓我們稍微修改一下我們的路由(我們仍然會使用瀏覽器中的 URL 來“觸發”生成):

/api/index.js

import fs from "fs";
import graphql from "./graphql/server";
import htmlToImage from "../lib/htmlToImage";

export default (app) => {
  graphql(app);

  app.use("/graphic", async (req, res) => {
    const imageBuffer = await htmlToImage(`
      <html>
        [...]
      </html>
    `);

    fs.writeFileSync("./image.png", imageBuffer);

    res.set("Content-Type", "image/png");
    res.send(imageBuffer);
  });
};

很簡單。在這裡,我們所做的只是導入 fs (Node.js 中的文件系統——fs 不需要安裝),然後添加了對fs.writeFileSync()的調用 ,傳遞我們希望文件存儲的路徑(在這種情況下,在一個名為 image.png 的文件中 在我們項目的根目錄)和文件的數據。

值得注意的是,請注意,對於文件擴展名,我們明確設置了 image/png .類似於我們看到的將圖像直接渲染到我們的路線,即 .png 向計算機傳達此文件的內容代表 .png 中的圖像 格式。

現在,當我們訪問我們的路線時,我們的文件將被寫入 /image.png 在磁盤上以及在瀏覽器中呈現。

將生成的圖像發送到 Amazon S3

在我們繼續之前,為了訪問 Amazon S3,我們需要添加一個新的依賴項:aws-sdk .讓我們現在安裝它:

終端

npm i aws-sdk

接下來,雖然類似,但將我們生成的圖像發送到 Amazon S3 有點複雜。為此,我們將在 /lib/s3.js 處創建一個新文件 實現一些代碼來幫助我們連接到 Amazon S3 並編寫我們的文件(稱為“將對象放入存儲桶”)。

/lib/s3.js

import AWS from "aws-sdk";

AWS.config = new AWS.Config({
  accessKeyId: "<Your Access Key ID Here>",
  secretAccessKey: "<Your Secret Access Key Here>",
  region: "us-east-1",
});

// We'll write the S3 code for writing files here.

在這裡,我們導入 AWS 來自 aws-sdk 我們剛剛安裝。接下來,我們設置 AWS.config 等於 AWS.Config 的新實例 (注意名稱之間的區別是大寫“C”),傳入我們要用於與 AWS 通信的憑據。

如果您還沒有必要的憑據,您將需要閱讀 Amazon 提供的有關如何創建新用戶的教程。對於此示例,在創建用戶時,請確保在第一步中啟用“程序訪問”並附加 AmazonS3FullAccess 第二步中“直接附加現有策略”下的策略。

生成訪問密鑰 ID 和秘密訪問密鑰後,您可以填充上面的字段。

公平警告:請勿將這些密鑰提交到公共 Github 存儲庫。 Github 上有機器人掃描未受保護的 AWS 密鑰,並使用它們來啟動機器人場並執行非法活動(同時讓您買單)。

對於 region ,您需要指定在其中創建 Amazon S3 存儲桶的區域。該區域是您的存儲桶在 Internet 上的地理位置。如果您還沒有創建存儲桶,您需要閱讀 Amazon 提供的有關如何創建新存儲桶的教程。

在設置您的存儲桶時,對於本教程,請確保取消選中“阻止公共訪問”。這對於生產環境來說是一個很好的設置,但是因為我們只是在玩,所以取消選中它是安全的。 公平警告:請勿在此存儲桶中存儲任何敏感數據。

/lib/s3.js

import AWS from "aws-sdk";

AWS.config = new AWS.Config({
  accessKeyId: "<Your Access Key ID Here>",
  secretAccessKey: "<Your Secret Access Key Here>",
  region: "us-east-1",
});

const s3 = new AWS.S3();

export default {
  putObject(options = {}) {
    return new Promise((resolve, reject) => {
      s3.putObject(
        {
          Bucket: options.bucket,
          ACL: options.acl || "public-read",
          Key: options.key,
          Body: options.body,
          ContentType: options.contentType,
        },
        (error, response) => {
          if (error) {
            console.warn("[s3] Upload Error: ", error);
            reject(error);
          } else {
            resolve({
              url: `https://${options.bucket}.s3.amazonaws.com/${options.key}`,
              name: options.key,
              type: options.contentType || "application/",
            });
          }
        }
      );
    });
  },
};

一旦我們配置了我們的 AWS IAM 用戶和存儲桶區域,接下來,我們要創建一個 s3 的實例 通過調用 new AWS.S3() .

提前考慮,我們希望以後需要其他 S3 方法,因此,我們不只是從我們的文件中導出單個函數,而是在這裡導出一個帶有 putObject 的對象 方法。

對於該方法(定義為對像一部分的函數的名稱),我們預計會有一個 options 要傳遞的對象,其中包含有關如何處理我們的文件的數據和說明。在這個函數的主體中,我們返回一個 Promise 以便我們可以包裝異步 s3.putObject() aws-sdk 中的方法 包。

當我們調用該方法時,我們根據 Amazon S3 SDK 文檔傳遞選項,描述我們的文件、我們希望它存在的位置以及與之關聯的權限。在 s3.putObject() 的回調方法中 ,假設我們沒有錯誤,我們構造一個對象來描述我們在 Amazon S3 和 resolve() 上的新文件的位置 我們從函數返回的 Promise。

/api/index.js

import fs from "fs";
import graphql from "./graphql/server";
import htmlToImage from "../lib/htmlToImage";
import s3 from "../lib/s3";

export default (app) => {
  graphql(app);

  app.use("/graphic", async (req, res) => {
    const imageBuffer = await htmlToImage(`
      <html>
        [...]
      </html>
    `);

    fs.writeFileSync("./image.png", imageBuffer);

    const s3File = await s3.putObject({
      bucket: "<Your Bucket Name Here>",
      key: `generated-image.png`,
      body: imageBuffer,
      contentType: "image/png",
    });

    console.log(s3File);

    res.set("Content-Type", "image/png");
    res.send(imageBuffer);
  });
};

回到我們的 /api/index.js 文件,現在我們準備上傳到 S3。稍微修改我們之前的代碼,我們導入我們的 s3 /lib/s3.js 中的文件 在頂部,然後在路由回調的主體中,我們將調用添加到 s3.putObject() , 傳入 bucket 我們希望我們的文件存儲在 key (相對於我們存儲桶的根目錄的路徑和文件名)我們的文件 body (原始 imageBuffer 數據)和 contentType (同樣的image/png 我們之前討論過的 MIME 類型)。

最後,我們確保 await 我們調用 S3 以確保我們取回我們的文件。在您自己的應用中,如果您對在後台上傳的文件沒有問題,則可能不需要這樣做。

而已!現在,如果我們訪問 http://localhost:5001/graphic 在我們的應用程序中,我們應該看到我們的圖形上傳到 Amazon S3,然後在終端中確認退出:

終端

{
  url: 'https://cheatcode-tutorials.s3.amazonaws.com/generated-image.png',
  name: 'generated-image.png',
  type: 'image/png'
}

總結

在本教程中,我們學習瞭如何使用 Puppeteer 從 HTML 和 CSS 生成圖像。我們學習瞭如何在內存中啟動瀏覽器,向其傳遞一些 HTML,然後使用 Puppeteer 截取渲染頁面的屏幕截圖。我們還學習瞭如何將我們的圖像直接返回到瀏覽器,以及如何使用 Node.js 文件系統將該文件存儲在磁盤上,並使用 AWS JavaScript SDK 將我們的圖像上傳到 Amazon S3。


Tutorial JavaScript 教程
  1. 在 Vue 中遞歸渲染一個未知深度的嵌套數組!

  2. 子字符串和 JavaScript 的東西

  3. 使用 React &React bootstrap 構建匿名聊天應用

  4. 使用 Postgresql+Nestjs+Typeorm 進行地理定位

  5. vue.js 中的 v-for 循環

  6. 將對象轉換為方括號字符串(不使用 JSON.stringify)

  7. create-awesome-package :發布了一個 CLI 來引導你很棒的包🚀 📦

  1. 從 Markdown 生成的頁面獲取所有標題及其目標 URL

  2. GatsbyJs:優點和缺點

  3. 可選鏈接和空值合併即將出現在 JavaScript 中

  4. 何時使用 useMemo 和 useCallback - 第 3 部分

  5. Twitter Bootstrap Modal 停止 Youtube 視頻

  6. 將 Markdown 博客變成簡單的 SSG

  7. VueJS 2:vee-validate 3 – 子組件驗證不起作用

  1. 玩轉 CSS3:旋轉報紙

  2. 使用 Jest 自定義參數

  3. 用 HTML/CSS 開發星球大戰開場爬行

  4. 如何使用 VueJS 和 TailwindCSS 構建詳細信息下拉列表