PythonでGoogleフォーム自動入力&自動反映

## はじめに
初めての投稿です。エンジニアに転職を目指して学習中です。
毎日の作業を自動化・効率化にする事を考えるのが好きで、Pythonを使えばさまざまな作業を自動化できることを知り、自分でも活用してみました。
備忘録として、試行錯誤した作成過程や発生したエラーも一部記載しています。

## 概要
PythonでGoogleスプレッドシートとGoogleフォームを操作して、日報作成を自動化しました。
GUIからボタン操作でテンプレートをスプレッドシートに作成し、それをフォームに反映させる仕組みです。
備忘録として、開発中に遭遇したエラーとその解決方法も記録しています。

## 目的
毎日Googleフォームで日報を作成している作業を自動化するのが目的です。

 ## 自動化の大まかな流れ
 1. スプレッドシートにアクセス
 1. スプレッドシートにテンプレート文を構築
 1. 2の内容をGoogleフォームに反映させる

 ### 前提条件
 - OS: Mac
 - 言語: Python3
 - ファイル構成
 ```bash
 /app
├── main.py             # GUIでボタンを作り、他2つの.pyを起動
├── create_spread.py      # スプレッドシートへの自動入力
├── input.py          # Googleフォームに反映
 ```

 #### Googleフォームの入力内容
 - 名前
 - メールアドレス
 - 今日の日付
 - 開始時間
 - 終了時間
 - 今日やった事
 - 達成率
 - 所感
 - 明日やる事

## Googleスプレッドシートの操作
ライブラリ: gspread
- 用途: Googleスプレッドシートの読み書き
- 認証: Google CloudでAPIキーまたはサービスアカウントの設定が必要

