毎月のデータ集計や、問い合わせフォームの回答確認。同じ手順を繰り返す作業は、プログラミングで自動化しやすい仕事です。

ただし、「PythonやGASを使えば便利になる」と聞くだけでは、何から始めればよいか分かりません。

この記事では、次の2つを作ります。

  • Python:複数のCSVファイルを読み、部門別の売上を集計する。
  • GAS:Googleフォームに回答が届いたら、Slackへ通知する。

準備するデータ、コード、実行手順、結果の確認まで順番に進めます。

目次

PythonとGASは、何を基準に選ぶ?

最初は、処理したいデータがどこにあるかで考えると選びやすくなります。

比較項目PythonGAS
得意な作業ファイルの一括処理、データ集計・分析などGoogleサービス間の連携、通知など
主な実行場所手元のPCやサーバーなどGoogleのクラウド
今回の準備Pythonの実行環境とCSVファイルGoogleアカウント、フォーム、回答先のスプレッドシート、Slack
自動実行の方法OSのスケジューラーなどを別途設定時刻やフォーム送信などのトリガーを設定

今回のPython例は、コマンドを実行すると集計する仕組みです。毎月決まった日時に動かす設定までは含みません。

GAS例は、フォームの送信をきっかけに実行します。

実践1:Pythonで複数のCSVを集計する

まずは、2つの売上ファイルを読み込み、部門ごとの合計を作ります。

追加ライブラリは使わず、Python標準のcsvモジュールで処理します。

1.入力データを用意する

作業用フォルダにinputというフォルダを作り、その中に次の2ファイルを保存してください。

april.csv

部門,売上
営業,12000
制作,8000

may.csv

部門,売上
営業,15000
制作,9000

保存形式はUTF-8にします。Excelから作る場合は、見出しと数値を入力し、「CSV UTF-8」形式で保存してください。

今回のコードには、次の入力ルールがあります。

  • 見出しは、この順番で部門,売上にする。
  • 売上は12000のように整数で入力する。
  • 桁区切りのカンマや円記号は付けない。
  • inputには、今回集計するCSVだけを入れる。

2.集計コードを保存する

inputフォルダと同じ階層に、次のコードをaggregate_sales.pyという名前で保存します。

import csv
from pathlib import Path


def summarize(input_dir, output_path):
    files = sorted(input_dir.glob("*.csv"))

    if not files:
        raise FileNotFoundError(
            "inputフォルダにCSVがありません。"
        )

    totals = {}

    for path in files:
        with path.open(
            encoding="utf-8-sig",
            newline=""
        ) as f:
            reader = csv.DictReader(f)

            if reader.fieldnames != ["部門", "売上"]:
                raise ValueError(
                    f"{path.name}: "
                    "見出しを「部門,売上」にしてください。"
                )

            for line, row in enumerate(reader, start=2):
                department = (
                    row.get("部門") or ""
                ).strip()

                try:
                    amount = int(row.get("売上") or "")
                except ValueError:
                    raise ValueError(
                        f"{path.name}の{line}行目: "
                        "売上は整数にしてください。"
                    ) from None

                if not department or None in row:
                    raise ValueError(
                        f"{path.name}の{line}行目: "
                        "部門名と列数を確認してください。"
                    )

                totals[department] = (
                    totals.get(department, 0) + amount
                )

    with output_path.open(
        "w",
        encoding="utf-8-sig",
        newline=""
    ) as f:
        writer = csv.writer(f)
        writer.writerow(["部門", "売上合計"])
        writer.writerows(sorted(totals.items()))

    print(
        f"{len(files)}ファイルを集計しました: "
        f"{output_path.name}"
    )


if __name__ == "__main__":
    base_dir = Path(__file__).resolve().parent

    summarize(
        base_dir / "input",
        base_dir / "summary.csv"
    )

コードは、Pythonファイルの保存場所を基準にinputを探します。入力用CSVは変更せず、同じ作業用フォルダにsummary.csvを作成します。

3.実行する

Pythonファイルを保存したフォルダでターミナルを開き、次を実行します。

python aggregate_sales.py

環境によっては、pythonの代わりにpython3を使います。

正常に終わると、次のメッセージが表示されます。

2ファイルを集計しました: summary.csv

出力ファイルをExcelなどで開くと、次の結果を確認できます。

部門売上合計
制作17,000
営業27,000

手計算でも、制作は8,000+9,000、営業は12,000+15,000となり、結果が一致します。

4.再実行するとどうなる?

summary.csvは、実行するたびに上書きします。

前回の集計結果へ加算する仕組みではないため、同じ入力データで再実行しても合計額は増えません。また、出力先をinputの外にしているので、集計結果を次の入力として読み込むこともありません。

ただし、同じ内容のCSVを別名でinputに入れると、その分も集計されます。 このコードは、重複する取引やファイルの判定までは行いません。

本番の売上データに使う前に、少量のデータで確認してください。

実践2:GASでフォームの回答受付をSlackへ通知する

次は、Googleフォームに回答が届いたとき、Slackへ通知する仕組みを作ります。

今回は回答本文をSlackへ転送せず、新しい回答を受け付けたことと、回答行へのリンクを送ります。

1.フォームと回答先を準備する

  1. Googleフォームを作成する。
  2. フォームの「回答」から、回答先のスプレッドシートを設定する。
  3. 回答先のスプレッドシートを開く。
  4. 「拡張機能」→「Apps Script」を開く。

