投稿者: lumino-dx

  • GASでグラフ・チャートを自動生成してSlackに送信する

    「毎週の売上グラフを手動でスクショしてSlackに貼り付けている」「レポートのたびにExcelでグラフを作る作業が面倒」——そんな繰り返し作業、Google Apps Script(GAS)で完全自動化できます。この記事では、スプレッドシーのデータからグラフを自動生成し、Slack指定チャンネルに毎週自動送信する仕組みをゼロから構築する方法を解説します。

    週次レポートや月次KPI共有など、定期的なグラフ報告業務を抱えているチームに最適です。一度セットアップすれば、あとは完全放置でグラフがSlackに届きます。

    この記事でできること

    • GASでGoogleスプレッドシートのデータからグラフ(棒グラフ・折れ線グラフ)を自動生成する
    • 生成したグラフを画像(PNG)として書き出す
    • Slack Incoming Webhookを使ってグラフ画像をチャンネルに自動投稿する
    • 毎週月曜朝9時など、時間ベーストリガーで定期実行する

    事前準備(10分)

    以下を事前に用意してください。

    • Googleスプレッドシート:グラフの元データが入ったシート(週別売上、KPIなど)
    • Slack Incoming Webhook URL:Slack APIページで発行(後述)
    • Google Apps Script:スプレッドシートのメニュー「拡張機能 → Apps Script」から開く

    Slack Webhook URLの発行手順:
    ① https://api.slack.com/apps にアクセスして「Create New App」
    ② 「From scratch」を選択してアプリ名とワークスペースを設定
    ③ 「Incoming Webhooks」を有効化 → 「Add New Webhook to Workspace」
    ④ 投稿先チャンネルを選択 → 生成され�Webhook URLをコピーして保存

    STEP 1:スプレッドシートにデータとグラフを準備する

    まず、自動生成の元となるデータとグラフをスプレッドシートに用意します。サンプルとして「週別売上データ」を使います。

    スプレッドシートの「Sheet1」に以下のようなデータを入力してください:

    A列(週)    B列(売上・万円)
    第1週        120
    第2週        145
    第3週        132
    第4週        158
    第5週        170

    次に、このデータを選択してスプレッドシートのメニューから「挿入 → グラフ」でグラフを作成します。グラフの種類は棒グラフや折れ線グラフがおすすめです。グラフを作成したら、グラフオブジェクトがシート丈に配置されます。

    STEP 2:GASでグラフを画像として取得するコードを書く

    Apps Scriptエディタを開ぎ(拡張機能 → Apps Script)、以下のコードを貼り付けます。

    // GASでグラフを画像取得してSlackに送信する
    function sendChartToSlack() {
      // ===== 設定項目 =====
      const WEBHOOK_URL = PropertiesService.getScriptProperties().getProperty('SLACK_WEBHOOK_URL');
      const SHEET_NAME = 'Sheet1'; // グラフがあるシート名
      const CHART_INDEX = 0;       // シート上の何番目のグラフか(0始まり)
      
      // ===== スプレッドシートからグラフを取得 =====
      const ss = SpreadsheetApp.getActiveSpreadsheet();
      const sheet = ss.getSheetByName(SHEET_NAME);
      const charts = sheet.getCharts();
      
      if (charts.length === 0) {
        console.log('グラフが見つかりません。スプレッドシートにグラフを作成してください。');
        return;
      }
      
      const chart = charts[CHART_INDEX];
      
      // ===== グラフをPNG画像として取得 =====
      const imageBlob = chart.getAs('image/png');
      imageBlob.setName('weekly_sales_chart.png');
      
      // ===== Google DriveにびとまずアップロードしてURLを取得 =====
      const folder = DriveApp.getRootFolder();
      const file = folder.createFile(imageBlob);
      file.setSharing(DriveApp.Access.ANYONE_WITH_LINK, DriveApp.Permission.VIEW);
      const fileUrl = 'https://drive.google.com/uc?export=view&id=' + file.getId();
      
      // ===== 今週の日付を取得してメッセージを組み立てる =====
      const today = new Date();
      const dateStr = Utilities.formatDate(today, 'Asia/Tokyo', 'yyyy年MM月dd日');
      
      const payload = {
        text: '📊 *週次売上レポート(' + dateStr + '時点)*',
        attachments: [
          {
            title: '週別売上推移',
            image_url: fileUrl,
            color: '#36a64f'
          }
        ]
      };
      
      // ===== SlackのWebhookにPOSTする =====
      const options = {
        method: 'post',
        contentType: 'application/json',
        payload: JSON.stringify(payload)
      };
      
      UrlFetchApp.fetch(WEBHOOK_URL, options);
      console.log('Slackへの送信が完了しました: ' + fileUrl);
    }

    STEP 3:Webhook URLをスクリプトプロパティに安全に保存する

    Webhook URLをコードに直書きするはセキュリティ上NGです。必ずスクリプトプロパティに保存してください。

    設定手順:
    ① Apps Scriptエディタの左メニューから「プロジェクトの設定(歯車アイコン)」を開く
    ② 「スクリプト プロパティ」セクションで「プロパティを追加」をクリック
    ③ プロパティ名に SLACK_WEBHOOK_URL、値に取得し�Webhook URLを貼り付けて保存

    これで PropertiesService.getScriptProperties().getProperty('SLACK_WEBHOOK_URL') で安全に呼び出せます。

    STEP 4:動作確認

    Apps Scriptエディタで sendChartToSlack 関数を選択して「実行」ボタンをクリックします。初回実行時は権限承認ダイアログが表示されるので「許可」を選んでください。

    実行後、Slackの指定チャンネルにグラフ画像付内にメッセージが届けば成功です。

    トラブルシュートチェックリスト

    • 「グラフが見つかりません」と表示される:シート名(SHEET_NAME)が正しか確認。スプレッドシートにグラフオブジェクトが存在するか確認
    • Slackにメッセージが届かない:SLACK_WEBHOOK_URLが正しく設定されているか確認。Webhook URLの末尾にスペースや改行が入っついないか確認
    • 画像がSlackに表示されない:DriveファイルのURL共有設定が「リンクを知っついる共㎯」になっついか確認。アタッチメントのimage_urlが正しいか確認
    • 権限エラーが出る:DriveAppやUrlFetchApp、SpreadsheetAppの権限を承認しているか確認

    STEP 5:毎週自動実行するトリガーを設定する

    手動実行だすしなき、毎週月曜日の朝9時など定期実行させましょう。

    // トリガーを自動設定するセットアップ関数(一度だ�e実行する)
    function setupWeeklyTrigger() {
      // 既存のトリガーを削除(重複防止)
      const triggers = ScriptApp.getProjectTriggers();
      triggers.forEach(trigger => {
        if (trigger.getHandlerFunction() === 'sendChartToSlack') {
          ScriptApp.deleteTrigger(trigger);
        }
      });
      
      // 毎週月曜日の午前9〜10時に実行するトリガーを設定
      ScriptApp.newTrigger('sendChartToSlack')
        .timeBased()
        .onWeekDay(ScriptApp.WeekDay.MONDAY)
        .atHour(9)
        .create();
        
      console.log('週次トリガーを設定しました(毎週月曜 9〜10時)');
    }

    setupWeeklyTrigger 関数を一度ださ実行すれば、毎週月曜朝にグラフが自動でSlackに届くようになります。

    応用:さらに便利にする拡張アイデア

    • 複数グラフをまとめて送信:charts配列をループして複数グラフをまとめて1だのSlackメッセージに添付することができます
    • Google Driveに蓄積せず直接送信:Slack Files Upload APIを使うと、DriveにファイルをアップせずGASから直接画像をSlackにアップロードできます(より抲本櫙どe��装向す)
    • グラフをGASかり動的に生成する:EmbeddedChartBuilderを使えば、シート上に配置済みのグラフを使わず、毎回最新データでグラフを動的に生成することも可能です
    • チャンネル別に送り�&ける:売上グラフは #sales-report、KPIグラフは #management-report など、用途別に複数Webhook URLを使い分けることができます
    • スプレッドシートURLも丁緒に送る:Slackメッセージに元のスプレッドシートへのリンクを添付すると、詳細確認もスムーズになります

    まとめ

    GASを使えば、スプレッドシートのグラフを自動生成してSlackに送信する仕組みを30分以内に構築できます。週次レポートや月次KPI共有など、繰り返しの報告業務を完全自動化することで、作業時間の削減と報告漏れーロを実現できます。

    • GASのgetAs(‘image/png’)メソッドでグラフを画像として取得できる
    • Slack Incoming Webhookと組み合わせることでグラフ自動投稿が実現できる
    • Webhook URLはスクリプトプロパティで安全に管理する
    • 時間ベーストリガーで毎週・毎月の定期実行が可能
    • 応用次第で複数グラフの一括送信や動的グラフ生成にも発展できる

    「どのグラフを自動化すればいいか分からない」「社内のSlack通知フローを設計したい」とう方は、お気軽にご相談ください。

    お問い合わせはこちら
  • GASでQRコードを自動生成する方法

    GASでQRコードを自動生成する方法

    「QRコードを大量に作りたいけど、1つずつ手作業で生成するのは時間がかかる…」と感じていませんか?Google Apps Script(GAS)とGoogle Charts APIを組み合わせれば、スプレッドシートのURLリストからQRコードを自動で一括生成し、Googleドライブへの保存まで完全自動化できます。

    この記事では、プログラミング未経験者でもコピペで動かせるコードを使って、QRコード自動生成の仕組みをゼロから構築する方法を解説します。名刺・チラシ・社内資料など、QRコードが必要なあらゆる場面で活用できます。

    この記事でできること

    • スプレッドシートのURLリストからQRコードを一括自動生成
    • 生成したQRコード画像をGoogleドライブに自動保存
    • スプレッドシートのセルにQRコード画像のURLを自動記録
    • ボタン1つでQRコードを生成するカスタムメニューの作成

    事前準備(5分)

    以下のものを準備してください。特別なツールや有料サービスへの登録は不要です。

    • Googleアカウント(無料)
    • Googleスプレッドシート:QRコードにしたいURLを入力するシート
    • Googleドライブ:QRコード画像の保存先として自動的に使用されます

    Google Charts APIはGoogleが提供する無料のAPI(追加設定不要)で、URLをパラメータに渡すだけでQRコード画像を返してくれます。

    STEP 1:スプレッドシートを準備する

    まず、QRコードにしたいURLのリストをスプレッドシートに璨意します。

    Googleスプレッドシートを新規作成し、以下のように入力してください。シート名は「QRリスト」に設定しておきましょう(後のコードと対応します)。

    • A列(ラベル):QRコードの用途・名称(例:会社HP、採用ページ、商品詳細)
    • B列(URL):QRコードにしたいURL(例:https://example.com/product)
    • C列(ドライブURL):自動生成後にQRコードのドライブURLが記録されます(空白のままでOK)

    1行目はヘッダー行として「ラベル」「URL」「QRコードURL」と入力し、2行目以降に実際のデータを入力します。

    STEP 2:GASスクリプトを作成する

    スプレッドシートのメニューから「拡張機能」→「Apps Script」を開き、以下のコードをエディタに貼り付けてください。既存のコードは削除してから貼り付けます。

    function generateQRCodes() {
      const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('QRリスト');
      const lastRow = sheet.getLastRow();
    
      if (lastRow < 2) {
        SpreadsheetApp.getUi().alert('データが入力されていません。B列にURLを入力してください。');
        return;
      }
    
      // Googleドライブにフォルダを作成(初回のみ)
      let folder;
      const folders = DriveApp.getFoldersByName('QRコード自動生成');
      if (folders.hasNext()) {
        folder = folders.next();
      } else {
        folder = DriveApp.createFolder('QRコード自動生成');
      }
    
      let successCount = 0;
    
      // ヘッダー行をスキップして2行目から処理
      for (let i = 2; i <= lastRow; i++) {
        const label = sheet.getRange(i, 1).getValue(); // A列:ラベル
        const url = sheet.getRange(i, 2).getValue();   // B列:URL
    
        if (!url) continue; // URLが空の場合はスキップ
    
        try {
          // Google Charts APIでQRコード画像を取得
          const qrApiUrl = 'https://chart.googleapis.com/chart'
            + '?chs=300x300'
            + '&cht=qr'
            + '&chl=' + encodeURIComponent(url)
            + '&choe=UTF-8';
    
          const response = UrlFetchApp.fetch(qrApiUrl);
          const imageBlob = response.getBlob().setName('qr_' + label + '_' + i + '.png');
    
          // Googleドライブに保存
          const file = folder.createFile(imageBlob);
    
          // ファイルを全員が閲覧できるように設定
          file.setSharing(DriveApp.Access.ANYONE_WITH_LINK, DriveApp.Permission.VIEW);
    
          // C列にドライブURLを記録
          sheet.getRange(i, 3).setValue(file.getUrl());
    
          successCount++;
          Logger.log('生成完了: ' + label + ' / ' + url);
    
          // API制限対策:0.5秒待機
          Utilities.sleep(500);
    
        } catch (e) {
          Logger.log('エラー(行' + i + '): ' + e.message);
          sheet.getRange(i, 3).setValue('エラー: ' + e.message);
        }
      }
    
      SpreadsheetApp.getUi().alert(successCount + '件のQRコードを生成しました!\nドライブの「QRコード自動生成」フォルダをご確認ください。');
    }

    コードを貼り付けたら、画面上部の「保存」ボタン(フロッピーディスクアイコン)をクリックして保存します。

    STEP 3:カスタムメニューを追加する

    毎回Apps Scriptを開かずにスプレッドシートから直接QRコードを生成できるよう、カスタムメニューを追加します。以下のコードを先ほどのコードの下に追記してください。

    // スプレッドシートを開いたときにカスタムメニューを追加
    function onOpen() {
      SpreadsheetApp.getUi()
        .createMenu('QRコード生成')
        .addItem('QRコードを生成する', 'generateQRCodes')
        .addItem('使い方を確認', 'showHelp')
        .addToUi();
    }
    
    // 使い方ガイドを表示
    function showHelp() {
      const message =
        '【使い方】\n' +
        '1. A列にラベル(名称)を入力\n' +
        '2. B列にQRコードにしたいURLを入力\n' +
        '3. メニュー「QRコード生成」→「QRコードを生成する」をクリック\n' +
        '4. C列にQRコードのドライブURLが自動記録されます\n\n' +
        '生成されたQRコードはドライブの\n' +
        '「QRコード自動生成」フォルダに保存されます。';
      SpreadsheetApp.getUi().alert(message);
    }

    2つの関数を保存したら、スプレッドシートをリロードしてください。上部のメニューバーに「QRコード生成」という項目が追加されます。

    STEP 4:動作確認

    実際にQRコードを生成してみましょう。スプレッドシートのA列にラベル、B列にURLを入力し、メニューから「QRコード生成」→「QRコードを生成する」をクリックします。

    初回実行時はGoogleからアクセス権限の確認ダイアログが表示されます。「権限を確認」→Googleアカウントを選択→「許可」をクリックして進んでください。

    トラブルシュートチェックリスト

    • 「シートが見つかりません」エラー:スプレッドシートのシート名が「QRリスト」になっているか確認してください。スクリプト内の'QRリスト'の部分を実際のシート名に変更するか、シート名を「QRリスト」に統一します。
    • 「Error: 429」や「リクエストが多すぎます」:Google Charts APIのレート制限に達しました。Utilities.sleep(500)の数値を1000〜2000に増やして再実行してください。
    • QRコードが読み取れない:B列のURLが正しい形式か確認してください。「https://」から始まる完全なURLを入力する必要があります。スペースや全角文字が混入していないかも確認しましょう。

    応用:さらに便利にする拡張アイデア

    • QRコードのサイズを変更する:`?chs=300x300` の数値を変更することでサイズを調整できます。名刺用なら100x100、ポスター用なら500x500など、用途に合わせて変えましょう。
    • 時間トリガーで定期更新:商品ページやキャンペーンURLのQRコードを毎週月曜日に自動再生成するよう、Apps Scriptのトリガー設定で定期実行できます。
    • QRコードをGoogleスライドに自動挿入:生成したQRコード画像をGoogleスライドのプレゼン資料に自動で貼り付けることも可能。SlidesApp.insertImage()を使って展開できます。

    まとめ

    GASとGoogle Charts APIを組み合わせることで、QRコードの一括自動生成が無料・手軽に実現できます。手作業での生成にかかる時間を大幅に短縮し、URLの変更があっても一括で再生成できるのが大きなメリットです。

    • Google Charts APIはGoogleアカウントがあれば無料で使用可能・API登録不要
    • スプレッドシートのURLリストから数十件のQRコードも数分で自動生成できる
    • 生成したQRコードはGoogleドライブに自動保存され、リンクで管理できる
    • カスタムメニューを設定すればApps Script不要でボタン1つで実行できる

    QRコード生成の自動化以外にも、GASを使ったさまざまな業務効率化について無料でご相談いただけます。お気軽にお問い合わせください。

    お問い合わせはこちら
  • GASでWebスクレイピングをして競合情報を自動収集する

    GASでWebスクレイピングをして競合情報を自動収集する

    「競合サイトの価格・ニュース・求人情報を毎日チェックするのが手間すぎる」「情報収集に時間を取られて本業に集中できない」——そんな悩みを、Google Apps Script(GAS)のWebスクレイピング機能で一気に解決します。本記事では、GASだけで競合サイトの情報を自動収集し、スプレッドシートに蓄積する仕組みを、コードをコピペするだけで作れるよう丁寧に解説します。

    GASには外部URLにHTTPリクエストを送るUrlFetchAppと、HTMLを解析するParserライブラリが備わっています。これらを組み合わせれば、プログラミング経験が浅い方でも本格的なスクレイピング自動化が実現できます。

    この記事でできること

    • GASのUrlFetchAppで任意のWebページのHTMLを取得する
    • 正規表現を使って必要な情報(価格・タイトル・日付など)を抽出する
    • 収集したデータをGoogleスプレッドシートに自動で記録する
    • 毎日・毎時などのタイマートリガーで完全自動化する

    事前準備(5分)

    必要なものは以下だけです。費用はすべて無料です。

    • Googleアカウント
    • Googleスプレッドシート(新規作成でOK)
    • 収集対象のURL(競合サイト・ニュースサイトなど)

    重要な注意点:スクレイピングを行う前に、対象サイトの利用規約とrobots.txtを必ず確認してください。スクレイピングを禁止しているサイトへの実施は規約違反になる場合があります。また、短時間に大量のリクエストを送るのは避け、クロール間隔は最低でも1時間以上空けるよう設計します。

    STEP 1:スプレッドシートとGASエディタを準備する

    まず情報を記録するスプレッドシートを用意します。

    • Googleドライブで新しいスプレッドシートを作成する
    • シート名を「競合情報」に変更する
    • 1行目に見出しを入力する:A1=「取得日時」、B1=「タイトル」、C1=「価格/内容」、D1=「URL」
    • メニューの「拡張機能」→「Apps Script」をクリックしてGASエディタを開く

    STEP 2:HTMLを取得して情報を抽出するコードを書く

    GASエディタのコードをすべて削除して、以下のコードを貼り付けます。このサンプルでは、架空の競合ECサイトの商品名と価格を取得する想定で構成しています。実際の対象サイトのHTML構造に合わせてセレクタ(正規表現)を変更してください。

    // ===== 設定エリア(ここだけ編集する)=====
    const TARGET_URLS = [
      "https://example-competitor.com/products/",  // 競合サイトURL1
      "https://example-news.com/category/tech/",   // ニュースサイトURL
    ];
    
    // スプレッドシートID(URLの /d/ と /edit の間の文字列)
    const SPREADSHEET_ID = "ここにスプレッドシートIDを入力";
    const SHEET_NAME = "競合情報";
    // =========================================
    
    function scrapeCompetitorInfo() {
      const sheet = SpreadsheetApp
        .openById(SPREADSHEET_ID)
        .getSheetByName(SHEET_NAME);
    
      TARGET_URLS.forEach(url => {
        try {
          // HTMLを取得(最大待機5秒に設定)
          const response = UrlFetchApp.fetch(url, {
            muteHttpExceptions: true,
            followRedirects: true,
            headers: {
              "User-Agent": "Mozilla/5.0 (compatible; GAS-Bot/1.0)"
            }
          });
    
          // HTTPステータスチェック
          if (response.getResponseCode() !== 200) {
            Logger.log(`取得失敗: ${url} (ステータス: ${response.getResponseCode()})`);
            return;
          }
    
          const html = response.getContentText("UTF-8");
    
          // --- 抽出ロジック(対象サイトのHTML構造に合わせて変更)---
          // 例1:タグの内容を抽出
          const titleMatch = html.match(/<title[^>]*>([^<]+)<\/title>/i);
          const pageTitle = titleMatch ? titleMatch[1].trim() : "取得不可";
    
          // 例2:特定クラスの価格を抽出(例:<span class="price">¥1,980</span>)
          const priceMatch = html.match(/<span[^>]*class="[^"]*price[^"]*"[^>]*>([^<]+)<\/span>/i);
          const price = priceMatch ? priceMatch[1].trim() : "価格情報なし";
    
          // スプレッドシートに記録
          const now = new Date();
          sheet.appendRow([
            Utilities.formatDate(now, "Asia/Tokyo", "yyyy/MM/dd HH:mm:ss"),
            pageTitle,
            price,
            url
          ]);
    
          Logger.log(`記録完了: ${pageTitle} / ${price}`);
    
          // サーバー負荷軽減のため2秒待機
          Utilities.sleep(2000);
    
        } catch (e) {
          Logger.log(`エラー発生 (${url}): ${e.message}`);
        }
      });
    
      Logger.log("全URL処理完了");
    }</code></pre>
    
    
    
    <p>コードを貼り付けたら、<code>SPREADSHEET_ID</code>をご自身のスプレッドシートのIDに書き換えてください。IDはスプレッドシートのURLの<code>/d/</code>と<code>/edit</code>の間にある長い文字列です。</p>
    
    
    
    <h2 class="wp-block-heading">STEP 3:正規表現を対象サイトに合わせてカスタマイズする</h2>
    
    
    
    <p>スクレイピングで最も重要なのが「どこから何を取り出すか」の指定です。Chromeの開発者ツール(F12)でHTMLを確認しながら正規表現を調整します。</p>
    
    
    
    <p>よく使うパターンをいくつか紹介します:</p>
    
    
    
    <pre class="wp-block-code"><code>// パターン1:特定のIDを持つ要素の内容を取得
    // 例:<div id="product-name">商品名テキスト</div>
    const idMatch = html.match(/<div[^>]*id="product-name"[^>]*>([\s\S]*?)<\/div>/i);
    const productName = idMatch ? idMatch[1].replace(/<[^>]+>/g, "").trim() : "";
    
    // パターン2:メタタグからOGPタイトルを取得(多くのサイトで有効)
    const ogTitleMatch = html.match(/<meta[^>]*property="og:title"[^>]*content="([^"]+)"/i);
    const ogTitle = ogTitleMatch ? ogTitleMatch[1] : "";
    
    // パターン3:特定テキストの後に続く数値を取得(価格など)
    // 例:「価格:12,800円」という形式
    const numMatch = html.match(/価格[::]\s*([\d,]+)円/);
    const price = numMatch ? numMatch[1] + "円" : "";
    
    // パターン4:リスト形式で複数件取得(最新ニュース一覧など)
    // 例:<h2 class="entry-title"><a href="...">記事タイトル</a></h2>
    const newsMatches = [...html.matchAll(/<h2[^>]*class="[^"]*entry-title[^"]*"[^>]*>[\s\S]*?<a[^>]*>([^<]+)<\/a>/gi)];
    const newsTitles = newsMatches.map(m => m[1].trim());</code></pre>
    
    
    
    <p>対象サイトのHTML構造を確認する手順:Chrome で対象ページを開く → F12キーで開発者ツール起動 → 「Elements」タブで取りたい情報のHTMLを確認 → 周辺のタグやクラス名を正規表現に組み込む、という流れです。</p>
    
    
    
    <h2 class="wp-block-heading">STEP 4:タイマートリガーで毎日自動実行する</h2>
    
    
    
    <p>手動実行で動作確認ができたら、タイマートリガーを設定して完全自動化します。</p>
    
    
    
    <ul class="wp-block-list"><li>GASエディタ左側の時計アイコン(トリガー)をクリック</li><li>右下の「トリガーを追加」をクリック</li><li>実行する関数:<code>scrapeCompetitorInfo</code></li><li>イベントのソース:「時間主導型」</li><li>時間ベースのトリガーのタイプ:「日付ベースのタイマー」</li><li>時刻:「午前8時〜9時」(毎朝出社前に収集完了するよう設定)</li><li>「保存」をクリック</li></ul>
    
    
    
    <p>これで毎朝8時台に自動でスクレイピングが実行され、スプレッドシートに最新情報が蓄積されていきます。</p>
    
    
    
    <h2 class="wp-block-heading">STEP 5:動作確認とトラブルシュート</h2>
    
    
    
    <p>エディタ上部の「実行」ボタンをクリックして動作を確認します。初回実行時はGoogleアカウントへのアクセス許可を求めるダイアログが表示されるので「許可」をクリックしてください。</p>
    
    
    
    <p><strong>トラブルシュートチェックリスト</strong></p>
    
    
    
    <ul class="wp-block-list"><li>「Exception: Address unavailable」エラー → 対象URLが存在するか・GASからアクセス可能なURLかを確認。一部の企業サイトはBot判定でブロックすることがある</li><li>スプレッドシートに何も記録されない → <code>SPREADSHEET_ID</code>が正しいか、シート名が「競合情報」と一致しているかを確認</li><li>文字化けする → <code>getContentText("UTF-8")</code>の文字コードをサイトに合わせて変更(”Shift_JIS”など)</li><li>価格や情報が「取得不可」になる → 開発者ツールでHTMLを再確認し、正規表現のパターンを修正する</li><li>「Exception: Quota exceeded」→ GASの1日の実行回数上限(無料版:90分/日)に達している。取得頻度を下げるか、実行間隔を広げる</li></ul>
    
    
    
    <h2 class="wp-block-heading">応用:さらに便利にする拡張アイデア</h2>
    
    
    
    <ul class="wp-block-list"><li><strong>差分検知アラート</strong>:前回取得データと比較して変化があった場合のみGmailやSlackで通知する(価格変動・新着記事の検知に最適)</li><li><strong>複数サイト一括管理</strong>:TARGET_URLSに対象URLを追加するだけで複数サイトを横断収集。スプレッドシートに「サイト名」列を追加して一元管理する</li><li><strong>GeminiAPIで要約・分析</strong>:収集したテキストをGemini APIに渡して「競合の最新動向まとめ」を自動生成し、毎朝メールで受け取る仕組みに発展させる</li><li><strong>グラフ自動生成</strong>:価格データを蓄積してスプレッドシートのグラフ機能で推移グラフを自動更新し、競合価格戦略の分析に活用する</li></ul>
    
    
    
    <h2 class="wp-block-heading">まとめ</h2>
    
    
    
    <p>GASのUrlFetchAppと正規表現を組み合わせることで、競合サイトの情報収集を完全自動化できます。毎日30分かかっていた情報収集作業がゼロになり、その時間を分析・戦略立案に充てられます。</p>
    
    
    
    <ul class="wp-block-list"><li>UrlFetchAppでHTTPリクエストを送り、サイトのHTMLを取得する</li><li>正規表現でタイトル・価格・日付などの必要情報を抽出し、スプレッドシートに記録する</li><li>タイマートリガーで毎日自動実行し、人手ゼロで情報収集を継続する</li><li>差分検知・AI要約と組み合わせることで「競合監視ダッシュボード」へと発展させられる</li></ul>
    
    
    
    <p>スクレイピングの対象サイト選定、正規表現のカスタマイズ、Slack通知との連携など、さらに突っ込んだ自動化の仕組み構築についてはお気軽にご相談ください。</p>
    
    
    
    <figure class="wp-block-image size-full"><a href="https://dx-jitsumu-labo.com/contact/"><img decoding="async" src="https://dx-jitsumu-labo.com/wp-content/uploads/2026/04/contact-cta-banner.png" alt="お問い合わせはこちら" class="wp-image-33"/></a></figure>
    
    </div>
    			<div style="margin-top:var(--wp--preset--spacing--40);" class="wp-block-post-date has-small-font-size"><time datetime="2026-04-22T21:50:42+09:00"><a href="https://dx-jitsumu-labo.com/gas-web-scraping/">2026年4月22日</a></time></div>
    		</div>
    		
    	</li><li class="wp-block-post post-372 post type-post status-publish format-standard has-post-thumbnail hentry category-gas-automation">
    		
    		<div class="wp-block-group alignfull has-global-padding is-layout-constrained wp-block-group-is-layout-constrained" style="padding-top:var(--wp--preset--spacing--60);padding-bottom:var(--wp--preset--spacing--60)">
    			<figure style="aspect-ratio:3/2;" class="wp-block-post-featured-image"><a href="https://dx-jitsumu-labo.com/gas-youtube-data-api/" target="_self"  ><img width="1280" height="720" src="https://dx-jitsumu-labo.com/wp-content/uploads/2026/04/eyecatch-article16-gas-youtube-data-api.png" class="attachment-post-thumbnail size-post-thumbnail wp-post-image" alt="GASでYouTube Data APIを使って動画情報を自動取得する" style="width:100%;height:100%;object-fit:cover;" decoding="async" srcset="https://dx-jitsumu-labo.com/wp-content/uploads/2026/04/eyecatch-article16-gas-youtube-data-api.png 1280w, https://dx-jitsumu-labo.com/wp-content/uploads/2026/04/eyecatch-article16-gas-youtube-data-api-300x169.png 300w, https://dx-jitsumu-labo.com/wp-content/uploads/2026/04/eyecatch-article16-gas-youtube-data-api-1024x576.png 1024w, https://dx-jitsumu-labo.com/wp-content/uploads/2026/04/eyecatch-article16-gas-youtube-data-api-768x432.png 768w" sizes="(max-width: 1280px) 100vw, 1280px" /></a></figure>
    			<h2 class="wp-block-post-title has-x-large-font-size"><a href="https://dx-jitsumu-labo.com/gas-youtube-data-api/" target="_self" >GASでYouTube Data APIを使って動画情報を自動取得する</a></h2>
    			<div class="entry-content alignfull wp-block-post-content has-medium-font-size has-global-padding is-layout-constrained wp-block-post-content-is-layout-constrained">
    <p class="article-lead">「自社のYouTubeチャンネルの視聴回数やチャンネル登録者数を毎日手動でメモしている」「競合チャンネルの動画本数や再生数を調査するのに時間がかかる」——そんな担当者に向けて、Google Apps Script(GAS)とYouTube Data APIを組み合わせて、動画情報を自動取得・スプレッドシートへ記録する仕組みを作ります。</p>
    
    
    
    <p>YouTube Data APIはGoogleが公式提供している無料APIです。GASと組み合わせることで、プログラミング未経験者でも比較的短時間でYouTube情報の自動収集ツールが完成します。本記事ではAPIキー取得から実際に動くスクリプト作成・自動実行まで一気通貫で解説します。</p>
    
    
    
    <h2 class="wp-block-heading">この記事でできること</h2>
    
    
    
    <ul class="wp-block-list"><li>YouTube Data APIキーの取得とGASへの設定</li><li>指定チャンネルの最新動画一覧(タイトル・再生回数・いいね数・投稿日時)をスプレッドシートに自動記録</li><li>複数チャンネルの競合調査データを毎日自動収集</li><li>毎朝9時に自動実行するトリガー設定</li></ul>
    
    
    
    <h2 class="wp-block-heading">事前準備(15分)</h2>
    
    
    
    <p>作業を始める前に以下を用意してください。</p>
    
    
    
    <ul class="wp-block-list"><li>Googleアカウント(Gmail)</li><li>Google Cloud Consoleへのアクセス権限</li><li>調査対象のYouTubeチャンネルID(後述の手順で確認できます)</li></ul>
    
    
    
    <p><strong>チャンネルIDの調べ方:</strong>YouTubeでチャンネルページを開き、URLに含まれる「@ハンドル名」か「UC〜」から始まる文字列がチャンネルIDです。例:<code>https://www.youtube.com/@channelname</code>の場合は、チャンネルページ右クリック→「ページのソースを表示」で<code>channel_id</code>を検索すると「UC〜」形式のIDが確認できます。</p>
    
    
    
    <h2 class="wp-block-heading">STEP 1:Google Cloud ConsoleでYouTube Data API v3を有効化してAPIキーを取得する</h2>
    
    
    
    <p>GASからYouTube APIを呼び出すには、まずGoogle Cloud側でAPIキーを発行する必要があります。</p>
    
    
    
    <ul class="wp-block-list"><li><strong>1-1.</strong> Google Cloud Consoleにアクセスし、プロジェクトを作成(または既存プロジェクトを選択)</li><li><strong>1-2.</strong> 左メニュー「APIとサービス」→「ライブラリ」→検索欄に「YouTube Data API v3」と入力→「有効にする」をクリック</li><li><strong>1-3.</strong> 「APIとサービス」→「認証情報」→「認証情報を作成」→「APIキー」を選択</li><li><strong>1-4.</strong> 生成されたAPIキーをコピーして控えておく(例:<code>AIzaSy〜〜〜〜〜〜〜〜〜〜</code>)</li></ul>
    
    
    
    <p><strong>セキュリティ上の注意:</strong>APIキーはソースコードに直接書き込まず、GASのスクリプトプロパティ(設定ファイル)に保存します。これにより、誰かにコードを見られてもキーが漏洩しません。</p>
    
    
    
    <h2 class="wp-block-heading">STEP 2:スプレッドシートとGASプロジェクトを作成する</h2>
    
    
    
    <p>データを記録するスプレッドシートを作成し、GASエディタを開きます。</p>
    
    
    
    <ul class="wp-block-list"><li><strong>2-1.</strong> Googleドライブで新規スプレッドシートを作成し、シート名を「動画データ」に変更</li><li><strong>2-2.</strong> A1〜H1にヘッダーを入力:<code>取得日時</code>/<code>チャンネル名</code>/<code>動画タイトル</code>/<code>動画ID</code>/<code>投稿日時</code>/<code>再生回数</code>/<code>いいね数</code>/<code>コメント数</code></li><li><strong>2-3.</strong> スプレッドシートのメニュー「拡張機能」→「Apps Script」を開く</li><li><strong>2-4.</strong> 左メニューの「プロジェクトの設定」(歯車アイコン)→「スクリプトプロパティ」→「プロパティを追加」をクリック</li><li><strong>2-5.</strong> プロパティ名:<code>YOUTUBE_API_KEY</code>、値:STEP 1で取得したAPIキー を入力して保存</li></ul>
    
    
    
    <h2 class="wp-block-heading">STEP 3:GASスクリプトを作成する</h2>
    
    
    
    <p>GASエディタの<code>コード.gs</code>に以下のコードを貼り付けます。</p>
    
    
    
    <pre class="wp-block-code"><code>// YouTube Data APIで動画情報を自動取得してスプレッドシートに記録するスクリプト
    
    const CHANNEL_IDS = [
      'UCxxxxxxxxxxxxxxxxxxxxxxxx',  // 調査したいチャンネルID1
      'UCyyyyyyyyyyyyyyyyyyyyyy',   // 調査したいチャンネルID2
    ];
    const MAX_RESULTS = 10; // 1チャンネルあたり取得する動画数
    
    function fetchYouTubeData() {
      const apiKey = PropertiesService.getScriptProperties().getProperty('YOUTUBE_API_KEY');
      const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('動画データ');
      const now = new Date();
      const timestamp = Utilities.formatDate(now, 'Asia/Tokyo', 'yyyy/MM/dd HH:mm');
    
      CHANNEL_IDS.forEach(channelId => {
        // チャンネル情報を取得
        const channelUrl = `https://www.googleapis.com/youtube/v3/channels?part=snippet&id=${channelId}&key=${apiKey}`;
        const channelRes = UrlFetchApp.fetch(channelUrl);
        const channelData = JSON.parse(channelRes.getContentText());
        
        if (!channelData.items || channelData.items.length === 0) {
          console.log(`チャンネルが見つかりません: ${channelId}`);
          return;
        }
        const channelName = channelData.items[0].snippet.title;
    
        // 最新動画一覧を取得
        const searchUrl = `https://www.googleapis.com/youtube/v3/search?part=snippet&channelId=${channelId}&maxResults=${MAX_RESULTS}&order=date&type=video&key=${apiKey}`;
        const searchRes = UrlFetchApp.fetch(searchUrl);
        const searchData = JSON.parse(searchRes.getContentText());
    
        if (!searchData.items) return;
    
        // 動画IDリストで統計情報を一括取得
        const videoIds = searchData.items.map(item => item.id.videoId).join(',');
        const statsUrl = `https://www.googleapis.com/youtube/v3/videos?part=statistics,snippet&id=${videoIds}&key=${apiKey}`;
        const statsRes = UrlFetchApp.fetch(statsUrl);
        const statsData = JSON.parse(statsRes.getContentText());
    
        statsData.items.forEach(video => {
          const title = video.snippet.title;
          const videoId = video.id;
          const publishedAt = Utilities.formatDate(new Date(video.snippet.publishedAt), 'Asia/Tokyo', 'yyyy/MM/dd HH:mm');
          const viewCount = video.statistics.viewCount || '0';
          const likeCount = video.statistics.likeCount || '0';
          const commentCount = video.statistics.commentCount || '0';
    
          sheet.appendRow([
            timestamp,
            channelName,
            title,
            videoId,
            publishedAt,
            parseInt(viewCount),
            parseInt(likeCount),
            parseInt(commentCount)
          ]);
        });
        
        console.log(`${channelName}の動画${statsData.items.length}件を記録しました`);
      });
    }
    
    // 【おまけ】チャンネル登録者数・総再生回数もシートに記録する関数
    function fetchChannelStats() {
      const apiKey = PropertiesService.getScriptProperties().getProperty('YOUTUBE_API_KEY');
      const ss = SpreadsheetApp.getActiveSpreadsheet();
      let statsSheet = ss.getSheetByName('チャンネル統計');
      
      if (!statsSheet) {
        statsSheet = ss.insertSheet('チャンネル統計');
        statsSheet.appendRow(['取得日時', 'チャンネル名', 'チャンネルID', '登録者数', '総動画数', '総再生回数']);
      }
      
      const now = new Date();
      const timestamp = Utilities.formatDate(now, 'Asia/Tokyo', 'yyyy/MM/dd HH:mm');
    
      CHANNEL_IDS.forEach(channelId => {
        const url = `https://www.googleapis.com/youtube/v3/channels?part=snippet,statistics&id=${channelId}&key=${apiKey}`;
        const res = UrlFetchApp.fetch(url);
        const data = JSON.parse(res.getContentText());
        
        if (!data.items || data.items.length === 0) return;
        
        const item = data.items[0];
        statsSheet.appendRow([
          timestamp,
          item.snippet.title,
          channelId,
          parseInt(item.statistics.subscriberCount || 0),
          parseInt(item.statistics.videoCount || 0),
          parseInt(item.statistics.viewCount || 0)
        ]);
      });
    }</code></pre>
    
    
    
    <p><strong>コードの解説:</strong></p>
    
    
    
    <ul class="wp-block-list"><li><code>CHANNEL_IDS</code>:調査したいチャンネルIDの配列。複数設定可能</li><li><code>UrlFetchApp.fetch()</code>:GASからHTTP通信でAPIを呼び出すメソッド</li><li><code>PropertiesService.getScriptProperties()</code>:スクリプトプロパティからAPIキーを安全に読み込む</li><li><code>sheet.appendRow()</code>:スプレッドシートの末尾に1行追加する</li></ul>
    
    
    
    <h2 class="wp-block-heading">STEP 4:動作確認と毎日自動実行するトリガーを設定する</h2>
    
    
    
    <p><strong>動作テスト</strong></p>
    
    
    
    <ul class="wp-block-list"><li>GASエディタ上部の関数選択ドロップダウンで「fetchYouTubeData」を選択</li><li>▶ ボタン(実行)をクリック</li><li>初回は「承認が必要です」というポップアップが出るので「権限を確認」→Googleアカウントで許可</li><li>スプレッドシート「動画データ」シートにデータが追記されたら成功</li></ul>
    
    
    
    <p><strong>トリガー設定(毎朝9時に自動実行)</strong></p>
    
    
    
    <ul class="wp-block-list"><li>GASエディタ左メニュー「トリガー」(時計アイコン)→「トリガーを追加」</li><li>実行する関数:<code>fetchYouTubeData</code></li><li>イベントのソース:<code>時間主導型</code></li><li>時間ベースのトリガーのタイプ:<code>日付ベースのタイマー</code></li><li>時刻:<code>午前9時〜10時</code>→「保存」</li></ul>
    
    
    
    <p><strong>トラブルシュートチェックリスト</strong></p>
    
    
    
    <ul class="wp-block-list"><li>❌ <code>Exception: Request failed for...returned code 403</code>→ APIキーが正しくコピーされていない、またはYouTube Data API v3が有効化されていない。Cloud Consoleで確認を</li><li>❌ <code>チャンネルが見つかりません</code>→ チャンネルIDが間違っている。「UC〜」から始まるID形式かハンドル名(@なし)で再確認</li><li>❌ <code>TypeError: Cannot read property...of null</code>→ API割り当て上限(無料枠:1日10,000ユニット)に達している可能性。翌日リトライするか、Google Cloud Consoleでクォータを確認</li><li>❌ スプレッドシートに書き込まれない→ シート名が「動画データ」と完全一致しているか確認(全角スペース等に注意)</li></ul>
    
    
    
    <h2 class="wp-block-heading">応用:さらに便利にする拡張アイデア</h2>
    
    
    
    <ul class="wp-block-list"><li><strong>Slack通知と組み合わせる</strong>:特定動画の再生回数が前日比10%以上伸びたらSlackに自動通知。急上昇コンテンツをいち早く把握できる</li><li><strong>競合チャンネルの週次レポートをGmailで自動送信</strong>:毎週月曜に先週の競合データをHTML形式のメールで自動送信。会議資料作成が不要になる</li><li><strong>Googleデータポータル(Looker Studio)でダッシュボード化</strong>:蓄積したスプレッドシートデータをLooker Studioに接続し、再生数推移グラフを自動更新するダッシュボードを作れる</li><li><strong>キーワード検索で競合動画を自動収集</strong>:searchエンドポイントのqパラメータに検索ワードを設定すると、競合他社の動画を横断調査できる</li></ul>
    
    
    
    <h2 class="wp-block-heading">まとめ</h2>
    
    
    
    <p>GASとYouTube Data API v3を組み合わせることで、面倒な手動調査を完全自動化できます。一度セットアップすれば毎朝最新のデータが自動でスプレッドシートに蓄積されるため、コンテンツ戦略の意思決定スピードが格段に上がります。</p>
    
    
    
    <ul class="wp-block-list"><li>APIキーはスクリプトプロパティに保存し、セキュリティを確保する</li><li>1回の実行で複数チャンネルを一括調査でき、競合分析にも活用できる</li><li>トリガーを設定すれば毎朝自動実行され、手間ゼロでデータが蓄積される</li><li>Slack通知やLooker Studioと組み合わせることで、さらに実用的なツールに発展できる</li></ul>
    
    
    
    <p>「自社のチャンネルに加えて競合チャンネルも一緒に監視したい」「データを分析してレポートまで自動化したい」など、もう一歩進んだ仕組みを作りたい場合はお気軽にご相談ください。</p>
    
    
    
    <figure class="wp-block-image size-full"><a href="https://dx-jitsumu-labo.com/contact/"><img decoding="async" src="https://dx-jitsumu-labo.com/wp-content/uploads/2026/04/contact-cta-banner.png" alt="お問い合わせはこちら" class="wp-image-33"/></a></figure>
    </div>
    			<div style="margin-top:var(--wp--preset--spacing--40);" class="wp-block-post-date has-small-font-size"><time datetime="2026-04-22T17:11:54+09:00"><a href="https://dx-jitsumu-labo.com/gas-youtube-data-api/">2026年4月22日</a></time></div>
    		</div>
    		
    	</li><li class="wp-block-post post-368 post type-post status-publish format-standard has-post-thumbnail hentry category-gas-automation">
    		
    		<div class="wp-block-group alignfull has-global-padding is-layout-constrained wp-block-group-is-layout-constrained" style="padding-top:var(--wp--preset--spacing--60);padding-bottom:var(--wp--preset--spacing--60)">
    			<figure style="aspect-ratio:3/2;" class="wp-block-post-featured-image"><a href="https://dx-jitsumu-labo.com/gas-holiday-detection/" target="_self"  ><img width="1280" height="720" src="https://dx-jitsumu-labo.com/wp-content/uploads/2026/04/eyecatch-article15-gas-holiday-detection.png" class="attachment-post-thumbnail size-post-thumbnail wp-post-image" alt="GASで祝日・休日を自動判定する【Googleカレンダー活用】" style="width:100%;height:100%;object-fit:cover;" decoding="async" loading="lazy" srcset="https://dx-jitsumu-labo.com/wp-content/uploads/2026/04/eyecatch-article15-gas-holiday-detection.png 1280w, https://dx-jitsumu-labo.com/wp-content/uploads/2026/04/eyecatch-article15-gas-holiday-detection-300x169.png 300w, https://dx-jitsumu-labo.com/wp-content/uploads/2026/04/eyecatch-article15-gas-holiday-detection-1024x576.png 1024w, https://dx-jitsumu-labo.com/wp-content/uploads/2026/04/eyecatch-article15-gas-holiday-detection-768x432.png 768w" sizes="auto, (max-width: 1280px) 100vw, 1280px" /></a></figure>
    			<h2 class="wp-block-post-title has-x-large-font-size"><a href="https://dx-jitsumu-labo.com/gas-holiday-detection/" target="_self" >GASで祝日・休日を自動判定する【Googleカレンダー活用】</a></h2>
    			<div class="entry-content alignfull wp-block-post-content has-medium-font-size has-global-padding is-layout-constrained wp-block-post-content-is-layout-constrained">
    <p class="article-lead">「今日が祝日かどうか、GASで自動的に判定できないか?」——業務自動化スクリプトを組んでいると、必ずぶつかる壁がこれです。この記事では、Googleカレンダーの日本の祝日カレンダーとGASを組み合わせて、祝日・休日を完全自動判定する方法を解説します。</p>
    
    
    
    <p>「休日は処理をスキップしたい」「営業日だけメールを送りたい」という要件は業務自動化でよく出てきます。手動でカレンダーを管理するのは現実的ではありません。Googleカレンダーには公式の「日本の祝日」カレンダーが存在するため、これをGASから参照することで、祝日判定を完全に自動化できます。</p>
    
    
    
    <h2 class="wp-block-heading">この記事でできること</h2>
    
    
    
    <ul class="wp-block-list"><li>指定した日付が祝日・土日・休日かどうかをGASで自動判定する</li><li>Googleカレンダーの「日本の祝日」データをAPIで取得する</li><li>営業日のみスクリプトを実行するトリガー制御を実装する</li><li>年末年始・会社独自の休日にも対応した拡張方法を理解する</li></ul>
    
    
    
    <h2 class="wp-block-heading">事前準備(5分)</h2>
    
    
    
    <p>必要なものは以下の3つだけです。特別なAPIキーや権限申請は不要で、Googleアカウントがあればすぐに始められます。</p>
    
    
    
    <ul class="wp-block-list"><li><strong>Googleアカウント</strong>(GAS実行用)</li><li><strong>Google Apps Script</strong>(スプレッドシートまたはスタンドアロンプロジェクト)</li><li><strong>CalendarApp権限</strong>(スクリプト初回実行時に自動で許可を求められます)</li></ul>
    
    
    
    <p>Googleカレンダーの「日本の祝日」カレンダーのIDは <code>ja.japanese#holiday@group.v.calendar.google.com</code> です。このIDをそのままGASコードに記述すれば、外部APIへのアクセス申請なしに祝日データを取得できます。</p>
    
    
    
    <h2 class="wp-block-heading">STEP 1:祝日判定の基本関数を作る</h2>
    
    
    
    <p>まずはGASプロジェクトを開いて(スプレッドシートのメニュー「拡張機能」→「Apps Script」)、以下の関数を貼り付けます。この関数は日付を受け取り、その日が「祝日かどうか」をtrue/falseで返します。</p>
    
    
    
    <pre class="wp-block-code"><code>/**
     * 指定した日付が日本の祝日かどうかを判定する
     * @param {Date} date - 判定したい日付オブジェクト
     * @return {boolean} 祝日なら true
     */
    function isJapaneseHoliday(date) {
      const calendarId = 'ja.japanese#holiday@group.v.calendar.google.com';
      const calendar = CalendarApp.getCalendarById(calendarId);
      
      // 指定日の00:00〜23:59のイベントを取得
      const startOfDay = new Date(date);
      startOfDay.setHours(0, 0, 0, 0);
      const endOfDay = new Date(date);
      endOfDay.setHours(23, 59, 59, 999);
      
      const events = calendar.getEvents(startOfDay, endOfDay);
      return events.length > 0;
    }
    
    // テスト実行用
    function testHolidayCheck() {
      const testDates = [
        new Date('2026-01-01'), // 元日(祝日)
        new Date('2026-01-02'), // 平日
        new Date('2026-03-20'), // 春分の日(祝日)
      ];
      
      testDates.forEach(date => {
        const result = isJapaneseHoliday(date);
        console.log(`${date.toLocaleDateString('ja-JP')} : ${result ? '祝日' : '祝日ではない'}`);
      });
    }</code></pre>
    
    
    
    <p><code>testHolidayCheck</code> 関数を実行すると、ログに「元日: 祝日」「1月2日: 祝日ではない」などと表示されます。初回実行時はカレンダーへのアクセス許可を求めるダイアログが出るので「許可」をクリックしてください。</p>
    
    
    
    <h2 class="wp-block-heading">STEP 2:「営業日かどうか」を判定する関数を作る</h2>
    
    
    
    <p>実務では「祝日」だけでなく「土曜・日曜」も除外した「営業日かどうか」を判定したいケースがほとんどです。STEP 1の関数を組み合わせて、土日祝を一括で判定する関数を作ります。</p>
    
    
    
    <pre class="wp-block-code"><code>/**
     * 指定した日付が営業日(平日かつ祝日でない)かどうかを判定する
     * @param {Date} date - 判定したい日付オブジェクト
     * @return {boolean} 営業日なら true
     */
    function isBusinessDay(date) {
      const dayOfWeek = date.getDay(); // 0=日, 1=月, ..., 6=土
      
      // 土日チェック
      if (dayOfWeek === 0 || dayOfWeek === 6) {
        return false;
      }
      
      // 祝日チェック
      if (isJapaneseHoliday(date)) {
        return false;
      }
      
      return true;
    }
    
    /**
     * 本日が営業日かどうかをチェックしてログ出力
     */
    function checkTodayIsBusinessDay() {
      const today = new Date();
      const result = isBusinessDay(today);
      
      if (result) {
        console.log('本日は営業日です。処理を実行します。');
      } else {
        console.log('本日は休業日です。処理をスキップします。');
      }
      
      return result;
    }
    
    /**
     * 今月の営業日をすべてリストアップする
     */
    function listBusinessDaysThisMonth() {
      const today = new Date();
      const year = today.getFullYear();
      const month = today.getMonth();
      const lastDay = new Date(year, month + 1, 0).getDate();
      
      const businessDays = [];
      for (let d = 1; d <= lastDay; d++) {
        const date = new Date(year, month, d);
        if (isBusinessDay(date)) {
          businessDays.push(date.toLocaleDateString('ja-JP'));
        }
      }
      
      console.log(`今月の営業日(${businessDays.length}日):`);
      console.log(businessDays.join('
    '));
      return businessDays;
    }</code></pre>
    
    
    
    <h2 class="wp-block-heading">STEP 3:自動化スクリプトに営業日チェックを組み込む</h2>
    
    
    
    <p>毎朝9時に実行する日次レポート送信スクリプトを例に、営業日チェックをどう組み込むか説明します。GASのトリガーは「毎日9時」で設定し、スクリプト内で営業日判定をすることで「平日のみ実行」を実現します。</p>
    
    
    
    <pre class="wp-block-code"><code>/**
     * 毎朝9時にトリガーで実行する日次レポート送信関数
     * トリガー設定: 時間ベース → 日タイマー → 午前9時〜10時
     */
    function sendDailyReportIfBusinessDay() {
      const today = new Date();
      
      // 営業日でなければスキップ
      if (!isBusinessDay(today)) {
        console.log(`${today.toLocaleDateString('ja-JP')} は休業日のためスキップ`);
        return;
      }
      
      // ===== ここから通常の業務処理 =====
      const ss = SpreadsheetApp.getActiveSpreadsheet();
      const sheet = ss.getSheetByName('日次データ');
      
      // スプレッドシートから昨日のデータを取得(例)
      const lastRow = sheet.getLastRow();
      const data = sheet.getRange(lastRow, 1, 1, 5).getValues()[0];
      
      const subject = `【日次レポート】${today.toLocaleDateString('ja-JP')}`;
      const body = `
    本日の日次レポートです。
    
    売上: ${data[1].toLocaleString()}円
    件数: ${data[2]}件
    目標達成率: ${data[3]}%
    
    詳細はスプレッドシートをご確認ください。
      `.trim();
      
      GmailApp.sendEmail(
        'manager@example.com',
        subject,
        body
      );
      
      console.log(`レポートを送信しました: ${subject}`);
    }
    
    /**
     * 月末営業日にのみ実行する月次集計
     * トリガー設定: 時間ベース → 日タイマー → 午後6時〜7時
     */
    function runMonthlyReportOnLastBusinessDay() {
      const today = new Date();
      
      // 今日が営業日でなければスキップ
      if (!isBusinessDay(today)) return;
      
      // 明日が同じ月の営業日であればスキップ(月末営業日でない)
      const tomorrow = new Date(today);
      tomorrow.setDate(today.getDate() + 1);
      
      // 翌日も同月かつ営業日ならまだ月末でない
      if (tomorrow.getMonth() === today.getMonth() && isBusinessDay(tomorrow)) {
        console.log('まだ月末営業日ではありません。');
        return;
      }
      
      console.log('月末営業日です。月次集計を実行します。');
      // ここに月次集計処理を記述
    }</code></pre>
    
    
    
    <h2 class="wp-block-heading">STEP 4:動作確認とトラブルシュート</h2>
    
    
    
    <p>コードを貼り付けたら、まず <code>testHolidayCheck</code> 関数をGASエディタから手動実行して動作を確認しましょう。ログは「表示」→「ログ」またはCtrl+Enter(Mac: Cmd+Enter)で確認できます。</p>
    
    
    
    <p><strong>トラブルシュートチェックリスト</strong></p>
    
    
    
    <ul class="wp-block-list"><li><strong>「カレンダーが見つからない」エラー</strong>:CalendarAppの権限が付与されていない可能性があります。スクリプトを初回実行したとき「承認が必要です」と表示されたら、必ず「許可」をクリックしてください。</li><li><strong>祝日が正しく取得できない</strong>:カレンダーIDのスペルを確認してください。正しいIDは <code>ja.japanese#holiday@group.v.calendar.google.com</code> です。</li><li><strong>振替休日が取得できない</strong>:Googleカレンダーの日本の祝日カレンダーには振替休日も含まれています。別途対応は不要です。</li><li><strong>処理が遅い</strong>:<code>getCalendarById</code> や <code>getEvents</code> はAPI呼び出しのため1〜2秒かかります。月全体の営業日を一括チェックする場合、APIコール数が多くなります。1ヶ月分のイベントを一度に取得してキャッシュする最適化を検討してください(後述の応用参照)。</li></ul>
    
    
    
    <h2 class="wp-block-heading">応用:さらに便利にする拡張アイデア</h2>
    
    
    
    <ul class="wp-block-list"><li><strong>会社独自の休日を追加する</strong>:Googleカレンダーに社内向けの「会社カレンダー」を作成し、年末年始や創立記念日などを登録しておく。GAS側では複数のカレンダーIDに対してイベント取得を行い、いずれかにイベントがあれば休日と判定するよう拡張できます。</li><li><strong>翌営業日・前営業日を自動計算する</strong>:「明日が休日なので今日中にリマインドを送りたい」という要件に対応するため、「次の営業日」を返す関数を作っておくと便利です。ループで翌日〜翌々日と検索し、最初にisBusinessDayがtrueになった日付を返す設計になります。</li><li><strong>Slack通知と組み合わせる</strong>:月末営業日検知と組み合わせてSlackに「月次締め処理のリマインド」を自動投稿する仕組みを作ることで、うっかり忘れを防止できます。GASのSlack通知連携については別記事で詳しく解説しています。</li></ul>
    
    
    
    <h2 class="wp-block-heading">まとめ</h2>
    
    
    
    <p>GASとGoogleカレンダーを組み合わせることで、日本の祝日判定を外部サービスや手動管理なしに完全自動化できます。一度このコードを組み込んでおけば、毎年の祝日変更にも自動で対応してくれるため、メンテナンスコストがほぼゼロになるのが最大のメリットです。</p>
    
    
    
    <ul class="wp-block-list"><li>Googleカレンダーの祝日カレンダーID(<code>ja.japanese#holiday@group.v.calendar.google.com</code>)をGASから直接参照することで、APIキー不要で祝日データを取得できる</li><li><code>isBusinessDay()</code> 関数で土日+祝日を一括判定し、業務処理の先頭に置くだけで「営業日限定実行」が実現できる</li><li>会社独自カレンダーを追加することで年末年始や特別休暇にも対応できる</li><li>月末営業日の自動検知とSlack通知を組み合わせると、月次締め処理のリマインド自動化が実現できる</li></ul>
    
    
    
    <p>「このコードを自社の業務に合わせてカスタマイズしたい」「もっと複雑な営業日ロジックが必要」という場合は、お気軽にお問い合わせください。</p>
    
    
    
    <figure class="wp-block-image size-full"><a href="https://dx-jitsumu-labo.com/contact/"><img decoding="async" src="https://dx-jitsumu-labo.com/wp-content/uploads/2026/04/contact-cta-banner.png" alt="お問い合わせはこちら" class="wp-image-33"/></a></figure>
    
    </div>
    			<div style="margin-top:var(--wp--preset--spacing--40);" class="wp-block-post-date has-small-font-size"><time datetime="2026-04-20T07:34:57+09:00"><a href="https://dx-jitsumu-labo.com/gas-holiday-detection/">2026年4月20日</a></time></div>
    		</div>
    		
    	</li><li class="wp-block-post post-363 post type-post status-publish format-standard has-post-thumbnail hentry category-gas-automation">
    		
    		<div class="wp-block-group alignfull has-global-padding is-layout-constrained wp-block-group-is-layout-constrained" style="padding-top:var(--wp--preset--spacing--60);padding-bottom:var(--wp--preset--spacing--60)">
    			<figure style="aspect-ratio:3/2;" class="wp-block-post-featured-image"><a href="https://dx-jitsumu-labo.com/gas-spreadsheet-notion-sync/" target="_self"  ><img width="1200" height="675" src="https://dx-jitsumu-labo.com/wp-content/uploads/2026/04/eyecatch-article14-gas-spreadsheet-notion-sync.png" class="attachment-post-thumbnail size-post-thumbnail wp-post-image" alt="GASでスプレッドシートのデータをNotionに自動同期する方法" style="width:100%;height:100%;object-fit:cover;" decoding="async" loading="lazy" srcset="https://dx-jitsumu-labo.com/wp-content/uploads/2026/04/eyecatch-article14-gas-spreadsheet-notion-sync.png 1200w, https://dx-jitsumu-labo.com/wp-content/uploads/2026/04/eyecatch-article14-gas-spreadsheet-notion-sync-300x169.png 300w, https://dx-jitsumu-labo.com/wp-content/uploads/2026/04/eyecatch-article14-gas-spreadsheet-notion-sync-1024x576.png 1024w, https://dx-jitsumu-labo.com/wp-content/uploads/2026/04/eyecatch-article14-gas-spreadsheet-notion-sync-768x432.png 768w" sizes="auto, (max-width: 1200px) 100vw, 1200px" /></a></figure>
    			<h2 class="wp-block-post-title has-x-large-font-size"><a href="https://dx-jitsumu-labo.com/gas-spreadsheet-notion-sync/" target="_self" >GASでスプレッドシートのデータをNotionに自動同期する方法</a></h2>
    			<div class="entry-content alignfull wp-block-post-content has-medium-font-size has-global-padding is-layout-constrained wp-block-post-content-is-layout-constrained">
    <p class="article-lead">「スプレッドシートのデータをNotionに手動でコピーしているが、入力漏れやミスが多い…」そんな悩みを抱える方は多いはずです。この記事では、Google Apps Script(GAS)とNotion APIを使って、スプレッドシートのデータをNotionデータベースへ自動同期する仕組みを、コードつきで徹底解説します。</p>
    
    
    
    <p>Notionは多機能なプロジェクト管理ツールですが、既存のスプレッドシートとのデータ連携は手動になりがちです。GASを使えば、毎日決まった時間にスプレッドシートの最新データをNotionに自動で同期させることができます。プログラミング経験が浅くても、コードをコピーして設定するだけで動かせるよう丁寧に説明します。</p>
    
    
    
    <h2 class="wp-block-heading">この記事でできること</h2>
    
    
    
    <ul class="wp-block-list"><li>スプレッドシートの行データをNotionデータベースのページとして自動追加</li><li>差分検知で重複なく新規行のみを同期</li><li>毎朝9時に自動実行するトリガー設定</li><li>同期済みフラグをスプレッドシートに自動書き込み</li></ul>
    
    
    
    <h2 class="wp-block-heading">事前準備(15分)</h2>
    
    
    
    <p>以下のものを事前に用意してください。</p>
    
    
    
    <ul class="wp-block-list"><li><strong>Googleアカウント</strong>(スプレッドシートが使えるもの)</li><li><strong>Notionアカウント</strong>(無料プランでOK)</li><li><strong>Notion APIキー</strong>:Notionの「設定とメンバー」→「APIと連携」→「新しいインテグレーションを作成」から取得</li><li><strong>NotionデータベースID</strong>:同期先のNotionデータベースURLに含まれる32文字のID</li></ul>
    
    
    
    <p>NotionデータベースのURLは <code>https://www.notion.so/xxxxxxxxxx?v=yyyyyy</code> の形式です。<code>xxxxxxxxxx</code>部分(ハイフンなし32文字)がデータベースIDです。また、作成したインテグレーションをデータベースに「接続」する必要があります(データベース右上「…」→「接続先を追加」)。</p>
    
    
    
    <h2 class="wp-block-heading">STEP 1:スプレッドシートの構成を確認する</h2>
    
    
    
    <p>今回は以下のような営業リスト(シート名:「リスト」)を例にします。A列〜D列にデータが入り、E列を「同期済みフラグ」として使います。</p>
    
    
    
    <figure class="wp-block-table"><table><thead><tr><th>A列:会社名</th><th>B列:担当者</th><th>C列:ステータス</th><th>D列:次回アクション日</th><th>E列:同期済み</th></tr></thead><tbody><tr><td>株式会社〇〇</td><td>田中太郎</td><td>商談中</td><td>2026/04/25</td><td>(空白)</td></tr></tbody></table></figure>
    
    
    
    <p>NotionデータベースにはA〜D列に対応するプロパティを作成してください。プロパティ名は日本語でもOKです(コードで指定します)。</p>
    
    
    
    <h2 class="wp-block-heading">STEP 2:GASスクリプトを作成する</h2>
    
    
    
    <p>スプレッドシートを開き、「拡張機能」→「Apps Script」を選択してスクリプトエディタを開きます。デフォルトのコードを削除し、以下のコードを貼り付けてください。</p>
    
    
    
    <pre class="wp-block-code"><code>// ===== 設定値 =====
    const NOTION_API_KEY = PropertiesService.getScriptProperties().getProperty('NOTION_API_KEY');
    const DATABASE_ID = PropertiesService.getScriptProperties().getProperty('DATABASE_ID');
    const SHEET_NAME = 'リスト';
    const SYNCED_COLUMN = 5; // E列(1始まり)
    const DATA_START_ROW = 2; // ヘッダー行の次から
    
    function syncToNotion() {
      const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(SHEET_NAME);
      const lastRow = sheet.getLastRow();
      
      if (lastRow < DATA_START_ROW) {
        console.log('データがありません');
        return;
      }
      
      const dataRange = sheet.getRange(DATA_START_ROW, 1, lastRow - DATA_START_ROW + 1, 5);
      const values = dataRange.getValues();
      
      let syncCount = 0;
      
      values.forEach((row, index) => {
        const [companyName, contactPerson, status, nextActionDate, synced] = row;
        
        // 同期済みの行はスキップ
        if (synced === '済') return;
        // 会社名が空の行はスキップ
        if (!companyName) return;
        
        const notionPage = createNotionPage(companyName, contactPerson, status, nextActionDate);
        
        if (notionPage) {
          // 同期済みフラグを書き込む
          sheet.getRange(DATA_START_ROW + index, SYNCED_COLUMN).setValue('済');
          syncCount++;
          Utilities.sleep(300); // API制限対策
        }
      });
      
      console.log(`同期完了: ${syncCount}件`);
    }
    
    function createNotionPage(companyName, contactPerson, status, nextActionDate) {
      const url = 'https://api.notion.com/v1/pages';
      
      // 日付のフォーマット(スプレッドシートのDate型をISO文字列に変換)
      let dateStr = '';
      if (nextActionDate instanceof Date) {
        dateStr = Utilities.formatDate(nextActionDate, 'Asia/Tokyo', 'yyyy-MM-dd');
      } else if (nextActionDate) {
        dateStr = String(nextActionDate);
      }
      
      const payload = {
        parent: { database_id: DATABASE_ID },
        properties: {
          '会社名': {
            title: [{ text: { content: String(companyName) } }]
          },
          '担当者': {
            rich_text: [{ text: { content: String(contactPerson || '') } }]
          },
          'ステータス': {
            select: { name: String(status || '') }
          },
          '次回アクション日': {
            date: dateStr ? { start: dateStr } : null
          }
        }
      };
      
      const options = {
        method: 'POST',
        headers: {
          'Authorization': 'Bearer ' + NOTION_API_KEY,
          'Content-Type': 'application/json',
          'Notion-Version': '2022-06-28'
        },
        payload: JSON.stringify(payload),
        muteHttpExceptions: true
      };
      
      const response = UrlFetchApp.fetch(url, options);
      const responseCode = response.getResponseCode();
      const responseBody = JSON.parse(response.getContentText());
      
      if (responseCode === 200) {
        console.log(`追加成功: ${companyName}`);
        return responseBody;
      } else {
        console.error(`エラー (${responseCode}): ${companyName} - ${JSON.stringify(responseBody)}`);
        return null;
      }
    }</code></pre>
    
    
    
    <h2 class="wp-block-heading">STEP 3:APIキーとデータベースIDをスクリプトプロパティに設定する</h2>
    
    
    
    <p>APIキーなどの秘密情報はコードに直書きせず、スクリプトプロパティを使って安全に管理します。スクリプトエディタの左メニューから「プロジェクトの設定」→「スクリプト プロパティ」→「プロパティを追加」で以下を設定してください。</p>
    
    
    
    <ul class="wp-block-list"><li><strong>NOTION_API_KEY</strong>:Notionインテグレーションのシークレットキー(<code>secret_xxx...</code>の形式)</li><li><strong>DATABASE_ID</strong>:同期先NotionデータベースID(32文字の英数字)</li></ul>
    
    
    
    <p>設定後、スクリプトエディタ上部の関数名を「syncToNotion」に選択して▶ボタンをクリックし、初回の認証許可を行ってください。</p>
    
    
    
    <h2 class="wp-block-heading">STEP 4:動作確認</h2>
    
    
    
    <p>スクリプトを手動実行し、Notionデータベースに行が追加されることを確認します。</p>
    
    
    
    <p><strong>トラブルシュートチェックリスト</strong></p>
    
    
    
    <ul class="wp-block-list"><li><strong>「401 Unauthorized」エラー</strong>:NOTION_API_KEYが正しく設定されているか確認。スクリプトプロパティに保存したか再確認する</li><li><strong>「object not found」エラー</strong>:DATABASE_IDが間違っている、またはインテグレーションがデータベースに接続されていない。Notionのデータベース右上「…」→「接続先を追加」でインテグレーションを追加する</li><li><strong>プロパティ名エラー</strong>:Notionデータベースのプロパティ名とコード内の文字列が完全一致していることを確認(全角・半角・スペースの差異に注意)</li><li><strong>日付が入らない</strong>:スプレッドシートの日付セルの形式を確認。テキストではなく日付型になっているか確認する</li></ul>
    
    
    
    <h2 class="wp-block-heading">定期実行トリガーを設定する</h2>
    
    
    
    <p>毎朝9時に自動実行するには、スクリプトエディタ左メニューの時計アイコン「トリガー」をクリックし、以下の設定でトリガーを追加します。</p>
    
    
    
    <ul class="wp-block-list"><li>実行する関数:<code>syncToNotion</code></li><li>イベントのソース:時間主導型</li><li>時間ベースのトリガーのタイプ:日タイマー</li><li>時刻:午前9時〜10時</li></ul>
    
    
    
    <p>設定後は「保存」を押すだけで、翌朝から自動同期が始まります。スプレッドシートに新しい行を追加するだけで、翌朝Notionに自動反映される運用が完成します。</p>
    
    
    
    <h2 class="wp-block-heading">応用:さらに便利にする拡張アイデア</h2>
    
    
    
    <ul class="wp-block-list"><li><strong>双方向同期</strong>:Notionのページ更新をWebhookで受け取り、GASでスプレッドシートに書き戻す(Notion Webhookは現在β機能)</li><li><strong>ステータス変更時のみ同期</strong>:E列のフラグをステータス値と組み合わせ、「商談中→成約」に変わった行だけを別のNotionデータベースに追加する</li><li><strong>Slack通知との組み合わせ</strong>:syncToNotionの完了後にSlack通知を送り、同期件数を自動報告する仕組みにする</li><li><strong>複数シートの一括同期</strong>:シート名を配列で管理し、複数のスプレッドシートを1つのGASスクリプトでまとめて同期する</li></ul>
    
    
    
    <h2 class="wp-block-heading">まとめ</h2>
    
    
    
    <p>GASとNotion APIを組み合わせることで、スプレッドシートからNotionへのデータ自動同期が実現できます。手動コピーのミスや手間をゼロにして、常に最新データをNotionで管理できる環境を構築しましょう。</p>
    
    
    
    <ul class="wp-block-list"><li>Notion APIキーとデータベースIDをスクリプトプロパティに安全保管する</li><li>E列の「同期済みフラグ」で重複同期を防止する</li><li>時間トリガーで毎朝自動実行することで完全な自動化が完成する</li><li>Slack通知や複数シート対応などの拡張で、さらに実用的な仕組みに育てられる</li></ul>
    
    
    
    <p>GASとNotion APIの連携設定でお困りの点があれば、お気軽にお問い合わせください。御社の業務フローに合わせたカスタマイズも承っています。</p>
    
    
    
    <figure class="wp-block-image size-full"><a href="https://dx-jitsumu-labo.com/contact/"><img loading="lazy" decoding="async" width="2560" height="488" src="https://dx-jitsumu-labo.com/wp-content/uploads/2026/04/CTAバナー(820×160px).jpg" alt="お問い合わせはこちら" class="wp-image-33" srcset="https://dx-jitsumu-labo.com/wp-content/uploads/2026/04/CTAバナー(820×160px).jpg 2560w, https://dx-jitsumu-labo.com/wp-content/uploads/2026/04/CTAバナー(820×160px)-300x57.jpg 300w, https://dx-jitsumu-labo.com/wp-content/uploads/2026/04/CTAバナー(820×160px)-1024x195.jpg 1024w, https://dx-jitsumu-labo.com/wp-content/uploads/2026/04/CTAバナー(820×160px)-768x146.jpg 768w, https://dx-jitsumu-labo.com/wp-content/uploads/2026/04/CTAバナー(820×160px)-1536x293.jpg 1536w, https://dx-jitsumu-labo.com/wp-content/uploads/2026/04/CTAバナー(820×160px)-2048x390.jpg 2048w" sizes="auto, (max-width: 2560px) 100vw, 2560px" /></a></figure>
    </div>
    			<div style="margin-top:var(--wp--preset--spacing--40);" class="wp-block-post-date has-small-font-size"><time datetime="2026-04-19T15:38:30+09:00"><a href="https://dx-jitsumu-labo.com/gas-spreadsheet-notion-sync/">2026年4月19日</a></time></div>
    		</div>
    		
    	</li><li class="wp-block-post post-359 post type-post status-publish format-standard has-post-thumbnail hentry category-gas-automation">
    		
    		<div class="wp-block-group alignfull has-global-padding is-layout-constrained wp-block-group-is-layout-constrained" style="padding-top:var(--wp--preset--spacing--60);padding-bottom:var(--wp--preset--spacing--60)">
    			<figure style="aspect-ratio:3/2;" class="wp-block-post-featured-image"><a href="https://dx-jitsumu-labo.com/gas-invoice-pdf-gmail/" target="_self"  ><img width="1200" height="675" src="https://dx-jitsumu-labo.com/wp-content/uploads/2026/04/eyecatch-article13-gas-invoice-pdf-gmail.jpg" class="attachment-post-thumbnail size-post-thumbnail wp-post-image" alt="GASで請求書PDFを自動生成してGmailで送付する仕組み" style="width:100%;height:100%;object-fit:cover;" decoding="async" loading="lazy" srcset="https://dx-jitsumu-labo.com/wp-content/uploads/2026/04/eyecatch-article13-gas-invoice-pdf-gmail.jpg 1200w, https://dx-jitsumu-labo.com/wp-content/uploads/2026/04/eyecatch-article13-gas-invoice-pdf-gmail-300x169.jpg 300w, https://dx-jitsumu-labo.com/wp-content/uploads/2026/04/eyecatch-article13-gas-invoice-pdf-gmail-1024x576.jpg 1024w, https://dx-jitsumu-labo.com/wp-content/uploads/2026/04/eyecatch-article13-gas-invoice-pdf-gmail-768x432.jpg 768w" sizes="auto, (max-width: 1200px) 100vw, 1200px" /></a></figure>
    			<h2 class="wp-block-post-title has-x-large-font-size"><a href="https://dx-jitsumu-labo.com/gas-invoice-pdf-gmail/" target="_self" >GASで請求書PDFを自動生成してGmailで送付する仕組み</a></h2>
    			<div class="entry-content alignfull wp-block-post-content has-medium-font-size has-global-padding is-layout-constrained wp-block-post-content-is-layout-constrained">
    <p class="article-lead">「毎月の請求書作成に時間がかかりすぎる」「送り忘れが発生する」「ファイルの管理が煩雑」——こんな悩みをお持ちではないでしょうか?この記事では、Google Apps Script(GAS)を使って、スプレッドシートのデータから請求書PDFを自動生成し、Gmailで自動送付する仕組みを作る方法を解説します。</p>
    
    
    
    <p>一度仕組みを作ってしまえば、毎月の請求書送付がボタン1つで完了します。プログラミング未経験でも、この記事のコードをそのままコピーして使えるよう丁寧に解説します。</p>
    
    
    
    <h2 class="wp-block-heading">この記事でできること</h2>
    
    
    
    <ul class="wp-block-list"><li>スプレッドシートに入力した顧客情報・金額から請求書PDFを自動生成</li><li>生成したPDFをGoogleドライブに自動保存</li><li>顧客のメールアドレスに請求書PDFを添付して自動送信</li><li>毎月の定期実行を時間ベーストリガーで自動化</li></ul>
    
    
    
    <h2 class="wp-block-heading">事前準備(10分)</h2>
    
    
    
    <p>以下を用意してください。</p>
    
    
    
    <ul class="wp-block-list"><li><strong>Googleアカウント</strong>(無料で使える)</li><li><strong>請求書テンプレートのGoogleスプレッドシート</strong>(この記事で作成します)</li><li><strong>顧客データ管理用スプレッドシート</strong>(この記事で作成します)</li></ul>
    
    
    
    <p>特別なツールやソフトウェアは不要です。すべてGoogleのサービスだけで完結します。</p>
    
    
    
    <h2 class="wp-block-heading">STEP 1:請求書テンプレートをGoogleスプレッドシートで作成する</h2>
    
    
    
    <p>まず請求書のテンプレートとなるスプレッドシートを作成します。Googleドライブで新しいスプレッドシートを作成し、以下のように設定してください。</p>
    
    
    
    <p><strong>シート名:「請求書テンプレート」</strong></p>
    
    
    
    <ul class="wp-block-list"><li>B1セル:「御請求書」(タイトル)</li><li>B3セル:「請求先:」、C3セル:顧客名を後でGASが入力(空白のまま)</li><li>B4セル:「請求日:」、C4セル:日付を後でGASが入力(空白のまま)</li><li>B5セル:「支払期限:」、C5セル:期限を後でGASが入力(空白のまま)</li><li>B7セル:「品目」、C7セル:「数量」、D7セル:「単価」、E7セル:「金額」</li><li>B8セル:品目名(空白)、C8〜E8:数量・単価・金額(空白)</li><li>E10セル:「合計:」、F10セル:合計金額(空白)</li><li>B12セル:振込先情報(銀行名・口座番号など)</li></ul>
    
    
    
    <p>次に、顧客データ管理用の別シートを作成します。</p>
    
    
    
    <p><strong>シート名:「顧客データ」</strong></p>
    
    
    
    <ul class="wp-block-list"><li>A列:顧客名</li><li>B列:メールアドレス</li><li>C列:品目名</li><li>D列:数量</li><li>E列:単価</li><li>F列:送付済みフラグ(空白→送付後に「済」と記入)</li></ul>
    
    
    
    <p>スプレッドシートのIDをメモしておいてください。URLの「/spreadsheets/d/」と「/edit」の間にある文字列がIDです。</p>
    
    
    
    <h2 class="wp-block-heading">STEP 2:GASスクリプトを作成する</h2>
    
    
    
    <p>スプレッドシートのメニューから「拡張機能」→「Apps Script」を開き、以下のコードを貼り付けます。</p>
    
    
    
    <pre class="wp-block-code"><code>// 設定値(ご自身の環境に合わせて変更してください)
    const SPREADSHEET_ID = 'YOUR_SPREADSHEET_ID'; // スプレッドシートのID
    const TEMPLATE_SHEET = '請求書テンプレート';
    const DATA_SHEET = '顧客データ';
    const SAVE_FOLDER_ID = 'YOUR_FOLDER_ID'; // 保存先Googleドライブフォルダのフォルダ ID
    
    function sendInvoices() {
      const ss = SpreadsheetApp.openById(SPREADSHEET_ID);
      const dataSheet = ss.getSheetByName(DATA_SHEET);
      const lastRow = dataSheet.getLastRow();
    
      for (let i = 2; i <= lastRow; i++) {
        const row = dataSheet.getRange(i, 1, 1, 6).getValues()[0];
        const customerName = row[0];
        const email = row[1];
        const itemName = row[2];
        const quantity = row[3];
        const unitPrice = row[4];
        const status = row[5];
    
        // 送付済みの行はスキップ
        if (status === '済') continue;
    
        // テンプレートをコピーしてPDF生成
        const pdfFile = createInvoicePdf(ss, customerName, itemName, quantity, unitPrice);
    
        // メール送信
        GmailApp.sendEmail(
          email,
          `【御請求書】${customerName} 様`,
          `${customerName} 様\n\nいつもお世話になっております。\n今月分の御請求書をPDFにてお送りいたします。\nご確認のほどよろしくお願いいたします。\n\n株式会社〇〇\n担当:〇〇`,
          {
            attachments: [pdfFile.getAs(MimeType.PDF)],
            name: '株式会社〇〇'
          }
        );
    
        // 送付済みフラグを更新
        dataSheet.getRange(i, 6).setValue('済');
    
        // API制限を避けるため少し待機
        Utilities.sleep(1000);
      }
    
      Logger.log('請求書送付が完了しました');
    }
    
    function createInvoicePdf(ss, customerName, itemName, quantity, unitPrice) {
      // テンプレートシートをコピー
      const templateSheet = ss.getSheetByName(TEMPLATE_SHEET);
      const newSheet = templateSheet.copyTo(ss);
      newSheet.setName('請求書_一時');
    
      // データを入力
      const today = new Date();
      const invoiceDate = Utilities.formatDate(today, 'Asia/Tokyo', 'yyyy年MM月dd日');
      const dueDate = new Date(today.getFullYear(), today.getMonth() + 1, 25);
      const dueDateStr = Utilities.formatDate(dueDate, 'Asia/Tokyo', 'yyyy年MM月dd日');
      const totalAmount = quantity * unitPrice;
    
      newSheet.getRange('C3').setValue(customerName + ' 御中');
      newSheet.getRange('C4').setValue(invoiceDate);
      newSheet.getRange('C5').setValue(dueDateStr);
      newSheet.getRange('B8').setValue(itemName);
      newSheet.getRange('C8').setValue(quantity);
      newSheet.getRange('D8').setValue(unitPrice);
      newSheet.getRange('E8').setValue(totalAmount);
      newSheet.getRange('F10').setValue(totalAmount);
    
      // SpreadsheetApp.flush()で変更を確定
      SpreadsheetApp.flush();
    
      // PDFとして書き出し
      const pdfName = `請求書_${customerName}_${Utilities.formatDate(today, 'Asia/Tokyo', 'yyyyMM')}.pdf`;
      const url = `https://docs.google.com/spreadsheets/d/${ss.getId()}/export?format=pdf&gid=${newSheet.getSheetId()}&portrait=true&fitw=true`;
      const token = ScriptApp.getOAuthToken();
      const response = UrlFetchApp.fetch(url, {
        headers: { Authorization: `Bearer ${token}` }
      });
      const pdfBlob = response.getBlob().setName(pdfName);
    
      // Googleドライブに保存
      const folder = DriveApp.getFolderById(SAVE_FOLDER_ID);
      const pdfFile = folder.createFile(pdfBlob);
    
      // 一時シートを削除
      ss.deleteSheet(newSheet);
    
      return pdfFile;
    }</code></pre>
    
    
    
    <p>コードを貼り付けたら、上部の定数(SPREADSHEET_IDとSAVE_FOLDER_ID)をご自身の環境に合わせて変更します。</p>
    
    
    
    <h2 class="wp-block-heading">STEP 3:スクリプトプロパティで設定を安全に管理する(推奨)</h2>
    
    
    
    <p>上記のコードではIDを直接記述していますが、スクリプトプロパティを使うとより安全に管理できます。GASエディタで「プロジェクトの設定」→「スクリプトプロパティ」から以下を設定してください。</p>
    
    
    
    <ul class="wp-block-list"><li>プロパティ名:SPREADSHEET_ID / 値:スプレッドシートのID</li><li>プロパティ名:SAVE_FOLDER_ID / 値:保存先フォルダのID</li></ul>
    
    
    
    <p>スクリプトプロパティを使う場合は、コード冒頭の定数部分を以下のように変更します。</p>
    
    
    
    <pre class="wp-block-code"><code>const props = PropertiesService.getScriptProperties();
    const SPREADSHEET_ID = props.getProperty('SPREADSHEET_ID');
    const SAVE_FOLDER_ID = props.getProperty('SAVE_FOLDER_ID');</code></pre>
    
    
    
    <h2 class="wp-block-heading">STEP 4:動作確認とトリガー設定</h2>
    
    
    
    <p>まず手動で実行してテストします。GASエディタで「sendInvoices」関数を選択し、▶(実行)ボタンをクリックします。初回実行時はGoogleアカウントへのアクセス権限の許可を求めるダイアログが表示されるので、「許可」をクリックしてください。</p>
    
    
    
    <p>動作確認が取れたら、毎月自動実行するトリガーを設定します。GASエディタ左のメニューから「トリガー」を選択し、「トリガーを追加」をクリックして以下のように設定します。</p>
    
    
    
    <ul class="wp-block-list"><li>実行する関数:sendInvoices</li><li>イベントのソース:時間主導型</li><li>時間ベースのトリガーのタイプ:月ベースのタイマー</li><li>実行する日:月の初日(または任意の日)</li><li>時刻:午前9時〜10時</li></ul>
    
    
    
    <p><strong>トラブルシュートチェックリスト</strong></p>
    
    
    
    <ul class="wp-block-list"><li>「権限エラー」が出る場合:スクリプトエディタで「承認が必要」ダイアログが出たら「許可」をクリック。GmailAppとDriveAppのアクセス許可が必要です</li><li>PDFが空白になる場合:SpreadsheetApp.flush()の後にUtilities.sleep(2000)を追加して処理を待つようにしてください</li><li>「ファイルが見つからない」エラー:SPREADSHEET_IDとSAVE_FOLDER_IDが正しくコピーされているか確認してください</li><li>メールが届かない場合:迷惑メールフォルダを確認。また、1日のGmail送信数上限(100通)に注意してください</li></ul>
    
    
    
    <h2 class="wp-block-heading">応用:さらに便利にする拡張アイデア</h2>
    
    
    
    <ul class="wp-block-list"><li><strong>請求書番号の自動採番</strong>:スプレッドシートのA列に連番を設け、PDFファイル名や請求書本文に含めることで管理が楽になります</li><li><strong>消費税の自動計算</strong>:コードにtax = totalAmount * 0.1の計算を追加し、税込金額と税抜金額を別々に表示させることができます</li><li><strong>送付完了をSlackに通知</strong>:UrlFetchApp.fetch()でSlackのWebhook URLにPOSTすれば、送付完了をリアルタイムで通知できます</li><li><strong>顧客ごとに異なるテンプレートを使用</strong>:Dシートに「テンプレートシート名」列を追加して、顧客ごとに異なるデザインの請求書を送ることも可能です</li></ul>
    
    
    
    <h2 class="wp-block-heading">まとめ</h2>
    
    
    
    <p>GASを使えば、請求書PDFの自動生成・自動送付という面倒な作業を完全自動化できます。一度仕組みを作ってしまえば、毎月の請求業務が大幅に削減されます。</p>
    
    
    
    <ul class="wp-block-list"><li>スプレッドシートのデータを元に請求書PDFを自動生成できる</li><li>GmailAppでPDF添付メールを顧客に自動送信できる</li><li>時間ベーストリガーで毎月の定期実行が可能</li><li>「送付済み」フラグで二重送付を防止できる</li></ul>
    
    
    
    <p>GASの活用でお困りの点や、ご自身の業務に合わせたカスタマイズについてはお気軽にお問い合わせください。</p>
    
    
    
    <figure class="wp-block-image size-full"><a href="https://dx-jitsumu-labo.com/contact/"><img decoding="async" src="https://dx-jitsumu-labo.com/wp-content/uploads/2026/04/contact-cta-banner.png" alt="お問い合わせはこちら" class="wp-image-33"/></a></figure>
    </div>
    			<div style="margin-top:var(--wp--preset--spacing--40);" class="wp-block-post-date has-small-font-size"><time datetime="2026-04-18T22:04:32+09:00"><a href="https://dx-jitsumu-labo.com/gas-invoice-pdf-gmail/">2026年4月18日</a></time></div>
    		</div>
    		
    	</li><li class="wp-block-post post-301 post type-post status-publish format-standard has-post-thumbnail hentry category-gas-automation">
    		
    		<div class="wp-block-group alignfull has-global-padding is-layout-constrained wp-block-group-is-layout-constrained" style="padding-top:var(--wp--preset--spacing--60);padding-bottom:var(--wp--preset--spacing--60)">
    			<figure style="aspect-ratio:3/2;" class="wp-block-post-featured-image"><a href="https://dx-jitsumu-labo.com/gas-form-response-format/" target="_self"  ><img width="2560" height="1352" src="https://dx-jitsumu-labo.com/wp-content/uploads/2026/04/eyecatch-article12-gas-form-response-format.jpg" class="attachment-post-thumbnail size-post-thumbnail wp-post-image" alt="GASでGoogleフォームの回答を自動でスプレッドシートに整形する" style="width:100%;height:100%;object-fit:cover;" decoding="async" loading="lazy" srcset="https://dx-jitsumu-labo.com/wp-content/uploads/2026/04/eyecatch-article12-gas-form-response-format.jpg 2560w, https://dx-jitsumu-labo.com/wp-content/uploads/2026/04/eyecatch-article12-gas-form-response-format-300x158.jpg 300w, https://dx-jitsumu-labo.com/wp-content/uploads/2026/04/eyecatch-article12-gas-form-response-format-1024x541.jpg 1024w, https://dx-jitsumu-labo.com/wp-content/uploads/2026/04/eyecatch-article12-gas-form-response-format-768x406.jpg 768w, https://dx-jitsumu-labo.com/wp-content/uploads/2026/04/eyecatch-article12-gas-form-response-format-1536x811.jpg 1536w, https://dx-jitsumu-labo.com/wp-content/uploads/2026/04/eyecatch-article12-gas-form-response-format-2048x1082.jpg 2048w" sizes="auto, (max-width: 2560px) 100vw, 2560px" /></a></figure>
    			<h2 class="wp-block-post-title has-x-large-font-size"><a href="https://dx-jitsumu-labo.com/gas-form-response-format/" target="_self" >GASでGoogleフォームの回答を自動でスプレッドシートに整形する</a></h2>
    			<div class="entry-content alignfull wp-block-post-content has-medium-font-size has-global-padding is-layout-constrained wp-block-post-content-is-layout-constrained">
    <p class="article-lead">「Googleフォームで集めた回答をそのまま使おうとしたら、列がバラバラで整形が大変…」そんな悩みを抱えている方は多いのではないでしょうか。この記事では、Google Apps Script(GAS)を使って、Googleフォームの回答を自動でスプレッドシートに整形・加工する仕組みを、コード付きで解説します。</p>
    
    
    
    <p>フォームの回答が蓄積するたびに手動でコピー&ペーストしたり、列を並び替えたりする作業は、じつは完全に自動化できます。GASのフォーム送信トリガーを活用すれば、回答が届いた瞬間に自動で整形・別シートへ転記・通知まで行えます。</p>
    
    
    
    <h2 class="wp-block-heading">この記事でできること</h2>
    
    
    
    <ul class="wp-block-list"><li>フォーム送信のたびに自動で回答を整形・加工する</li><li>必要な列だけを別シートに転記する</li><li>回答内容をフォーマットして担当者にメール通知する</li><li>重複回答の自動チェックと除外処理を実装する</li></ul>
    
    
    
    <h2 class="wp-block-heading">事前準備(5分)</h2>
    
    
    
    <p>以下のものを用意してください。</p>
    
    
    
    <ul class="wp-block-list"><li>Googleアカウント(無料)</li><li>Googleフォーム(回答先スプレッドシートと連携済み)</li><li>Google Apps Script(フォームに紐づくコンテナバインドスクリプト)</li></ul>
    
    
    
    <p>フォームの回答先スプレッドシートが未設定の場合は、フォーム編集画面の「回答」タブ → 「スプレッドシートにリンク」から連携してください。</p>
    
    
    
    <h2 class="wp-block-heading">STEP 1:GASエディタを開いてスクリプトを作成する</h2>
    
    
    
    <p>フォームの回答が蓄積するスプレッドシートを開き、「拡張機能」→「Apps Script」を選択します。エディタが開いたら、デフォルトの関数を削除して以下のコードを貼り付けます。</p>
    
    
    
    <pre class="wp-block-code"><code>// フォーム送信時に自動実行される関数
    // スプレッドシートのフォームトリガーに設定する
    function onFormSubmit(e) {
      const ss = SpreadsheetApp.getActiveSpreadsheet();
      const rawSheet = ss.getSheetByName('フォームの回答 1'); // 回答シート名
      const destSheet = ss.getSheetByName('整形済み') || ss.insertSheet('整形済み');
    
      // 最新の回答行を取得
      const lastRow = rawSheet.getLastRow();
      const startCol = 1, numRows = 1;
      const rawData = rawSheet.getRange(lastRow, startCol, numRows, rawSheet.getLastColumn()).getValues()[0];
    
      // ヘッダーを取得
      const headerRow = 1, headerCol = 1, numHeaderRows = 1;
      const headers = rawSheet.getRange(headerRow, headerCol, numHeaderRows, rawSheet.getLastColumn()).getValues()[0];
    
      // 整形済みシートのヘッダーを初期化(初回のみ)
      if (destSheet.getLastRow() === 0) {
        const destHeaders = ['受付番号', 'タイムスタンプ', '名前', 'メールアドレス', 'お問い合わせ内容', 'ステータス'];
        destSheet.appendRow(destHeaders);
        destSheet.getRange(1, 1, 1, destHeaders.length).setFontWeight('bold');
      }
    
      // 受付番号を生成(例:REQ-0001)
      const receiptNo = 'REQ-' + String(destSheet.getLastRow()).padStart(4, '0');
    
      // タイムスタンプを整形(例:2026/04/17 09:30)
      const timestamp = Utilities.formatDate(
        new Date(rawData[0]),
        'Asia/Tokyo',
        'yyyy/MM/dd HH:mm'
      );
    
      // 回答データをマッピング(ヘッダー名でインデックスを取得)
      const nameIdx = headers.indexOf('名前');
      const emailIdx = headers.indexOf('メールアドレス');
      const contentIdx = headers.indexOf('お問い合わせ内容');
    
      // 整形したデータを作成
      const formattedRow = [
        receiptNo,
        timestamp,
        nameIdx >= 0 ? rawData[nameIdx] : '',
        emailIdx >= 0 ? rawData[emailIdx] : '',
        contentIdx >= 0 ? rawData[contentIdx] : '',
        '未対応' // デフォルトステータス
      ];
    
      // 整形済みシートに書き込み
      destSheet.appendRow(formattedRow);
    
      Logger.log('整形完了: ' + receiptNo);
    }</code></pre>
    
    
    
    <h2 class="wp-block-heading">STEP 2:ヘッダー名に合わせてマッピングをカスタマイズする</h2>
    
    
    
    <p>STEP 1のコードは「名前」「メールアドレス」「お問い合わせ内容」というフォーム質問名を前提にしています。自分のフォームに合わせて変更が必要な場合は、以下の部分を編集してください。</p>
    
    
    
    <pre class="wp-block-code"><code>// ▼ ここを自分のフォームの質問名に変更する
    const nameIdx = headers.indexOf('氏名');         // 「名前」→「氏名」に変更する例
    const emailIdx = headers.indexOf('連絡先メール');  // 「メールアドレス」→「連絡先メール」
    const contentIdx = headers.indexOf('相談内容');   // 「お問い合わせ内容」→「相談内容」</code></pre>
    
    
    
    <p>質問名が一致しない場合は indexOf が -1 を返し、空文字がセットされます。フォームのヘッダー名を「ログ」で確認したい場合は、以下のコードを追加して実行してください。</p>
    
    
    
    <pre class="wp-block-code"><code>function checkHeaders() {
      const ss = SpreadsheetApp.getActiveSpreadsheet();
      const rawSheet = ss.getSheetByName('フォームの回答 1');
      const headerRow = 1, headerCol = 1, numHeaderRows = 1;
      const headers = rawSheet.getRange(headerRow, headerCol, numHeaderRows, rawSheet.getLastColumn()).getValues()[0];
      Logger.log(headers); // ログでヘッダー一覧を確認
    }</code></pre>
    
    
    
    <h2 class="wp-block-heading">STEP 3:フォーム送信トリガーを設定する</h2>
    
    
    
    <p>GASはコードを書いただけでは動きません。フォームが送信されたときに自動実行されるよう「トリガー」を設定します。</p>
    
    
    
    <ul class="wp-block-list"><li>GASエディタ左側のメニューから「トリガー(時計アイコン)」をクリック</li><li>右下の「トリガーを追加」ボタンをクリック</li><li>実行する関数:onFormSubmit</li><li>イベントのソース:「スプレッドシートから」</li><li>イベントの種類:「フォーム送信時」</li><li>「保存」をクリック</li></ul>
    
    
    
    <p>初回は権限承認のポップアップが表示されます。「許可」→Googleアカウントを選択→「許可」の順にクリックしてください。</p>
    
    
    
    <h2 class="wp-block-heading">STEP 4:動作確認</h2>
    
    
    
    <p>テスト用のフォーム回答を送信し、「整形済み」シートに正しく転記されているか確認します。</p>
    
    
    
    <p><strong>トラブルシュートチェックリスト</strong></p>
    
    
    
    <ul class="wp-block-list"><li>「整形済み」シートが作成されない → シート名の自動生成に失敗している可能性。手動でシートを作成し、シート名が正確に「整形済み」であることを確認</li><li>ヘッダーが空欄になる → フォームの質問名とコード内の文字列が一致しているか checkHeaders() で確認</li><li>トリガーが動かない → 権限承認が完了しているか確認。GASのトリガー画面でエラーが出ていないかもチェック</li><li>受付番号が重複する → destSheet.getLastRow() は行数を返すので、削除・並び替えをすると重複が起きる。UUIDを使う方法に切り替えること</li></ul>
    
    
    
    <h2 class="wp-block-heading">応用:さらに便利にする拡張アイデア</h2>
    
    
    
    <ul class="wp-block-list"><li><strong>メール自動通知</strong>:整形後に GmailApp.sendEmail() で担当者や回答者へ確認メールを自動送信する</li><li><strong>重複チェック</strong>:メールアドレスを使って既存の回答と重複していないか検索し、重複行にフラグを立てる</li><li><strong>Slack通知連携</strong>:フォーム受付時にSlackのWebhookへPOSTして、チャンネルに即時通知する</li><li><strong>条件分岐ルーティング</strong>:回答内容に応じて「整形済みA」「整形済みB」など複数シートに振り分ける</li><li><strong>PDF自動生成</strong>:回答内容をテンプレートDocumentに差し込んでPDF化し、Driveに自動保存する</li></ul>
    
    
    
    <h2 class="wp-block-heading">まとめ</h2>
    
    
    
    <p>GASのフォーム送信トリガーを使えば、Googleフォームの回答を受け取った瞬間に自動整形・転記・通知まで行えます。手作業のコピー&ペーストや列の並び替えから解放され、業務効率が大きく改善します。</p>
    
    
    
    <ul class="wp-block-list"><li>フォームと連携したスプレッドシートのGASに onFormSubmit 関数を作成する</li><li>ヘッダー名でインデックスを取得し、必要な列だけを整形して別シートに書き込む</li><li>トリガーを「フォーム送信時」に設定すれば全自動で動く</li><li>メール通知・Slack連携・重複チェックなどの拡張も簡単に追加できる</li></ul>
    
    
    
    <p>「自社のフォームに合わせてカスタマイズしたい」「もっと複雑な整形ロジックを実装したい」という方は、お気軽にお問い合わせください。</p>
    
    
    
    <figure class="wp-block-image size-full"><a href="https://dx-jitsumu-labo.com/contact/"><img decoding="async" src="https://dx-jitsumu-labo.com/wp-content/uploads/2026/04/contact-cta-banner.png" alt="お問い合わせはこちら" class="wp-image-33"/></a></figure>
    
    </div>
    			<div style="margin-top:var(--wp--preset--spacing--40);" class="wp-block-post-date has-small-font-size"><time datetime="2026-04-17T09:49:20+09:00"><a href="https://dx-jitsumu-labo.com/gas-form-response-format/">2026年4月17日</a></time></div>
    		</div>
    		
    	</li><li class="wp-block-post post-196 post type-post status-publish format-standard hentry category-uncategorized">
    		
    		<div class="wp-block-group alignfull has-global-padding is-layout-constrained wp-block-group-is-layout-constrained" style="padding-top:var(--wp--preset--spacing--60);padding-bottom:var(--wp--preset--spacing--60)">
    			
    			<h2 class="wp-block-post-title has-x-large-font-size"><a href="https://dx-jitsumu-labo.com/gas%e3%81%a7pdf%e3%82%92%e8%87%aa%e5%8b%95%e7%94%9f%e6%88%90%e3%81%97%e3%81%a6%e3%83%a1%e3%83%bc%e3%83%ab%e9%80%81%e4%bb%98%e3%81%99%e3%82%8b%e4%bb%95%e7%b5%84%e3%81%bf%e3%82%92%e4%bd%9c%e3%82%8b/" target="_self" >GASでPDFを自動生成してメール送付する仕組みを作る方法</a></h2>
    			<div class="entry-content alignfull wp-block-post-content has-medium-font-size has-global-padding is-layout-constrained wp-block-post-content-is-layout-constrained"><p class="article-lead">Google Apps Script(GAS)を使えば、スプレッドシートのデータからPDFを自動生成し、指定のアドレスへ自動メール送信する仕組みをゼロコストで構築できます。</p>
    <p>「毎月末に請求書を手作業でPDF化してメールしている」「見積書をいちいちダウンロードして添付するのが面倒」といった業務は、GASで完全自動化できます。Googleドライブ上のスプレッドシートやドキュメントをPDF変換して添付メール送信するスクリプトを一度作ってしまえば、ボタン一つ・あるいはスケジュール実行で完結します。</p>
    <p>本記事では、スプレッドシートの内容をPDF化してGmailで送付するまでの具体的な手順をステップ形式で解説します。GASの基本的な操作経験があれば、30〜60分で動くものが作れます。</p>
    <h2>この記事でできること</h2>
    <ul>
    <li>GASからスプレッドシートをPDF形式に変換してドライブに保存</li>
    <li>生成したPDFをGmailの添付ファイルとして自動送信</li>
    <li>送信先・件名・本文をスプレッドシートのセルで管理する構成</li>
    <li>月次・週次などのスケジュール実行で完全自動化</li>
    </ul>
    <h2>事前準備</h2>
    <p>以下のものを用意してください。</p>
    <ul>
    <li>Googleアカウント(GmailとDriveが使えること)</li>
    <li>PDF化したい内容を入力したGoogleスプレッドシート(例:請求書テンプレート)</li>
    <li>GASエディタへのアクセス(スプレッドシートのメニューから「拡張機能」→「Apps Script」で開く)</li>
    </ul>
    <p>スプレッドシートには「送信先メールアドレス」「件名」「本文」をセルに入力しておくと、スクリプト側で参照できて柔軟に使えます。今回は A1:メールアドレス、A2:件名、A3:本文 のシンプルな構成で進めます。</p>
    <h2>STEP1: スプレッドシートのIDを確認する</h2>
    <p>GASでスプレッドシートをPDF化するには、対象ファイルのIDが必要です。スプレッドシートのURLの /d/ 以降の長い文字列がファイルIDです。GASスクリプトはそのスプレッドシートと紐付いて動くため、<code>SpreadsheetApp.getActiveSpreadsheet().getId()</code> で自動取得できます。</p>
    <h2>STEP2: GASでPDF変換とメール送信のスクリプトを書く</h2>
    <p>GASエディタを開き、以下のスクリプトを貼り付けてください。DriveApp の getAs メソッドを使うと、スプレッドシートをPDFとして取得できます。</p>
    <pre><code>function generateAndSendPDF() {
      var ss = SpreadsheetApp.getActiveSpreadsheet();
      var sheet = ss.getSheetByName('Sheet1');
    
      // 送信情報をシートから取得
      var toEmail = sheet.getRange('A1').getValue();
      var subject = sheet.getRange('A2').getValue();
      var body    = sheet.getRange('A3').getValue();
    
      // DriveAppでPDF形式に変換する
      var ssFile  = DriveApp.getFileById(ss.getId());
      var pdfBlob = ssFile.getAs(MimeType.PDF).setName(subject + '.pdf');
    
      // Gmailで添付送信
      GmailApp.sendEmail(toEmail, subject, body, {
        attachments: [pdfBlob]
      });
      Logger.log('送信完了: ' + toEmail);
    }</code></pre>
    <p>スクリプトを保存したら「実行」ボタンで動作確認します。初回はGmail・DriveへのPermissionが求められるので「許可」してください。</p>
    <h2>STEP3: スプレッドシートに送信情報を入力する</h2>
    <p>Sheet1 の A1〜A3 に以下のように入力してください。</p>
    <pre><code>A1: recipient@example.com
    A2: 【5月分】請求書送付のご案内
    A3: お世話になっております。5月分の請求書をPDFにてお送りします。ご確認ください。</code></pre>
    <p>スプレッドシートの他の部分(B列以降)に請求書データを入力しておくと、そのシート全体がPDFになります。印刷範囲を設定しておくと余白のない綺麗なPDFになります。</p>
    <h2>STEP4: 複数の取引先に一括送信する</h2>
    <p>複数取引先に一括送信したい場合は、「送信リスト」シートを別に用意して繰り返し処理にします。「送信リスト」シートには1行目をヘッダー(メール・件名・本文)として、2行目以降に各取引先の情報を入力します。スクリプトはリストを上から読み込み、1件ずつPDFを生成して送信します。連続送信によるレート制限を避けるため、送信間に <code>Utilities.sleep()</code> で待機時間を入れることを推奨します。</p>
    <p>ループ処理の骨格は以下のとおりです。PDF生成とメール送信の部分はSTEP2のコードと同じロジックをそのまま使えます。</p>
    <pre><code>function sendBulkPDF() {
      var ss        = SpreadsheetApp.getActiveSpreadsheet();
      var listSheet = ss.getSheetByName('送信リスト');
      var lastRow   = listSheet.getLastRow();
    
      for (var i = 2; i <= lastRow; i++) {
        var toEmail = listSheet.getRange(i, 1).getValue();
        var subject = listSheet.getRange(i, 2).getValue();
        var body    = listSheet.getRange(i, 3).getValue();
    
        // PDF生成とメール送信はSTEP2と同じロジックを記述
        // ...
    
        Utilities.sleep(1500); // 連続送信間の待機(ミリ秒)
      }
      Logger.log('一括送信完了');
    }</code></pre>
    <h2>STEP5: スケジュール実行で月末に自動送信する</h2>
    <p>手動実行ではなく月末や特定日時に自動送信するにはトリガーを設定します。GASエディタ左メニューの「トリガー(時計アイコン)」をクリックし、「トリガーを追加」→ 関数を「generateAndSendPDF」に設定→ イベントのソース:「時間主導型」、タイプ:「月タイマー」→ 日と時刻を設定して「保存」します。これで毎月指定日の指定時刻に自動実行されます。</p>
    <h2>うまくいかないときのチェックリスト</h2>
    <ul>
    <li>「Authorization error」→ スクリプトのPermissionを再度許可。GASエディタの「実行」→「権限を確認」から再認証</li>
    <li>PDFが空白・崩れる → スプレッドシートの印刷範囲を設定しているか確認。「ファイル」→「印刷」でプレビュー確認</li>
    <li>メールが届かない → toEmailの値が正しいか、A1セルをLogger.logで出力して確認</li>
    <li>「Service invoked too many times」→ sleep値を増やすか、処理を分割して実行する</li>
    <li>PDFサイズが大きい → 画像や未使用シートを削除してからPDF化するとサイズを抑えられる</li>
    </ul>
    <h2>応用・発展</h2>
    <ul>
    <li><strong>Googleドキュメント版</strong>:DocumentApp.openById()で取得したドキュメントも同様にMimeType.PDFでBlob化できます</li>
    <li><strong>DriveへのPDF保存</strong>:DriveApp.createFile(pdfBlob) で送信前にDriveへバックアップ保存する運用も可能です</li>
    <li><strong>フォームと組み合わせる</strong>:Googleフォーム送信をトリガーにして、回答内容をPDFにして即時返信するシステムも構築できます</li>
    <li><strong>Slackへの通知</strong>:送信完了後にSlackのWebhookでチャンネルに通知を飛ばすと、チーム全体で送信状況を把握できます</li>
    </ul>
    <h2>まとめ</h2>
    <ul>
    <li>DriveApp.getFileById(ssId).getAs(MimeType.PDF) でスプレッドシートをPDF化できる</li>
    <li>GmailApp.sendEmail の attachments オプションで添付ファイル付きメール送信が可能</li>
    <li>送信情報(宛先・件名・本文)をシートのセルで管理することで、スクリプト変更なしに運用できる</li>
    <li>月末の自動実行はトリガー設定で「月タイマー」を使う</li>
    <li>一括送信・Docs対応・Drive保存などに応用すれば、社内の帳票業務を大幅に効率化できる</li>
    </ul>
    </div>
    			<div style="margin-top:var(--wp--preset--spacing--40);" class="wp-block-post-date has-small-font-size"><time datetime="2026-04-13T17:07:10+09:00"><a href="https://dx-jitsumu-labo.com/gas%e3%81%a7pdf%e3%82%92%e8%87%aa%e5%8b%95%e7%94%9f%e6%88%90%e3%81%97%e3%81%a6%e3%83%a1%e3%83%bc%e3%83%ab%e9%80%81%e4%bb%98%e3%81%99%e3%82%8b%e4%bb%95%e7%b5%84%e3%81%bf%e3%82%92%e4%bd%9c%e3%82%8b/">2026年4月13日</a></time></div>
    		</div>
    		
    	</li><li class="wp-block-post post-95 post type-post status-publish format-standard hentry category-gas-automation category-efficiency">
    		
    		<div class="wp-block-group alignfull has-global-padding is-layout-constrained wp-block-group-is-layout-constrained" style="padding-top:var(--wp--preset--spacing--60);padding-bottom:var(--wp--preset--spacing--60)">
    			
    			<h2 class="wp-block-post-title has-x-large-font-size"><a href="https://dx-jitsumu-labo.com/notion-gas-daily-report-v2/" target="_self" >NotionとGASを連携して日報を自動生成する</a></h2>
    			<div class="entry-content alignfull wp-block-post-content has-medium-font-size has-global-padding is-layout-constrained wp-block-post-content-is-layout-constrained"><p class="article-lead">NotionのデータベースとGoogle Apps Scriptを組み合わせることで、毎日の日報作成を完全自動化できます。</p>
    <p>「毎日の日報作成に時間がかかる」「書き忘れが多い」という悩みは、多くのビジネスパーソンに共通しています。Notionは情報管理ツールとして優秀ですが、単体では自動化に限界があります。そこでGoogle Apps Script(GAS)と連携させることで、Notionのデータを自動で集計し、日報を自動生成・送信する仕組みを構築できます。</p>
    <p>本記事では、Notion APIとGASを使って「Notionのタスクデータベースから当日の完了タスクを自動収集し、日報メールを自動送信する」システムを一から作る手順を解説します。プログラミング初心者でも手順通りに進めれば実装できるよう、詳しく説明します。</p>
    <h2>この記事でできること</h2>
    <ul>
    <li>Notion APIを使ってデータベースのデータをGASで取得する</li>
    <li>毎日決まった時刻に日報を自動生成する</li>
    <li>完成した日報をメールで自動送信する</li>
    <li>日報内容をNotionに自動で記録・保存する</li>
    </ul>
    <h2>事前準備</h2>
    <p>以下のものが必要です。作業開始前に揃えておきましょう。</p>
    <ul>
    <li>Notionアカウント(無料プランでOK)</li>
    <li>Googleアカウント(GASを使用)</li>
    <li>Notionのタスク管理データベース(プロパティ:タスク名、担当者、完了フラグ、日付)</li>
    </ul>
    <h2>STEP1: Notion APIキーを取得する</h2>
    <p>まずNotionとGASを連携させるためのAPIキーを取得します。</p>
    <ol>
    <li>Notionにログインし、notion.so/my-integrationsにアクセスします</li>
    <li>「新しいインテグレーション」をクリックし、名前(例:GAS日報Bot)を入力します</li>
    <li>「送信」をクリックするとInternal Integration Tokenが発行されます</li>
    <li>このトークン(secret_xxxx…形式)をコピーして保存します</li>
    </ol>
    <p>次に、連携したいNotionデータベースを開き、右上の「…」メニューから「接続」→作成したインテグレーションを選択して権限を付与します。データベースのURLに含まれる32文字の英数字がデータベースIDです。このIDも控えておきます。</p>
    <h2>STEP2: GASのスクリプトを作成する</h2>
    <p>Google Driveにアクセスし、「新規」から「Google Apps Script」を選択してプロジェクトを作成します。スクリプトエディタで以下の内容を入力します。まず設定項目として、NOTIONトークン、データベースID、送信先メール、氏名を定数として定義します。</p>
    <p>メイン関数generateDailyReport()では、今日の日付をyyyy-MM-dd形式で取得し、fetchNotionTasks()でタスク一覧を取得します。タスクがあればcreateReportBody()で日報テキストを作成し、sendReportEmail()でメール送信します。</p>
    <p>fetchNotionTasks()ではNotionのデータベースクエリAPIを呼び出します。filterにより完了フラグがtrueかつ本日の日付のタスクだけを絞り込みます。レスポンスのresults配列からタスク名とカテゴリを抽出して返します。</p>
    <p>createReportBody()では日報のテキストを組み立てます。日付・担当者・完了タスク一覧・所感欄をテンプレートに従って結合します。sendReportEmail()ではGmailAppのsendEmail()を使ってメール送信します。</p>
    <h2>STEP3: 設定値を書き換える</h2>
    <p>コードの上部にある設定項目を自分の環境に合わせて書き換えます。</p>
    <ul>
    <li>NOTION_TOKEN: STEP1で取得したIntegration Tokenに置き換え</li>
    <li>DATABASE_ID: STEP1で控えたデータベースIDに置き換え</li>
    <li>REPORT_EMAIL: 日報の送信先メールアドレスに変更</li>
    <li>YOUR_NAME: 自分の名前に変更</li>
    </ul>
    <p>Notionデータベースのプロパティ名がコードと一致しているか確認してください。チェックボックスのプロパティ名が「完了」であれば、コード内の「完了フラグ」を「完了」に変更します。</p>
    <h2>STEP4: 動作テストをする</h2>
    <p>実際にスクリプトを実行してテストします。</p>
    <ol>
    <li>GASエディタの上部にある関数選択プルダウンでgenerateDailyReportを選択します</li>
    <li>「実行」ボタンをクリックします</li>
    <li>初回実行時はGoogleアカウントの権限承認画面が表示されます。「許可」をクリックします</li>
    <li>実行ログに「日報送信完了」が表示されれば成功です</li>
    <li>指定したメールアドレスに日報メールが届いていることを確認します</li>
    </ol>
    <p>うまく動作したら、次のSTEPで毎日自動実行する設定をします。</p>
    <h2>STEP5: 毎日自動実行のトリガーを設定する</h2>
    <p>GASには「トリガー」という自動実行機能があります。これを設定することで、毎日決まった時刻に日報が自動送信されるようになります。</p>
    <ol>
    <li>GASエディタの左メニューから「トリガー」(時計アイコン)をクリックします</li>
    <li>右下の「トリガーを追加」ボタンをクリックします</li>
    <li>実行する関数にgenerateDailyReportを選択、イベントのソースを時間主導型、日付ベースのタイマーで17時〜18時に設定します</li>
    <li>「保存」をクリックして完了です</li>
    </ol>
    <p>これで毎日17〜18時の間に自動で日報が送信されます。</p>
    <h2>うまくいかないときのチェックリスト</h2>
    <ul>
    <li><strong>APIトークンエラー</strong>: Notionのインテグレーションが正しく作成されているか、トークンをコピーし直してみる</li>
    <li><strong>タスクが0件になる</strong>: Notionデータベースのプロパティ名がコードと一致しているか確認。日付フォーマットがyyyy-MM-dd形式になっているか確認</li>
    <li><strong>メールが届かない</strong>: REPORT_EMAILに正しいアドレスが設定されているか確認</li>
    <li><strong>権限エラー</strong>: Notionデータベースに対してインテグレーションのアクセス権が付与されているか確認</li>
    <li><strong>トリガーが動かない</strong>: GASプロジェクトがスタンドアロン型になっているか確認</li>
    </ul>
    <h2>応用・発展</h2>
    <p>基本的な日報自動化ができたら、さらに発展させることができます。</p>
    <ul>
    <li><strong>Slack通知に変更する</strong>: メールの代わりにSlack Incoming Webhookを使い、UrlFetchApp.fetch()でSlackに送信できます</li>
    <li><strong>日報をNotionに記録する</strong>: Notion APIのpages.createエンドポイントを使い、生成した日報を別のNotionページとして自動保存できます</li>
    <li><strong>週次サマリーを作る</strong>: トリガーを週ベースに設定し、1週間分のタスクをまとめた週次報告書を自動生成できます</li>
    <li><strong>ChatGPT APIと組み合わせる</strong>: タスクリストをChatGPT APIに送り、振り返りコメントを自動生成して日報に追加できます</li>
    </ul>
    <h2>まとめ</h2>
    <ul>
    <li>Notion APIとGASを連携させることで、日報作成を完全自動化できる</li>
    <li>Notion Integrationを作成してAPIトークンを取得し、GASからデータを取得する</li>
    <li>GASのトリガー機能で毎日決まった時刻に自動実行できる</li>
    <li>Slack通知やNotion記録など、応用範囲は広い</li>
    <li>日報の書き忘れゼロ・作成時間ゼロを実現し、本来の業務に集中できる</li>
    </ul>
    </div>
    			<div style="margin-top:var(--wp--preset--spacing--40);" class="wp-block-post-date has-small-font-size"><time datetime="2026-04-13T17:07:10+09:00"><a href="https://dx-jitsumu-labo.com/notion-gas-daily-report-v2/">2026年4月13日</a></time></div>
    		</div>
    		
    	</li></ul>
    	
    	<div class="wp-block-group has-global-padding is-layout-constrained wp-block-group-is-layout-constrained" style="padding-top:var(--wp--preset--spacing--60);padding-bottom:var(--wp--preset--spacing--60)">
    		
    	</div>
    	
    	
    	<div class="wp-block-group alignwide has-global-padding is-layout-constrained wp-block-group-is-layout-constrained">
    		<nav class="alignwide wp-block-query-pagination is-content-justification-space-between is-layout-flex wp-container-core-query-pagination-is-layout-b2891da8 wp-block-query-pagination-is-layout-flex" aria-label="ページネーション">
    			
    			<div class="wp-block-query-pagination-numbers"><span aria-current="page" class="page-numbers current">1</span>
    <a class="page-numbers" href="https://dx-jitsumu-labo.com/author/lumino-dx/page/2/">2</a>
    <a class="page-numbers" href="https://dx-jitsumu-labo.com/author/lumino-dx/page/3/">3</a></div>
    			<a href="https://dx-jitsumu-labo.com/author/lumino-dx/page/2/" class="wp-block-query-pagination-next">次のページ<span class='wp-block-query-pagination-next-arrow is-arrow-arrow' aria-hidden='true'>→</span></a>
    		</nav>
    	</div>
    	
    </div>
    
    
    </main>
    
    
    <footer class="wp-block-template-part">
    <div class="wp-block-group has-global-padding is-layout-constrained wp-block-group-is-layout-constrained" style="padding-top:var(--wp--preset--spacing--60);padding-bottom:var(--wp--preset--spacing--50)">
    	
    	<div class="wp-block-group alignwide is-layout-flow wp-block-group-is-layout-flow">
    		
    
    		
    		<div class="wp-block-group alignfull is-content-justification-space-between is-layout-flex wp-container-core-group-is-layout-e5edad21 wp-block-group-is-layout-flex">
    			
    			<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
    				
    				<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:100%"><h2 class="wp-block-site-title"><a href="https://dx-jitsumu-labo.com" target="_self" rel="home">DX実務ラボ</a></h2>
    
    				
    				</div>
    				
    
    				
    				<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
    					
    					<div style="height:var(--wp--preset--spacing--40);width:0px" aria-hidden="true" class="wp-block-spacer"></div>
    					
    				</div>
    				
    			</div>
    			
    
    			
    			<div class="wp-block-group is-content-justification-space-between is-layout-flex wp-container-core-group-is-layout-570722b2 wp-block-group-is-layout-flex">
    				<nav class="is-vertical wp-block-navigation is-layout-flex wp-container-core-navigation-is-layout-fe9cc265 wp-block-navigation-is-layout-flex"><ul class="wp-block-navigation__container  is-vertical wp-block-navigation"><li class=" wp-block-navigation-item wp-block-navigation-link"><a class="wp-block-navigation-item__content"  href="#"><span class="wp-block-navigation-item__label">ブログ</span></a></li><li class=" wp-block-navigation-item wp-block-navigation-link"><a class="wp-block-navigation-item__content"  href="#"><span class="wp-block-navigation-item__label">このサイトについて</span></a></li><li class=" wp-block-navigation-item wp-block-navigation-link"><a class="wp-block-navigation-item__content"  href="#"><span class="wp-block-navigation-item__label">よくある質問</span></a></li><li class=" wp-block-navigation-item wp-block-navigation-link"><a class="wp-block-navigation-item__content"  href="#"><span class="wp-block-navigation-item__label">投稿者</span></a></li></ul></nav>
    
    				<nav class="is-vertical wp-block-navigation is-layout-flex wp-container-core-navigation-is-layout-fe9cc265 wp-block-navigation-is-layout-flex"><ul class="wp-block-navigation__container  is-vertical wp-block-navigation"><li class=" wp-block-navigation-item wp-block-navigation-link"><a class="wp-block-navigation-item__content"  href="#"><span class="wp-block-navigation-item__label">イベント</span></a></li><li class=" wp-block-navigation-item wp-block-navigation-link"><a class="wp-block-navigation-item__content"  href="#"><span class="wp-block-navigation-item__label">ショップ</span></a></li><li class=" wp-block-navigation-item wp-block-navigation-link"><a class="wp-block-navigation-item__content"  href="#"><span class="wp-block-navigation-item__label">パターン</span></a></li><li class=" wp-block-navigation-item wp-block-navigation-link"><a class="wp-block-navigation-item__content"  href="#"><span class="wp-block-navigation-item__label">テーマ</span></a></li></ul></nav>
    			</div>
    				
    		</div>
    		
    
    		
    		<div style="height:var(--wp--preset--spacing--70)" aria-hidden="true" class="wp-block-spacer"></div>
    		
    
    		
    		<div class="wp-block-group alignfull is-content-justification-space-between is-layout-flex wp-container-core-group-is-layout-91e87306 wp-block-group-is-layout-flex">
    			
    			<p class="has-small-font-size">Twenty Twenty-Five</p>
    			
    			
    			<p class="has-small-font-size">
    				Designed with <a href="https://ja.wordpress.org" rel="nofollow">WordPress</a>			</p>
    			
    		</div>
    		
    	</div>
    	
    </div>
    
    
    </footer>
    </div>
    <script type="speculationrules">
    {"prefetch":[{"source":"document","where":{"and":[{"href_matches":"/*"},{"not":{"href_matches":["/wp-*.php","/wp-admin/*","/wp-content/uploads/*","/wp-content/*","/wp-content/plugins/*","/wp-content/themes/twentytwentyfive/*","/*\\?(.+)"]}},{"not":{"selector_matches":"a[rel~=\"nofollow\"]"}},{"not":{"selector_matches":".no-prefetch, .no-prefetch a"}}]},"eagerness":"conservative"}]}
    </script>
    <script type="module" src="https://dx-jitsumu-labo.com/wp-includes/js/dist/script-modules/block-library/navigation/view.min.js?ver=b0f909c3ec791c383210" id="@wordpress/block-library/navigation/view-js-module" fetchpriority="low" data-wp-router-options="{"loadOnClientNavigation":true}"></script>
    <script id="wp-block-template-skip-link-js-after">
    	( function() {
    		var skipLinkTarget = document.querySelector( 'main' ),
    			sibling,
    			skipLinkTargetID,
    			skipLink;
    
    		// Early exit if a skip-link target can't be located.
    		if ( ! skipLinkTarget ) {
    			return;
    		}
    
    		/*
    		 * Get the site wrapper.
    		 * The skip-link will be injected in the beginning of it.
    		 */
    		sibling = document.querySelector( '.wp-site-blocks' );
    
    		// Early exit if the root element was not found.
    		if ( ! sibling ) {
    			return;
    		}
    
    		// Get the skip-link target's ID, and generate one if it doesn't exist.
    		skipLinkTargetID = skipLinkTarget.id;
    		if ( ! skipLinkTargetID ) {
    			skipLinkTargetID = 'wp--skip-link--target';
    			skipLinkTarget.id = skipLinkTargetID;
    		}
    
    		// Create the skip link.
    		skipLink = document.createElement( 'a' );
    		skipLink.classList.add( 'skip-link', 'screen-reader-text' );
    		skipLink.id = 'wp-skip-link';
    		skipLink.href = '#' + skipLinkTargetID;
    		skipLink.innerText = '内容をスキップ';
    
    		// Inject the skip link.
    		sibling.parentElement.insertBefore( skipLink, sibling );
    	}() );
    	
    //# sourceURL=wp-block-template-skip-link-js-after
    </script>
    <script src="https://dx-jitsumu-labo.com/wp-includes/js/dist/hooks.min.js?ver=dd5603f07f9220ed27f1" id="wp-hooks-js"></script>
    <script src="https://dx-jitsumu-labo.com/wp-includes/js/dist/i18n.min.js?ver=c26c3dc7bed366793375" id="wp-i18n-js"></script>
    <script id="wp-i18n-js-after">
    wp.i18n.setLocaleData( { 'text direction\u0004ltr': [ 'ltr' ] } );
    //# sourceURL=wp-i18n-js-after
    </script>
    <script src="https://dx-jitsumu-labo.com/wp-content/plugins/contact-form-7/includes/swv/js/index.js?ver=6.1.5" id="swv-js"></script>
    <script id="contact-form-7-js-translations">
    ( function( domain, translations ) {
    	var localeData = translations.locale_data[ domain ] || translations.locale_data.messages;
    	localeData[""].domain = domain;
    	wp.i18n.setLocaleData( localeData, domain );
    } )( "contact-form-7", {"translation-revision-date":"2025-11-30 08:12:23+0000","generator":"GlotPress\/4.0.3","domain":"messages","locale_data":{"messages":{"":{"domain":"messages","plural-forms":"nplurals=1; plural=0;","lang":"ja_JP"},"This contact form is placed in the wrong place.":["\u3053\u306e\u30b3\u30f3\u30bf\u30af\u30c8\u30d5\u30a9\u30fc\u30e0\u306f\u9593\u9055\u3063\u305f\u4f4d\u7f6e\u306b\u7f6e\u304b\u308c\u3066\u3044\u307e\u3059\u3002"],"Error:":["\u30a8\u30e9\u30fc:"]}},"comment":{"reference":"includes\/js\/index.js"}} );
    //# sourceURL=contact-form-7-js-translations
    </script>
    <script id="contact-form-7-js-before">
    var wpcf7 = {
        "api": {
            "root": "https:\/\/dx-jitsumu-labo.com\/wp-json\/",
            "namespace": "contact-form-7\/v1"
        }
    };
    //# sourceURL=contact-form-7-js-before
    </script>
    <script src="https://dx-jitsumu-labo.com/wp-content/plugins/contact-form-7/includes/js/index.js?ver=6.1.5" id="contact-form-7-js"></script>
    <script src="https://dx-jitsumu-labo.com/wp-content/plugins/honeypot/includes/js/wpa.js?ver=2.3.04" id="wpascript-js"></script>
    <script id="wpascript-js-after">
    wpa_field_info = {"wpa_field_name":"ijdsgk2926","wpa_field_value":970923,"wpa_add_test":"no"}
    //# sourceURL=wpascript-js-after
    </script>
    <script id="wp-emoji-settings" type="application/json">
    {"baseUrl":"https://s.w.org/images/core/emoji/17.0.2/72x72/","ext":".png","svgUrl":"https://s.w.org/images/core/emoji/17.0.2/svg/","svgExt":".svg","source":{"concatemoji":"https://dx-jitsumu-labo.com/wp-includes/js/wp-emoji-release.min.js?ver=6.9.4"}}
    </script>
    <script type="module">
    /*! This file is auto-generated */
    const a=JSON.parse(document.getElementById("wp-emoji-settings").textContent),o=(window._wpemojiSettings=a,"wpEmojiSettingsSupports"),s=["flag","emoji"];function i(e){try{var t={supportTests:e,timestamp:(new Date).valueOf()};sessionStorage.setItem(o,JSON.stringify(t))}catch(e){}}function c(e,t,n){e.clearRect(0,0,e.canvas.width,e.canvas.height),e.fillText(t,0,0);t=new Uint32Array(e.getImageData(0,0,e.canvas.width,e.canvas.height).data);e.clearRect(0,0,e.canvas.width,e.canvas.height),e.fillText(n,0,0);const a=new Uint32Array(e.getImageData(0,0,e.canvas.width,e.canvas.height).data);return t.every((e,t)=>e===a[t])}function p(e,t){e.clearRect(0,0,e.canvas.width,e.canvas.height),e.fillText(t,0,0);var n=e.getImageData(16,16,1,1);for(let e=0;e<n.data.length;e++)if(0!==n.data[e])return!1;return!0}function u(e,t,n,a){switch(t){case"flag":return n(e,"\ud83c\udff3\ufe0f\u200d\u26a7\ufe0f","\ud83c\udff3\ufe0f\u200b\u26a7\ufe0f")?!1:!n(e,"\ud83c\udde8\ud83c\uddf6","\ud83c\udde8\u200b\ud83c\uddf6")&&!n(e,"\ud83c\udff4\udb40\udc67\udb40\udc62\udb40\udc65\udb40\udc6e\udb40\udc67\udb40\udc7f","\ud83c\udff4\u200b\udb40\udc67\u200b\udb40\udc62\u200b\udb40\udc65\u200b\udb40\udc6e\u200b\udb40\udc67\u200b\udb40\udc7f");case"emoji":return!a(e,"\ud83e\u1fac8")}return!1}function f(e,t,n,a){let r;const o=(r="undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?new OffscreenCanvas(300,150):document.createElement("canvas")).getContext("2d",{willReadFrequently:!0}),s=(o.textBaseline="top",o.font="600 32px Arial",{});return e.forEach(e=>{s[e]=t(o,e,n,a)}),s}function r(e){var t=document.createElement("script");t.src=e,t.defer=!0,document.head.appendChild(t)}a.supports={everything:!0,everythingExceptFlag:!0},new Promise(t=>{let n=function(){try{var e=JSON.parse(sessionStorage.getItem(o));if("object"==typeof e&&"number"==typeof e.timestamp&&(new Date).valueOf()<e.timestamp+604800&&"object"==typeof e.supportTests)return e.supportTests}catch(e){}return null}();if(!n){if("undefined"!=typeof Worker&&"undefined"!=typeof OffscreenCanvas&&"undefined"!=typeof URL&&URL.createObjectURL&&"undefined"!=typeof Blob)try{var e="postMessage("+f.toString()+"("+[JSON.stringify(s),u.toString(),c.toString(),p.toString()].join(",")+"));",a=new Blob([e],{type:"text/javascript"});const r=new Worker(URL.createObjectURL(a),{name:"wpTestEmojiSupports"});return void(r.onmessage=e=>{i(n=e.data),r.terminate(),t(n)})}catch(e){}i(n=f(s,u,c,p))}t(n)}).then(e=>{for(const n in e)a.supports[n]=e[n],a.supports.everything=a.supports.everything&&a.supports[n],"flag"!==n&&(a.supports.everythingExceptFlag=a.supports.everythingExceptFlag&&a.supports[n]);var t;a.supports.everythingExceptFlag=a.supports.everythingExceptFlag&&!a.supports.flag,a.supports.everything||((t=a.source||{}).concatemoji?r(t.concatemoji):t.wpemoji&&t.twemoji&&(r(t.twemoji),r(t.wpemoji)))});
    //# sourceURL=https://dx-jitsumu-labo.com/wp-includes/js/wp-emoji-loader.min.js
    </script>
    </body>
    </html>