Python・GASで業務を自動化する方法|CSV集計とフォーム通知の実践例
毎月のデータ集計や、問い合わせフォームの回答確認。同じ手順を繰り返す作業は、プログラミングで自動化しやすい仕事です。
ただし、「PythonやGASを使えば便利になる」と聞くだけでは、何から始めればよいか分かりません。
この記事では、次の2つを作ります。
- Python:複数のCSVファイルを読み、部門別の売上を集計する。
- GAS:Googleフォームに回答が届いたら、Slackへ通知する。
準備するデータ、コード、実行手順、結果の確認まで順番に進めます。
目次
PythonとGASは、何を基準に選ぶ?
最初は、処理したいデータがどこにあるかで考えると選びやすくなります。
| 比較項目 | Python | GAS |
|---|---|---|
| 得意な作業 | ファイルの一括処理、データ集計・分析など | 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.フォームと回答先を準備する
- Googleフォームを作成する。
- フォームの「回答」から、回答先のスプレッドシートを設定する。
- 回答先のスプレッドシートを開く。
- 「拡張機能」→「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
記事を検索したい方はここから!
記事を検索
関連記事や、今の内容に近いテーマをすぐに検索できます。
例: AI / 情報Ⅰ / Python / 統計 / 資格 / 学習法