GASでスプレッドシートをPDF化する方法

GAS

※本記事はプロモーションを含みます

目次

スプレッドシートで作成した請求書、見積書、月次売上レポートなどを社外の取引先や別部署に共有する際、編集可能なスプレッドシートのURLをそのまま渡すわけにはいかず、PDF形式に書き出して共有する運用がよく行われます。手作業で1件ずつエクスポートしてGoogleドライブに保存し、ファイル名を整えてメールに添付するというフローは、件数が増えるにつれて作業負荷が膨らみ、人的ミスの温床になります。なので、こうした定常業務を自動化したいときに頼りになるのがGoogle Apps Script(GAS)です。GASを使えば、スプレッドシートの内容をプログラムから直接PDFデータへと変換し、Googleドライブ内の指定フォルダに保存する一連の流れを自動化できます。本記事では、スプレッドシートをPDF化する基本的な処理の流れと、現場で開発を進める中で誰もが突き当たる仕様の注意点について順を追って整理していきましょう。

スプレッドシート全体のPDF化を最短で書く

月次の集計シートや業務進捗シートなど、スプレッドシート全体をまるごとバックアップとして保管したい場合や、ファイル内に不要な別シートが存在しないシンプルな台帳をそのままPDF出力したい場合には、標準のメソッドであるgetAsを利用した変換が最も手軽で安全です。ただし、複雑な認証トークンの構築や外部通信を意識することなく、わずか数行の記述だけでファイルオブジェクトをPDFバイナリへと変換できます。

このアプローチを採用するのは、対象のスプレッドシートに含まれるすべてのタブを出力対象として扱っても問題がない局面です。というのは、毎月の締め日に確定した売上管理シートをまるごとPDFとしてアーカイブに残すような運用や、単一のシートしか持たない独立したテンプレートファイルを元に生成処理を走らせるケースが該当するからです。以下のスクリプトは、アクティブなスプレッドシートを開き、その中身をPDFデータとして吸い上げた上で、Googleドライブに新規ファイルとして保存する最小構成のコードです。

function exportSpreadsheetToPdf() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const blob = ss.getAs("application/pdf");
  DriveApp.createFile(blob).setName("レポート.pdf");
}

このコードを実行すると、アクティブなスプレッドシートの全シートが結合された単一のPDFファイルが生成されます。なので、SpreadsheetApp.getActiveSpreadsheet()で対象のシートファイルそのものを取得し、そのオブジェクトに対してgetAs("application/pdf")を呼び出すことで、Google内部の変換エンジンが働き、MIMEタイプがPDFに指定されたBlob(バイナリデータ)がメモリ上に展開されます。そして最後にDriveApp.createFile(blob)を呼び出し、生成されたファイルに対してsetName()で任意の名前を付与して保存を確定させましょう。

この記述は非常にシンプルで読みやすい反面、後述するように「どの範囲がどのようにページ分割されるか」を細かく制御することができません。ただし、帳票としてミリ単位の見た目を整える必要がある用途ではレイアウトが崩れやすいため、まずは全体像をざっくりと保管する用途や、プロトタイピングの段階で素早く動作検証を行いたい場面で活用しましょう。

getAsによる一括変換で起きるシート構成とレイアウトのズレ

前述のgetAs("application/pdf")を使った実装を実際の業務ワークフローに投入した際、多くの人が最初に直面するのが「全シートが強制的にPDFに含まれてしまう」という挙動です。というのは、スプレッドシート業務では、印刷やPDF出力の対象となるメインシートのほかに、マスターデータを格納した参照用シート、計算用の中間シート、過去ログを残した退避用シートなど、複数のシートを1つのブック内に同居させることが日常茶飯事だからです。ただし、SpreadsheetAppのインスタンスに対してgetAsを実行すると、非表示にしていない限り、ファイル内に存在するすべてのシートが順に出力対象として連結されてしまいます。

「GAS シート PDF化」を行おうとする現場では、請求書シート1枚だけをPDF化して客先に送りたいのに、後ろに並んでいる原価計算シートや商品コードマスターシートまで一緒にPDF化されてしまい、情報漏洩のリスクにつながるという重大なトラブルが起こり得ます。なので、この問題に対処するため、一時的に出力不要なシートをhideSheet()で非表示に設定してからgetAsを呼び出し、変換完了後に再表示させるという回避策をとるアプローチも存在します。ただし、この方法は処理の途中でエラーが発生してスクリプトが停止した場合にシートが非表示のまま残ってしまう危険性があり、複数人が同時に閲覧している運用環境では表示がちらつくなどの副作用を生みます。

さらに、レイアウト面におけるズレも頻発します。というのは、手動でGoogleスプレッドシートのメニューから「印刷」を選んでPDF出力する場合は、用紙サイズ(A4やB5)、向き(縦・横)、余白の広さ、グリッド線の有無、シートを用紙幅に合わせる拡大縮小率などをプレビュー画面を見ながら微調整できるからです。ところが、getAsを使った一括変換では、これらのパラメータがスプレッドシート側の既定値に従って自動処理されるため、横に長い表が途中でぶつ切りになって2ページ目にまたがってしまったり、フォントサイズが極端に縮小されて視認性が損なわれたりすることがあります。なので、印刷設定があらかじめ完璧に保存されているシートであれば期待通りの出力が得られることもありますが、環境やフォントの差異によって改ページ位置がずれる現象は避けにくいため、厳密な帳票レイアウトを担保したい場合には別の手法を検討しましょう。