### 準備
#### Step1: Python環境を確認
ターミナルで以下を実行して、Pythonが入っているか確認:
```bash
python3 --version
```
もし入っていなければ、[公式サイト](https://www.python.org/)か、Homebrewでインストール
```bash
brew install python
```

#### Step2: 仮想環境
プロジェクト用の仮想環境を作成します:
```bash
python3 -m venv venv
source venv/bin/activate
```

#### Step3: 必要なライブラリをインストール
```bash
pip install gspread oauth2client
```
これでPythonからGoogleスプレッドシートにアクセスするためのライブラリが入りました。

#### Step4: Google Cloudでサービスアカウントを作成
1. [Google Cloud Console](https://console.cloud.google.com/)にログイン
1. プロジェクトを作成 or 選択
1. 左メニューから「APIとサービス」->「ライブラリ」で「 Google Sheets API」を有効化
1. 同様に「Google Drive API」も有効化
1. 「認証情報」->「＋認証情報を作成」->「サービスアカウント」
1. サービスアカウントを作成し、JSONキーをダウンロード
このJSONファイルをPythonスクリプトと同じディレクトリに保存

#### JSONキーのダウンロード手順
Step4の内容だとJSONキーの説明が不十分のためもう少し詳しく説明します
1. [Google Cloud Console](https://console.cloud.google.com/)にアクセス->対象のプロジェクトを選択
1. 左上のメニュー（≡）→ 「IAMと管理」→「サービスアカウント」をクリック->「鍵を管理」を選択
1. 「鍵を追加」->「新しい鍵を作成」をクリック
    - キーのタイプはJSONを選択
    - 「作成」をクリックするとダウンロードされる


    :::note warn
    ダウンロードしたJSONキーの扱い方(重要)
    - セキュリティ: このJSONキーは認証情報。他人に渡さない!
    - 設置場所: Pythonスクリプトと同じフォルダに置くのが一般的
    - git管理注意: GitHubなどにアップロードしないように`.gitignore`に追加
:::
    
```bash
# .gitignoreに追加
credentials.json
*.json
```

Pythonコードに取り込むときの例
```python
creds = ServiceAccountCredentials.from_json_keyfile_name('ダウンロードしたJOSNキー', scope)
```

#### Step5: Googleスプレッドシートを共有
- 操作したいスプレッドシートを作成
- ダウンロードしたJSON内の`client_email`に書かれているメールアドレスを、そのスプレッドシートの共有相手として「閲覧者以上の権限」で追加
「閲覧者」権限だと接続失敗になるので「編集者」の権限で追加

#### Step6: Pythonコード
```python:creat_spread.py
import gspread
from oauth2client.service_account import ServiceAccountCredentials

# スコープの定義
scope = [
    'https://spreadsheets.google.com/feeds',
    'https://www.googleapis.com/auth/drive'
]

# 認証情報を取得
creds = ServiceAccountCredentials.from_json_keyfile_name('credentials.json', scope)
client = gspread.authorize(creds)

# スプレッドシートを開く（タイトル指定）
sheet = client.open('あなたのスプレッドシート名').sheet1

# データを読み込み
print(sheet.get_all_records())

# 書き込み例
sheet.update_cell(2, 3, '書き込み成功')
```
実行してシート1のB3に「書き込み成功」と表示されていれば成功です。

注意点：私が作ったコードでは次のエラーがでた
```bash
Traceback (most recent call last):
  File "/Users/Desktop/app_python/gspread.py", line 1, in <module>
    import gspread
  File "/Users/Desktop/app_python/gspread.py", line 12, in <module>
    client = gspread.authorize(creds)
             ^^^^^^^^^^^^^^^^^
AttributeError: module 'gspread' has no attribute 'authorize' (consider renaming '/Users/Desktop/app_python/gspread.py' if it has the same name as a library you intended to import)
```
- 原因:
スクリプトのファイル名が`gspread.py`となっており、Pythonが標準ライブラリの`gspread`ではなく自分自身のスクリプトをモジュールとして読み込もうとしてしまったためです。
そのため、`gspread`ライブラリが読み込めず、エラーが出る
- 解決方法:
ファイル名を別の名前`create_spread.py`に変更。

その他ファイル名や、変数名を、ライブラリやモジュール名にして同様のエラーが出たので **命名**に注意!

次は日報のテンプレートを作成

### スプレッドシートにテンプレートを作成
- 手順
    1. Google Spreadsheet内に新しいシートを作成
    1. A列に項目を入力
  
- 完成イメージ
```text:スプレッドシート
A1: 名前
A2: メールアドレス
A3: 今日の日付
A4: 開始時間
A5: 終了時間
A6: 今日やったこと
A7: 達成率
A8: 所感
A9: 明日やること
```

- Pythonコード(gspread + datetime)
```python
import gspread
from oauth2client.service_account import ServiceAccountCredentials
from datetime import datetime

# 認証
scope = ["https://spreadsheets.google.com/feeds", "https://www.googleapis.com/auth/drive"]
creds = ServiceAccountCredentials.from_json_keyfile_name("credentials.json", scope)
client = gspread.authorize(creds)

# スプレッドシートのIDを指定（URLから取得）
SPREADSHEET_ID = "あなたのスプレッドシートのID"  # https://docs.google.com/spreadsheets/d/ここがID/edit

# スプレッドシートを開く
spreadsheet = client.open_by_key(SPREADSHEET_ID)

# 今日の日付を使ってシート名を作成
today = datetime.today().strftime("%Y-%m-%d")  # 例: '2025-06-04'

# 新しいシートを作成（20行×10列）
worksheet = spreadsheet.add_worksheet(title=today, rows="20", cols="10")

# A列にテンプレ項目を入力
items = ["名前", "メールアドレス", "今日の日付", "開始時間", "終了時間", "今日やったこと", "達成率", "所感", "明日やること"]
for i, item in enumerate(items, start=1):
    worksheet.update_cell(i, 1, item)  # i行目・1列目（A列）

print(f"{today} シートを作成し、項目を追加しました。")
```
補足
- spreadsheet.add_worksheet(...)でシートを追加
- すでに同じ日付のシートがあるとエラーになる

### B列にテンプレートor初期値を設定
イメージ
>A列: 項目名
>B列: テンプレート or 手入力欄

|A列(項目) |B列(テンプレートor初期値) |
|:-:|:-:|
|名前   | name(固定)  |
|メールアドレス   | mail(固定)  |
|今日の日付   |今日の日(例: 2025/06/04)   |
|開始時間   |10:00(初期値)   |
|終了時間   |18:00(初期値)   |
|今日やったこと   |都度手入力   |
|達成率   |5(初期値)   |
|所感   |都度手入力   |
|明日やること   |都度手入力   |

- Pythonコード(テンプレート)
```python:create_spread.py
import gspread
from oauth2client.service_account import ServiceAccountCredentials
from datetime import datetime

# 認証処理
scope = ["https://spreadsheets.google.com/feeds", "https://www.googleapis.com/auth/drive"]
creds = ServiceAccountCredentials.from_json_keyfile_name("credentials.json", scope)
client = gspread.authorize(creds)

# スプレッドシートIDを指定
SPREADSHEET_ID = "スプレッドシートID"
spreadsheet = client.open_by_key(SPREADSHEET_ID)

# 今日の日付を取得
today = datetime.today()
today_str = today.strftime("%Y-%m-%d")
year = today.strftime("%Y")
month = today.strftime("%m")
day = today.strftime("%d")

# 新しいシート作成
worksheet = spreadsheet.add_worksheet(title=today_str, rows="30", cols="10")

# A列（項目）とB列（テンプレート）のリスト
items = [
    ("名前", "山田太郎"),
    ("メールアドレス", "test@example.com"),
    ("今日の日付", "2025-06-05"),
    ("開始時間", "15:00"),
    ("終了時間", "18:00"),
    ("今日やったこと", ""),
    ("達成率", ""),
    ("所感", ""),
    ("明日やること", ""),
]

# シートにA列とB列を書き込む
for i, (label, value) in enumerate(items, start=1):
    worksheet.update_cell(i, 1, label)  # A列
    worksheet.update_cell(i, 2, value)  # B列

print(f"{today_str} シートを作成し、テンプレートを追加しました。")
```

ここまでは問題なく動作できた。

#### セル内に改行をする
```text
項目: 今日やったこと
テンプレート文:
【やる事】
【予定時間】
【実際にかかった時間】
【残り時間】
```
上記のテンプレート文を作る際に改行`\n`を入れたが改行されなかった。

##### 原因: セルの改行が文字列として認識されていない
`gpsread.update_cell()`に`\n`を渡すと今回のように反映されないことがある。
Google Sheetsでは明示的に改行コードを含んだ文字列を渡す必要がある

##### 解決策: `update_cell()`の代わりに`update()`を使う
失敗例:
```python:create_spread.py
# 12列目に項目とテンプレートを追加(1つのセルに複数行)
worksheet.update_cell('A12', '今日やったこと')
worksheet.update_cell('B12', '【やる事】\n【予定時間】\n【実際にかかった時間】\n【残り時間】\n', )
```

改良後:
```python:create_spread.py
worksheet.update('A12', '今日やったこと')
template_text = (
    "【やる事】\n",
    "【予定時間】\n",
    "【実際にかかった時間】\n",
    "【残り時間】\n",
)
worksheet.update('B12', 'template_text')
```
改良後のコードに修正したらスクリプトが実行されずエラーになった

#### エラー文
```bash
Traceback (most recent call last):
  File "/Users/Desktop/app_python/create_spread.py", line 59, in <module>
    worksheet.update('A12', '今日やったこと')
    ~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/Desktop/app_python/venv/lib/python3.13/site-packages/gspread/worksheet.py", line 1246, in update
    response = self.client.values_update(
        self.spreadsheet_id,
    ...<2 lines>...
        body={"values": values, "majorDimension": major_dimension},
    )
  File "/Users/Desktop/app_python/venv/lib/python3.13/site-packages/gspread/http_client.py", line 173, in values_update
    r = self.request("put", url, params=params, json=body)
  File "/Users/Desktop/app_python/venv/lib/python3.13/site-packages/gspread/http_client.py", line 128, in request
    raise APIError(response)
gspread.exceptions.APIError: APIError: [400]: Invalid value at 'data.values' (type.googleapis.com/google.protobuf.ListValue), "A12"
```

#### 原因
 `workshee.update('A12', '今日やったこと')`
 ここの書き方は**単一のセルに1つの値を入れるつもりでも`gspread.update()`は**2次元配列(リストのリスト)**形式での指定が必要。

つまり、次のように`A12`に`今日やったこと`を書こうとしても。
```python
worksheet.update('A12', '今日やったこと') # これはNG
```
`gepread`は `今日やったこと`をリストではない「ただの文字列」と認識して、エラーになる。
解決策: 値はリスト(2次元配列)として渡す
```python
worksheet.update('A12', [['今日やったこと']])
```
そして、B12に改行入りのテンプレートを入れる場合も同様に:
```python
template_text = [[(
    "やる事】\n【予定時間】\n【実際にかかった時間】\n【残り時間】\n"    
)]]
worksheet.update('B12', template_text)
```
上記の内容で修正したら無事反映されたが、コマンド実行時に警告文が出た。
```bash
/Users/Desktop/app_python/spread_test.py:59: DeprecationWarning: The order of arguments in worksheet.update() has changed. Please pass values first and range_name secondor used named arguments (range_name=, values=)
  worksheet.update('A12', [['本日の訓練内容']])
/Users/Desktop/app_python/create_spread.py:67: DeprecationWarning: The order of arguments in worksheet.update() has changed. Please pass values first and range_name secondor used named arguments (range_name=, values=)
  worksheet.update('B12', template_text)
```

#### 警告文の内容(日本語訳)
> `worksheet.update()`の引数の順番が変わったので、今後は
> - 値を　**先に**
> - セルの範囲を**後に**渡してください
> もしくはキーワード引数を使ってください

#### 修正前(警告文が出る書き方)
```python
worksheet.update('A12', [['今日やること']])
```

#### 修正後(推奨される書き方)
```python
worksheet.update([['今日やったこと']], 'A12')  # ✅ 引数の順序を修正
```

#### エラーの結論
- 今すぐ動作に支障はない(エラーではない)
- ただし将来的なバージョンでエラーになる可能性があるため、引数の順序を修正しておくのがベスト

### セルにドロップダウンをつける
データ検証ルール(Data Validation)を追加
`gspread`単体では実装できないが、`gspread-formatting`ライブラリで使うことができます。

#### 準備
`gspread-formatting`をインストールする
```bash
pip install gspread-formatting
```

#### 実装内容
例えば「A16に『達成率』、B16に１〜５のドロップダウン(初期値5)」を設定。

```python:create_spread.py
import gspread
from oauth2cilent.service_acount import ServiceAccountCredentials
from gspread_formatting import DataVakidationRule, BooleanCondition, set_data_validation_for_cell

# A16に「項目名」,B16にドロップダウンと初期値
worksheet.update([['達成率']], 'A16')
worksheet.update([['5']], 'B16') # 初期値

# ドロップダウン(1~5)をB16に追加
rule = DataValidationRule(
    condition=BooleanCondition('ONE_OF_LIST', ['1', '2', '3', '4' , '5']),
    showCustimUi=True,
    strict=True
)

set_data_validation_for_cell(worksheet, 'B16', 'rule')
```
ここまで時に問題なくできた

## スプレッドシートの内容をGoogleフォームに反映
方法は２つ

### 方法1: 事前入力URLで「入力済み状態のページ」を開く
この方法は「事前入力」までで、**自動送信されません**
ただし、Pythonで自動でブラウザを開くことも可能

### 方法2: フォームを自動送信する(Selenium使用)
もし「送信ボタン」までPythonで自動化したい場合は、Seleniumを使います。

今回は動作を見たい、入力の確認をしたいため**方法1**で実装

#### 必要な物
- Googleフォーム項目の`entry ID`
    例: `entry.1234`
- GoogleフォームURL

コード例
```python:create_spread.py
import webbrowser
import urllib.parse

form_url = "GoogleフォームURLをここに入力"

params = {
    "entry.1234567890": "山田太郎",
    "entry.9876543210": "test@example.com",
    "entry.1122334455": "2025-06-05"
}

url_with_params = form_url + "?" + urllib.parse.urlencode(params)

webbrowser.open(url_with_params)
```

ポイント
- `entry.1234567890`の数字の部分を、調べたIDに置き換えます
- 実行するとブラウザが開き、フォームに値が自動入力された状態になります(手動送信が必要)

### スプレッドシートに下書きした内容を反映させる

スプレッドシート構成
|  | A(項目) | B(記入例) |
|:-:|:-:|:-:|
| 1  | 名前  | 山田太郎  |
| 2  | メールアドレス  | test@example.com   |
| 3  | 西暦  | 2025  |
| 4  | 月  | 6  |
| 5  | 日  | 5  |
| 6  | 開始(時間)  | 10  |
| 7  | 開始(分)  | 00  |
| 8  | 終了(時間)  | 18  |
| 9  | 終了(分)  | 00  |
| 10  | 今日やったこと  |  Python基礎  |
| 11  | 達成率  | 5  |
| 12  | 所感  | type()でデータの型を確認できる  |
| 13  | 明日やること  | Python応用  |

日付と時間は`entry`タグが西暦、月、日、時間、分でそれぞれ別に設定されていたため分けて入力しました

#### 実装ステップ
```python:input.py
import gspread
from oauth2client.service_acount import ServiceAvvountCredentials
import urllib.parse
import webbrowser

# 1.スプレッドシートに接続
scope = [
    "https://spreadsheets.google.com/feeds",
    "heepts://www.googleapis.com/auth/drive"
]
creds = ServiceAccountCredentials.from_json_keyfile_name("JSONキー", scope)
client = gspread.authorize(creds)

sheet = client.open("ファイル名").worksheet("2025-06-05") # ワークシート名などは適宜変更

# 2.B列の値を辞書として取得
items = sheet.col_values(1)
values = sheet.col_values(2)

# A列:B列を辞書に(空の項目じゃスキップ)
form_data = {key: value for key, value in zip(items, values) if key and value}

# 3.フォームのentry ID マッピング
# entryIDは検証ツールで確認済みのものを
entry_map = {
    "名前": "entry.0000000001",
    "メールアドレス": "emailAdress",
    "西暦": "entry.0000000002_year",
    "月": "entry.0000000003_month",
    "日": "entry.0000000004_day",
    "時間": "entry.0000000005_hour",
    "分": "entry.0000000006_minute",
    "時間": "entry.0000000007_hour",
    "分": "entry.0000000008_minute",
    "今日やったこと": "entry.0000000009",
    "達成率": "entry.0000000010",
    "所感": "entry.0000000011",
    "明日やる事": "entry.0000000012",
}

# 4.entry.xxxにマッピングしてURL用のパラメータ辞書に変換
params = {
    entry_map[key]: value
    for key, value in form_data.items()
    if key in entry_map
}

# 5.URL生成&ブラウザで開く
form_url = "GoogleフォームURL"
url_with_params = form_url + "?" + urllib.parse.urlencode(params)
webbrowser.open(url_with_params)
```

#### 補足ポイント
- col_values(n)でスプレッドシートの列をリストで取得可能
- `entry_map`のキーはA列の値と一致させるように
- スプレッドシートのデータ行数に応じてエラーになる場合は`range`の行数を調整

#### 問題点
実行をするとエラーは出ないが、「開始時間」、「終了時間」が反映されない

#### 結論: 「項目名」が重複しているため
問題の部分
```python
    # 開始時間
    "時間": "entry.0000000005_hour",
    "分": "entry.0000000006_minute",
    # 終了時間
    "時間": "entry.0000000007_hour",
    "分": "entry.0000000008_minute",
```
->Pythonの辞書(disc)ではキーは一意なので、"時間"や"分"が**後から上書きされて**。終了時間しか残らなくなる

#### 解決策: 開始・終了時間に別のキー名をつける
```python
entry_map = {
    "名前": "entry.0000000001",
    "メールアドレス": "emailAdress",
    "西暦": "entry.0000000002_year",
    "月": "entry.0000000003_month",
    "日": "entry.0000000004_day",
    "開始(時間)": "entry.0000000005_hour",
    "開始(分)": "entry.0000000006_minute",
    "終了(時間)": "entry.0000000007_hour",
    "終了(分)": "entry.0000000008_minute",
    "今日やったこと": "entry.0000000009",
    "達成率": "entry.0000000010",
    "所感": "entry.0000000011",
    "明日やる事": "entry.0000000012",
}
```

#### スプレッドシートのA列を修正(コードと同じにする)
| A列 | B列 |
|:-:|:-:|
| 名前 | 省略  |
| メールアドレス  | 省略|
| 西暦  | 省略  |
| 月  | 省略  |
| 日  | 省略  |
| 開始(時間)  | 省略  |
| 開始(分)  | 省略  |
| 終了(時間)  | 省略  |
| 終了(分)  | 省略  |
| 省略  | 省略  |

A列の項目名も参照しているのでPythonのコードの項目名と揃える
そうしない場合は読み込まなくなるので注意！

#### ここまでで一旦動作はできる状態になった
使い方
1. `create_spread.py`を実行してスプレッドシートにテンプレート作成
1. テンプレートの内容を入力
1. `input.py`を実行したらGoogleフォームに反映
1. 入力内容に問題なければ送信

1と3のコマンド実行が若干手間に感じるのでデスクトップアプリにして操作しやすいようにする

## 構成案 Python + Tkinter
PythonのGUIライブラリ「Tkinter」を使えばば、簡単なウィンドウ付きアプリが作れる

### 参考コード(GUIつき)
```python:main.py
import tkinter as tk
from tkinter import messagebox
import subprocess

# ボタン1: スプレッドシートを作成して入力
def run_create_sheet():
    try:
        subprocess.run(["python3", "create_spread.py"], check=True)
        messagebox.showinfo("完了", "スプレッドシートの作成しました。")
    expect Exception as e:
        messagebox.showerror("エラー", f"エラーが発生しました:\n{e}")

# ボタン2: Googleフォームに反映
def run_send_to_form():
    try:
        subprocess.run(["python3", "input.py"], check=True)
        messagebox.showinfo("完了", "フォームへの反映が完了しました。")
    except Exception as e:
        messagebox.showerror("エラー", f"エラーが発生しました:\n{e}")

# GUIウィンドウ作成
root = tk.Tk()
root.title("スプレッドシート&フォーム操作")
root.geometry("300x150")

create_spread_btn = tk.Button(root, text="スプレッドシート作成", command=run_create_sheet)
create_spread_btn.pack(pady=10)

input_btn = tk.Button(root, text="Googleフォームへ反映", command=run_send_to_form)
input_btn.pack(pady=10)

root.mainloop()
```

### まとめ
ここまでで出来たこと
- `main.py`を実行して疑似的なアプリ(GUI)を起動
- `create_spread.py`でスプレッドシート作成(ボタン押下で実行)
- `input.py`でGoogleフォームの自動入力(ボタン押下で実行)

次やること
- アプリとして作成する
    ここで説明は省略してますが、アプリを作成して実行したがエラーが起きて動作しなかった(スクリプトの読み込みに失敗?)のでそこを改善していく