今回のコードは、回答先のスプレッドシートに紐づくスクリプトとして作成します。フォーム側のスクリプトとは、受け取るイベント情報が異なります。

2.SlackのWebhook URLを用意する

SlackのIncoming Webhooksは、専用URLへメッセージを送ることで、設定したチャンネルに投稿できる仕組みです。

Slackアプリを作成し、Incoming Webhooksを有効にして、通知先チャンネル用のWebhook URLを発行します。ワークスペースの設定によっては、管理者の承認が必要です。

Webhook URLを知っている相手は投稿できるため、コードの公開場所や記事、スクリーンショットには載せないでください。

3.Webhook URLをスクリプトプロパティに登録する

Apps Scriptの「プロジェクトの設定」から「スクリプト プロパティ」を開き、次を登録します。

項目設定内容
プロパティSLACK_WEBHOOK_URL
値発行したWebhook URL

コードからは、PropertiesServiceを使って値を読み込みます。

スクリプトプロパティは、共同編集者から秘密情報を隔離する仕組みではありません。スプレッドシートとスクリプトの編集権限も、必要な人だけに限定します。

4.通知コードを保存する

Apps Scriptに、次のコードを貼り付けて保存します。

function notifySlackOnFormSubmit(e) {
  if (!e || !e.range) {
    throw new Error(
      "回答先スプレッドシートの" +
      "フォーム送信トリガーから実行してください。"
    );
  }

  const webhookUrl = PropertiesService
    .getScriptProperties()
    .getProperty("SLACK_WEBHOOK_URL");

  if (!webhookUrl) {
    throw new Error(
      "SLACK_WEBHOOK_URLを設定してください。"
    );
  }

  const sheet = e.range.getSheet();
  const row = e.range.getRow();

  const answerUrl =
    sheet.getParent().getUrl() +
    "#gid=" + sheet.getSheetId() +
    "&range=A" + row;

  const text =
    "フォームに新しい回答が届きました。\n" +
    "回答行: " + row + "\n" +
    answerUrl;

  const response = UrlFetchApp.fetch(webhookUrl, {
    method: "post",
    contentType: "application/json",
    payload: JSON.stringify({
      text: text,
      unfurl_links: false,
      unfurl_media: false
    }),
    muteHttpExceptions: true
  });

  const status = response.getResponseCode();

  if (status < 200 || status >= 300) {
    throw new Error(
      "Slack通知に失敗しました。HTTP " + status
    );
  }
}

UrlFetchAppでSlackへHTTPリクエストを送信し、成功を示すステータスコードか確認しています。

5.フォーム送信トリガーを設定する

Apps Scriptの左側にある「トリガー」から、トリガーを1つ追加します。

設定項目選択する内容
実行する関数notifySlackOnFormSubmit
実行するデプロイHead
イベントのソーススプレッドシートから
イベントの種類フォーム送信時

保存時に表示される権限の内容を確認し、承認します。

インストール型トリガーは、基本的に作成者のアカウント権限で実行されます。担当者変更時には、トリガーの管理も引き継ぐ必要があります。

6.実際にフォームを送信して確認する

フォームを回答者向けの画面で開き、テスト回答を1件送信してください。

Slackには、次の内容が届きます。

  • フォームに新しい回答が届いたというメッセージ
  • 回答が記録された行番号
  • 回答先スプレッドシートの該当行へのリンク

リンクを送っても、スプレッドシートの閲覧権限が自動で付与されるわけではありません。通知を受け取る人に、必要な権限があるか確認します。

エディタ上の「実行」ボタンでは、フォーム送信時のイベント情報が渡されません。 このコードの動作確認は、実際のフォーム送信で行ってください。

動かないときの確認ポイント

状況確認すること
PythonでCSVが見つからないinputの場所と拡張子が正しいか
Pythonで見出しのエラーが出る1行目が部門,売上になっているか
Pythonで数値のエラーが出る売上に空欄・円記号・桁区切りがないか
GASでイベント情報のエラーが出る手動実行していないか、トリガーの種類が正しいか
Slackへ通知されないWebhook URL、権限、Apps Scriptの「実行数」に表示されるエラー
Slackへ同じ通知が複数届く同じ処理のトリガーを重複登録していないか

GASのサンプルには、通知失敗時の自動再送や重複防止までは含めていません。重要な受付業務に使う場合は、回答シートを原本として残し、通知の失敗を確認できる運用を組み合わせます。

自動化しても、確認の仕組みは必要

プログラムは、入力されたデータと実装したルールに従って動きます。

そのため、集計条件を間違えれば誤った合計を繰り返し作りますし、通知先を間違えれば意図しない場所へ情報を送ってしまいます。

自動化の効果は、作業時間だけでなく、次の点も含めて確認しましょう。

  • 手作業の結果と一致するか。
  • 同じ処理を再実行しても問題がないか。
  • 不正な入力や通信失敗に気づけるか。
  • 入力形式や担当者が変わっても修正できるか。

まずは、小さな集計や通知を1つ完成させることから始めてください。入力・処理・出力の流れを理解すると、ほかの業務へも応用しやすくなります。

Learning Tools

記事を検索したい方はここから!

辞書から探す

本文中で気になった概念やキーワードを、辞書ページで一覧から確認できます。

辞書を見る