GASでドキュメントを連続作成してPDF保存してみた(らハマった)

2023.08.21

宛名の違う簡単なドキュメントを複数作成する必要があり、めったにある作業じゃないけど、手動で作るよりGASで簡単なスクリプト書いた方が早いだろう、と思いやってみたら、謎のエラーにハマったという体験談をご紹介します。

(最終的に)GASでやったこと

一覧データとテンプレを元に、全員分の書類をGoogleドライブにPDF出力します。

準備作業

  • 一覧データシートの用意(Notion DBで管理している元データを貼り付け;今回使うのはName, Old, New 列だけ)

  • テンプレの用意:Dearの後に名前、AAAにOldの値、BBBにNewの値を入れるように(最終的にスクリプトで値を置き換えたものをシート作成していく)

スクリプト処理

上図の通り、個別に値を置き換えたシートを作成し、各シートをPDF出力してドライブに保存。

簡単な書面だけど、Wordで作って複製して値を更新…とするより人為ミスもないし、速くて効率が良いと思ってやってみたら、思わぬエラーにハマりました(常)

スクリプトの作成

まずはテンプレからシート複製しての値を置き換え。これはサクッと書いて動きました。

function createSheets() {
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var lSheet = ss.getSheetByName("List");
  var tSheet = ss.getSheetByName("Template");

  var numRows = lSheet.getLastRow() - 2 + 1;
  var names = lSheet.getRange(2, 1, numRows, 2).getValues(); // Mr. + Name
  for (var i = 0 ; i < numRows ; i++) {
    var empName = names[i][0] + " " + names[i][1];
    tSheet.activate();
    var sheetNew = ss.duplicateActiveSheet();
    sheetNew.setName(empName);

    var val1 = sheetNew.getRange('A14').getValue(); // Dear
    sheetNew.getRange('A14').setValue(val1 + " " + empName + ",");

    var val2 = sheetNew.getRange('A16').getValue(); // 本文(AAA, BBB含む)
    val2 = val2.replace("AAA", lSheet.getRange(i+2,3).getValue());
    val2 = val2.replace("BBB", lSheet.getRange(i+2,5).getValue());
    sheetNew.getRange('A16').setValue(val2);
  }
};

次に、作成したシートのPDF保存。基本はこちらを参考に、for 文を加えて繰り返し処理するよう改良しました。

解説もしっかりしていて、初心者にも分かりやすいです。
が、これはあくまで一つのPDF出力をする用のスクリプトなので、普通に処理を繰り返したスクリプトを実行したら、6個PDFが作成されたところでリクエスト上限のエラーになりました。

その後、謎のエラーに見舞われたのは後述しますが、ひとまず最終的に動いたスクリプトがこちら。

function savePdf(){
  // PDFの保存先(GoogleドライブのURLで ~/folders/以降、?前までの文字列)
  let folderId = "1mAOxxxxxxxxxxxxxxxxxI_jv";
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  let ssId = ss.getId();
  var sheets = ss.getSheets();
  // List・Templateを除いたシートの数だけ回す
  for (i = 2 ; i < sheets.length ; i++){
    var sheetName = sheets[i].getSheetName();
    var fileName = "20230801 hoge document " + sheetName;
    let shId = ss.getSheetByName(sheetName).getSheetId();
    createPdf(folderId, ssId, shId, fileName);
    Utilities.sleep(i*1000); // 429エラー回避
  }
}

function createPdf(folderId, ssId, shId, fileName){
  let baseUrl = "https://docs.google.com/spreadsheets/d/"
          +  ssId
          + "/export?gid="
          + shId;
  //PDFのオプションを指定
  let pdfOptions = "&exportFormat=pdf&format=pdf"
              + "&size=A4" //用紙サイズ (A4)
              + "&portrait=true"  //用紙の向き true: 縦向き / false: 横向き
              + "&fitw=true"  //ページ幅を用紙にフィットさせるか true: フィットさせる / false: 原寸大
              + "&top_margin=0.50" //上の余白
              + "&right_margin=0.50" //右の余白
              + "&bottom_margin=0.50" //下の余白
              + "&left_margin=0.50" //左の余白
              + "&horizontal_alignment=CENTER" //水平方向の位置
              + "&vertical_alignment=TOP" //垂直方向の位置
              + "&printtitle=false" //スプレッドシート名の表示有無
              + "&sheetnames=false" //シート名の表示有無
              + "&gridlines=false" //グリッドラインの表示有無
              + "&fzr=false" //固定行の表示有無
              + "&fzc=false" //固定列の表示有無;

  let url = baseUrl + pdfOptions;
  let token = ScriptApp.getOAuthToken();
  let options = {
    'headers': {
        'Authorization': 'Bearer ' +  token
    },
    'muteHttpExceptions': true  // エラー全文確認
  };
  try {
    //PDFを作成する
    let response = UrlFetchApp.fetch(url, options);
    let file = response.getBlob();
    let blob = file.setName(fileName + '.pdf');
    //PDFを指定したフォルダに保存する
    let folder = DriveApp.getFolderById(folderId);
    folder.createFile(blob);
  } catch(e) {
    // 例外エラー処理
    Logger.log('Error:')
    Logger.log(e)
  };
  console.log(`「${fileName}.pdf」を生成しました。`);
}

429からの500エラー

さて、先ほどの通りリクエスト上限の429エラーに見舞われ6個作成されたのち、試しにもう一度実行したら同じエラーで今度は7個まで作成されました。

調べたところ、100秒あたり20,000クエリの制限があるようです。

公式でも推奨されるエクスポネンシャルバックオフの説明が載っています。とはいっても、今回限りのスクリプトなので、以下を参考に Utilities.sleep を入れ、また万が一また止まっても残り5個のPDF作成されればいいかと思い、シートの順番を入れ替えてみました。

ところが、今度は何度やっても 500内部エラー に…

どうもPDFを作成するUrlFetchApp.fetchのところでエラーになっているようで、以下を参考にmuteHttpExceptionsオプションの設定を入れてエラー全文をログ出力しましたが、内部エラーなので特に手がかりはつかめず。

(前略...)Google ドキュメント内でエラーが発生しました。このページを再読み込みするか、数分後にこのページをもう一度開いてみてください。</p><p>Google ドキュメント エディタの詳細については、<a href="https://support.google.com/docs/?hl=ja&p=error_help" target="_blank">ヘルプセンター</a>をご参照ください。</p><p><br><b>ご不便をおかけして申し訳ございません。</b><br><i>- Google Docs チーム</i></p></div></div></body><style nonce="ATJrlT4zh5xWatCFk6XwLg">html {height: 100%; overflow: auto;}body {height: 100%; overflow: auto;}#outerContainer {margin: auto; max-width: 750px;}#innerContainer {margin-bottom: 20px; margin-left: 40px; margin-right: 40px; margin-top: 80px; position: relative;}</style></html>

結局、

  • スプシのファイルそのものをコピーして実行
  • キャッシュを消してページ再読み込みもして実行

など試すも変わらなかったので、全く新しいスプシを作成、Templateシートを複製して、GASスクリプトをコピペ・実行したら、動いた!!!

結局スプシそのものに何か問題があったのかな、と試しに元のスプシをもう一度実行してみたら、、

動いた!!!

ということで、内部エラーなので実際には不明ですが、繰り返し上限を超えたドキュメントでは失敗し続けたけど、一度別のドキュメントで実行することでエラーが解消されたようでした。

おしまい

結局、エラーの原因ははっきりと究明することはできませんでした、、

が、やり方や調査の方法が少しでもどなたかの参考になれば幸いです。