EmotionTechテックブログ

株式会社エモーションテックのProduct Teamのメンバーが、日々の取り組みや技術的なことを発信していくブログです。

ブラウザの画面をNode.jsでPDFに変換しよう

はじめに

こんにちは、テックリードのかどたみです。

お鍋が美味しい季節になってきましたね。私は最近豆乳鍋にハマっています。

さて、突然ですが皆さんはブラウザの画面を資料に載せたいときって無いでしょうか?私はBIのデータや、自社で導入している運用ツールのグラフなどを載せたいことがあります。毎回スクリーンショットを撮って資料作成ツールに貼り付けても良いですが、定例会議の資料などの場合毎回この作業をするのは面倒ですよね?

ということで今回はそれらの作業を自動化したいという目的のもと、ブラウザで表示されている画面の一部をサーバーサイドできれいなPDFに変換して保存する検証を行ったのでその方法を紹介したいと思います。

今回はNestJSでの実装を想定しているためNode.jsでの検証を行いました。

この記事はエモーションテック Advent Calendar 2022の1日目の記事です。

利用したライブラリ

今回の検証では2つのライブラリを利用しました。

1つは画面のキャプチャを取得するためにブラウザを制御できるpuppeteer、もう1つはPDFを生成するpdfkitです。

これらを使ってPDFを生成してみましょう。

処理の流れ

簡単に処理の流れを記載しておきます。

  1. puppeteerで該当ページにアクセス
  2. PDFにしたい要素が描画されるまで待つ
  3. 要素のスクリーンショットを取る
  4. PDFの生成する
  5. PDFにスクリーンショットを貼り付ける
  6. 保存する

実際のコード

では実際のコードを紹介します。

import * as PDFDocument from 'pdfkit';
import * as puppeteer from 'puppeteer';

async function createPdf() {
  // ブラウザの立ち上げ
  const browser = await puppeteer.launch({
    headless: true,
  });
  const page = await browser.newPage();

  // 対象ページへ遷移
  await page.goto('https://example.com');
  // 対象要素が描画されるまで待機
  await page.waitForSelector('.target-class');

  // スクリーンショットを取得
  const elements = await page.$$('.target-class');
  const screenshot = await elements[0].screenshot({ encoding: 'binary' });
  browser.close();

  // PDFを生成し、スクリーンショットを貼り付け
  const doc = new PDFDocument({ size: [841.89, 595.28] });
  doc.image(screenshot, 0, 0, {
    fit: [841.89, 595.28],
    align: 'center',
    valign: 'center',
  });
  doc.end();
  return doc;
}

たったこれだけでWebアプリの特定の要素を簡単にPDFにすることができます。 しかし、これだけではまだ実用に耐えない部分もあったので、改善点を示しながら紹介してみようと思います。

改善点

認証必須のページに対応

今回想定しているBIツールや運用ツールには認証が必要な場合が多いでしょう。 puppeteerでログインページから認証しても良いのですが、cookie情報がわかっていればそのままセットして、認証後のページにそのままアクセスすることができます。

const page = await browser.newPage();
await page.setCookie({
  name: 'emotion-tech-cookie',
  value: 'cookieContent',
  domain: emotion-tech.co.jp,
  secure: true,
});

await page.goto('ログイン必須のページ')

生成したPDFの画質が荒くなってしまう

例で示したコードを実際に動かしてみると、生成されたPDFの画像が粗くなります。 自分で利用する程度なら良いですが、他の人に見せる資料としてはあまりよろしくないです。 これはpuppeteerが起動するブラウザの設定としてviewportのscaleが最低のものになっているためです。なのでそれを上げていけば画質をしっかり上げることができます。

const page = await browser.newPage();
page.setViewport({
  deviceScaleFactor: 2,
});

ここで指定できる値は10までですが、2で十分きれいなPDFになり、4以降はあまり私の目では変わりないように見えます。強いこだわりがなければ2で実用には耐えうると思います。

参考に生成したPDFの画像を掲載しておきますが、画像だとちょっとわかりにくいですね。。。気になる方はぜひコードを書いて試してみてください。

1を指定したPDF

2を指定したPDF

スクロールが必要な箇所を対象にするとスクリーンショットがうまく撮れない

PDFにしたい要素がページの下部に存在するなど、スクロールしなければ見えない要素をPDFにする場合、スクリーンショットに要素が含まれたり含まれなかったりします。pupperteerでその要素へのスクロールが間に合っていないような、要素の下部だけの画像になります。 なので解決策としてはもともとその要素がスクロール無しで表示できる画面サイズを用意してあげることが挙げられます。

const page = await browser.newPage();
page.setViewport({
  width: 1280,
  height: 2000,
});

この設定によって安定してスクリーンショットが撮れるようになりました。

デプロイする場合にファイルをディスクに保存したくない

少し話は変わりますが、この機能をサービスとして展開したいと考えたときの話をします。 このままdocをファイルとして保存しても良いのですが、容量を考えると削除する処理が必要だったり、そもそも読み取り専用コンテナとして立ち上げたかったりすると思います。 なのでサービスとして展開する場合はディスクに保存したくはないことが多いですが、ご安心ください。pdfkitで作成されるPDFDocumentはそのままstreamとして扱うことができます。

doc.pipe(res);

この様にpipeを用いればそのままファイルがブラウザからダウンロードできたり、S3やGCSへのアップロードも簡単に行なえたりします。

おわりに

いかがでしたでしょうか?今回はブラウザの要素をPDFにする簡単な検証でした。 これを使えば定期的にブラウザのスクショを撮って資料を作成する手間が省けると思います。 もっといろんなことを自動化して快適なビジネスライフを過ごせるように頑張っていこうと思います。 エモーションテックでは顧客体験、従業員体験の改善をサポートし、世の中の体験を変えるプロダクトを開発しています。プロダクトに興味のある方、自動化推進をやっていきたい方、ぜひ採用ページからご応募をお願いいたします。

hrmos.co

hrmos.co