特定のシートだけを切り出してPDF出力したいときの壁とURLフェッチ

業務用の帳票自動化において「1つの特定シートだけを、指定した用紙設定で綺麗なPDFにしたい」という要求は極めて一般的です。ただし、標準のGAS APIであるSpreadsheetAppやDriveAppには、「第2シートだけを対象にPDF化する」という直接的なメソッドが用意されていません。なので、この壁を乗り越えるために実務で広く使われているのが、Googleスプレッドシートのエクスポート用URLをパラメータ付きで組み立て、UrlFetchApp.fetchを用いてHTTPリクエスト経由でPDFを取得する手法です。

スプレッドシートのURLには、WebブラウザからPDF形式で書き出す際に指定できるパラメータ群が存在します。というのは、特定シートのIDを示すgidをはじめ、用紙サイズを指定するsize、縦横を指定するportrait、用紙幅に合わせるfitw、余白を指定するtop_marginやleft_margin、グリッド線の表示有無を決めるgridlinesなどが用意されているからです。なので、これらをクエリ文字列として連結したURLを生成し、自スクリプトの認証トークン(OAuthトークン)をHTTPヘッダーのAuthorizationに付与してリクエストを送信することで、特定シートのみを理想的なレイアウトで描画したPDFバイナリを取り出すことが可能になります。

ただし、このURLフェッチを用いたアプローチは強力である反面、コードの複雑性が跳ね上がるというデメリットを伴います。スクリプト実行ユーザーのアクセストークンをScriptApp.getOAuthToken()から取得してヘッダーに含める必要があるほか、共有ドライブ内のファイルや組織のセキュリティポリシーによっては外部フェッチの権限エラーに遭遇することがあります。というのは、Googleの内部エクスポートURLの仕様は完全には公開されていない内部エンドポイントに近いため、将来的な仕様変更リスクをゼロにはできないからです。なので、プロジェクトの規模や保守性を考慮し、まずは全シート変換で事足りる運用(不要シートを別ファイルに分離する等)で済ませられないかを検討し、どうしても1シートのみの厳密な出力が必須である場合に限ってURLフェッチの構成へとステップアップしましょう。

ドライブへの保存先管理と権限まわりで引っかかりやすいポイント

コードが正常に走り、PDFのバイナリが生成されたとしても、その保存先を正しく設計していなければ運用面で大きな混乱を招きます。というのは、冒頭のサンプルコードで利用したDriveApp.createFile(blob)というメソッドは、引数に渡されたBlobを元に新規ファイルを作成しますが、保存先フォルダを明示的に指定しない場合、実行したユーザーの「マイドライブ」直下にファイルが保存されるからです。

個人の検証環境であればマイドライブ直下にファイルが作られても気付きやすいですが、チームで共有している業務フォルダ内で運用している場合、マイドライブ直下にファイルが散乱すると他のメンバーから保存場所が見えなくなってしまいます。なので、特にGoogle Workspaceの共有ドライブ(旧チームドライブ)上でスクリプトを動かす場合や、特定のプロジェクトフォルダ配下に「2024年10月度」といったサブフォルダを切って格納したい場合には、保存先の親フォルダをIDから特定して、そのフォルダオブジェクトのcreateFileを呼び出しましょう。

保存先フォルダをコントロールする場合のコードは以下のような構成を取ります。

function exportPdfToSpecificFolder() {
  const folderId = "ここに保存先フォルダのIDを記述";
  const folder = DriveApp.getFolderById(folderId);
  
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const blob = ss.getAs("application/pdf").setName("月次報告書.pdf");
  
  const file = folder.createFile(blob);
  console.log("保存完了: " + file.getUrl());
}

このように、DriveApp.getFolderById()を用いて目的のフォルダを取得し、folder.createFile(blob)を実行することで、散乱を防ぎ意図した階層へ確実にファイルを格納できます。ただし、ここで注意しなければならないのは、権限の範囲です。スクリプトを実行するアカウントがそのフォルダに対する「編集者」以上の権限を持っていない場合、実行時に権限不足エラーで処理が中断します。なので、サービスアカウントやトリガーを用いた時間指定実行を行う際には、トリガーの実行オーナーが該当フォルダに正しくアクセス権を持っているかを必ず確認しましょう。

また、同名のファイルが存在していてもcreateFileは上書きを行わず、同じ名前のファイルが同一フォルダ内に何個でも新規作成されるというGoogleドライブ特有の仕様があります。というのは、日次や月次で定期実行するシステムでは、古いファイルを削除するのか、ファイル名に実行日時やタイムスタンプを付与してユニークにするのかといった命名ルールの設計も合わせて行っておくことが、運用トラブルを未然に防ぐ鍵となるからです。

請求書や月次レポートの自動化ラインに組み込む際の設計

スプレッドシートをPDF化する処理は、それ単体で完結するケースよりも、一連の業務自動化パイプラインの中核パーツとして組み込まれるケースが大多数を占めます。代表的な例が、請求書シートに顧客ごとの売上データを流し込み、PDFとして書き出した上で、Gmailを使って取引先担当者へ自動送信するという仕組みです。なので、このパイプラインを安定して稼働させるためには、PDF生成処理の前後に適切なウェイトやデータ検証を挟みましょう。

タイトルとURLをコピーしました