ChatGPTやClaude Codeに「価格を毎日Googleスプレッドシートに記録するPythonを書いて」と頼むと、今でもoauth2clientのServiceAccountCredentialsで認証し、worksheet.update('A1', 値)と書く古いgspreadのコードが返ってくることがあります。gspread 6系ではこの書き方のままだと動きません。この記事では、AIへの指示文を実物で示しながら、gspread.service_account()・update(values, range_name)・append_row()へ書き直させる手順と、毎日止めずに追記し続けるための置き場所までを説明します。

AIが書くgspreadの古いコード:oauth2clientとupdate('A1', 値)の2箇所

最初に出した指示文

コードを書き慣れていない人が最初に出しがちな、ごく普通の指示です。

商品ページの価格を毎日取得して、Googleスプレッドシートに
日付と価格を記録するPythonスクリプトを書いてください。

返ってきたコード(そのまま動かすと止まる例)

生成AIは学習時点の情報でコードを書くため、ネット上に大量に残っている古いサンプルの書き方が出てきやすくなります。典型的なのが次の形です。

import gspread
from oauth2client.service_account import ServiceAccountCredentials
import requests
from bs4 import BeautifulSoup
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)
sheet = client.open('価格記録').sheet1

res = requests.get('https://example.com/item/123')
soup = BeautifulSoup(res.text, 'html.parser')
price = soup.find('span', class_='price').text

sheet.update('A1', [['日付', '価格']])
sheet.append_row([str(datetime.date.today()), price])

どこが古いのか

箇所

AIが書きがちな形

今の書き方

認証

oauth2client の ServiceAccountCredentials + gspread.authorize()

gspread.service_account(filename=...) の1行

セルへの書き込み

update('A1', 値)(範囲が先)

update(values, range_name='A1')(値が先・範囲はキーワード指定)

1行追記

(これは今も使える)

append_row(値のリスト)

  • oauth2clientは、gspreadをインストールしても一緒には入りません。新しい環境では import の段階で止まります。gspreadの認証は現在 google-auth 系を使う形が前提で、gspread.service_account() がその処理をまとめて引き受けます。
  • update()の引数順は、gspread 6系で「値が第1引数、範囲が range_name」に変わりました。古い順番のまま書くと、範囲と値の解釈が食い違います。手元の版は pip show gspread で確認できます。

止まるのは認証の行なので、コードを書き慣れていない人には「スプレッドシートの設定が悪いのでは」と見えてしまい、原因にたどり着きにくいのが厄介な点です。

gspread.service_account()とappend_row()へ書き直させる指示文

直させる指示文(実物)

「エラーが出たので直して」だけでは、AIはoauth2clientを入れる手順を足すだけで終わることがあります。使う関数名と、守らせたい条件を指示文に書くのがコツです。

このスクリプトを gspread 6系の書き方に書き直してください。条件は次のとおりです。

1. 認証は oauth2client を使わず、gspread.service_account(filename=...) にする。
   鍵ファイルのパスはコードに直書きせず、環境変数 GSPREAD_KEY_FILE から読む。
2. セルへの書き込みは update(values, range_name=...) の順で、range_name はキーワード引数で渡す。
3. 毎日の記録は append_row() で1行追記する。見出し行は、シートが空のときだけ書く。
4. スプレッドシートはタイトルではなくキー(URL中のID)で開く。キーも環境変数 SPREADSHEET_KEY から読む。
5. 価格が取れなかったときは空の行を追記せず、エラーを表示して終了コード1で終わる。
   try/except で例外を握りつぶさない。
6. 取得前に robots.txt を確認し、禁止されていれば取得しない。
   複数ページを取るときは各リクエストの間に5秒以上空ける。User-Agentに連絡先を書けるようにする。
7. requests にはタイムアウトを付ける。
8. 使っているパッケージ名と pip install のコマンドを最後に書く。

ポイントは5番です。AIのコードは except Exception: pass のように失敗しても黙って次へ進む形になりがちで、毎日動かすと「動いているのに空の行だけが増える」状態になります。止めるべきときは止める、と明示しておきます。

直った後のコード

import datetime
import os
import sys
import time
import urllib.robotparser

import gspread
import requests
from bs4 import BeautifulSoup

# 1. 設定は環境変数から読む(鍵ファイルのパス・シートのIDを直書きしない)
KEY_FILE = os.environ["GSPREAD_KEY_FILE"]
SHEET_KEY = os.environ["SPREADSHEET_KEY"]
USER_AGENT = "price-logger (contact: admin@example.com)"
TARGETS = [
    "https://example.com/item/123",
    "https://example.com/item/456",
]
INTERVAL_SEC = 5


# 2. robots.txt で取得してよいURLかを確かめる
def allowed(url):
    rp = urllib.robotparser.RobotFileParser()
    rp.set_url("https://example.com/robots.txt")
    rp.read()
    return rp.can_fetch(USER_AGENT, url)


# 3. 価格を1件取る。取れなければ例外を投げて止める
def fetch_price(url):
    res = requests.get(url, headers={"User-Agent": USER_AGENT}, timeout=20)
    res.raise_for_status()
    soup = BeautifulSoup(res.text, "html.parser")
    tag = soup.select_one("span.price")
    if tag is None:
        raise ValueError(f"価格の要素が見つかりません: {url}")
    return tag.get_text(strip=True)


def main():
    # 4. 認証とシートの取得
    gc = gspread.service_account(filename=KEY_FILE)
    ws = gc.open_by_key(SHEET_KEY).sheet1

    # 5. シートが空のときだけ見出し行を書く(値が先、範囲は range_name)
    if not ws.get_all_values():
        ws.update([["日付", "URL", "価格"]], range_name="A1")

    today = datetime.date.today().isoformat()
    for i, url in enumerate(TARGETS):
        if not allowed(url):
            print(f"robots.txt で禁止されているため取得しません: {url}", file=sys.stderr)
            continue
        if i > 0:
            time.sleep(INTERVAL_SEC)
        price = fetch_price(url)
        # 6. 1行ずつ追記する
        ws.append_row([today, url, price], value_input_option="USER_ENTERED")
        print(f"{today} {url} {price}")


if __name__ == "__main__":
    try:
        main()
    except Exception as e:
        # 7. 失敗は表示して終了コード1で終える(cron・systemdが失敗として扱える)
        print(f"失敗しました: {e}", file=sys.stderr)
        sys.exit(1)

ブロックごとの説明

  1. 設定の読み込み:鍵ファイル(サービスアカウントのJSON)の場所とスプレッドシートのIDを環境変数から読みます。コードを人に見せたり、AIに貼り付けたりしても鍵の在り処が漏れません。
  2. robots.txtの確認:標準ライブラリの urllib.robotparser で、そのURLを取ってよいかを判定します。
  3. 価格の取得:timeout を付けて、応答が無いまま永久に待ち続けるのを防ぎます。raise_for_status() は404や500を例外にする関数です。価格の要素が見つからないときも例外にして、空の値を記録しないようにしています。セレクタ span.price は例なので、対象ページに合わせて変えてください。
  4. 認証:gspread.service_account(filename=...) の1行で、旧コードの scope 定義・ServiceAccountCredentials・authorize() の3段階が置き換わります。シートは名前ではなく open_by_key() で開くので、同じ名前のファイルが複数あっても取り違えません。
  5. 見出し行:update([["日付", "URL", "価格"]], range_name="A1") と、値を先に、範囲をキーワードで渡すのがgspread 6系の書き方です。値は「行のリスト」なので二重の角括弧になります。
  6. 追記:append_row() は表の末尾に1行足す関数です。value_input_option="USER_ENTERED" を付けると、手入力と同じように数値や日付として解釈されます。
  7. 失敗時の終了:例外をまとめて受け、メッセージを出してから終了コード1で終わります。握りつぶさないので、後述のcronやsystemdが「失敗した」と判断できます。

実行環境

  • Python 3(版は python3 -V で確認)
  • pip install gspread requests beautifulsoup4
  • gspreadは6系を前提にしています。インストール後に pip show gspread で Version の先頭が6であることを確かめてください。5系以前が入っていると update() の引数順の扱いが違います。環境ごと固定したい場合は requirements.txt に版を書いておくと、別のマシンへ移したときに版が変わる事故を防げます。
  • oauth2client は入れる必要がありません。AIが「oauth2clientをpip installしてください」と書いてきたら、古い書き方に戻っている合図です。

依存パッケージをスクリプトと一緒に持ち運びたい場合は、PEP723とuv run --script 2026年10月版の書き方を併用すると、置き場所を移しても同じ環境で動かせます。

AIが出したコードで自分で確認すべき4箇所

調査では、ITエンジニアの95.5%がAI生成コードにも人間のレビューが必要だと答えています。コードを書き慣れていなくても、次の4つは文字列検索だけで見つけられます。

確認箇所

検索する文字列

危うい理由

直させる一言

古いAPI

oauth2client、update('、find_element_by_

現行版のライブラリで止まる・解釈が変わる

「gspread 6系/Selenium 4の書き方で」と版を指定する

アクセス間隔が無い

sleep が無い for 文

相手のサーバーに連続アクセスする

「各リクエストの間に5秒以上空けて」

例外で黙って止まる/進む

except:、except Exception: pass

失敗しても気づけず、空データが溜まる

「例外を握りつぶさず、終了コード1で終えて」

APIキー・鍵の直書き

.json'、key=、token=

コードを共有した瞬間に漏れる

「環境変数から読む形にして」

Seleniumでも同じ構図があり、AIは今も find_element_by_id のようなSelenium 3以前の書き方を出すことがあります。Seleniumで価格を取る場合はSelenium 4で価格監視をAIに書かせる2026年9月版も参考にしてください。

新しいモデルでも「古い書き方」は消えない

2026年9月にはOpenAIのGPT-6.1 Sol(9月30日)、AnthropicのClaude Opus 5.5(9月22日)が相次いで出ています。Claude Codeには9月25日から、5時間のセッション制限に達しても区切りの良いところまで作業を続ける機能も入りました。ただ、モデルが新しくなっても、ネット上の古いサンプルの量が減るわけではありません。指示文で版と関数名を指定するほうが、モデルの選び方より確実に効きます。なおGitHub Copilotでは10月19日にGPT-5.5などの非推奨化が予定されているので、Copilot経由で使っている人は選べるモデルが変わる点も押さえておいてください。

取得先サイトへの配慮:robots.txt・利用規約・アクセス間隔

  • robots.txt:上のコードのように取得前に確認し、禁止されているパスは取りません。
  • 利用規約:自動取得を禁じているサイトもあります。規約で禁止されていれば、スクリプトを書く前に対象から外します。公式のAPIやデータ提供があるなら、そちらを使います。
  • アクセス間隔と頻度:価格の定点観測なら1日1回で足ります。複数ページは間隔を空けて順に取り、並列化はしません。短時間の大量アクセスは業務妨害として法的な問題になりうると指摘されています。
  • 取ったデータの扱い:自分で分析する範囲に留め、そのまま転載・公開しないこと。著作権法で許されるのは情報解析などの目的の範囲です。

ログインが必要なページやbot対策を突破する方法をAIに求めるのはやめてください。相手が明確に「機械で取らないで」と示している状態です。

毎日止めずに追記し続けるには:手元のPC・レンタルサーバーのcron・VPS

スクリプトが1回動いても、毎日動かなければ定点観測になりません。置き場所ごとに止まる条件が違います。

置き場所

向いている条件

止まる条件

手元のPC

数日だけ試す、記録が飛んでも困らない

スリープ・電源オフ・PCを閉じている時間帯は実行されない。Macのlaunchdは開いたときにまとめて動くだけで、指定時刻には動かない

共有レンタルサーバーのcron

1日1回・数十秒で終わる処理。今回のスクリプトはこれで足りる

実行時間・メモリの上限に当たると強制終了される。pipで入れられるパッケージや対応するPythonの版がサーバー側で決まっている。ブラウザを動かすSeleniumは使えないことが多い

VPS(自分で管理するサーバー)

Seleniumを使う、取得件数が多い、常駐させたい

OS更新・再起動後にタイマーが外れる、ディスクが一杯になる、契約や支払いの切れ目。管理は自分の仕事になる

cronで1日1回動かす例(レンタルサーバー・VPS共通)

GSPREAD_KEY_FILE=/home/username/.config/price-log/sa.json
SPREADSHEET_KEY=xxxxxxxxxxxxxxxxxxxx
0 9 * * * cd /home/username/price-log && /usr/bin/python3 price_log.py >> log.txt 2>&1

毎朝9時に実行し、成功・失敗のどちらの出力も log.txt に残します。鍵ファイルは chmod 600 にして、自分以外が読めないようにしておきます。

VPSならsystemd timerにする

# /home/username/.config/systemd/user/price-log.service
[Service]
Type=oneshot
WorkingDirectory=/home/username/price-log
EnvironmentFile=/home/username/.config/price-log/env
ExecStart=/usr/bin/python3 price_log.py

# /home/username/.config/systemd/user/price-log.timer
[Timer]
OnCalendar=*-*-* 09:00
Persistent=true

[Install]
WantedBy=timers.target
  • Persistent=true は、サーバーが止まっていて逃した回を、起動時に1回実行する設定です。
  • 有効化は systemctl --user enable --now price-log.timer、ログは journalctl --user -u price-log.service で見ます。
  • ログアウト後も動かすには loginctl enable-linger username が要ります。
  • 再起動後は systemctl --user list-timers でタイマーが残っているかを確認します。

失敗に気づける仕組みを最後に足す

上のスクリプトは失敗すると終了コード1で終わるので、「終了コードが0以外ならSlackやメールに通知する」処理を外側に足せます。AIには次のように頼めます。

price_log.py を実行して、終了コードが0以外だったときだけ
Slack の Incoming Webhook に失敗を通知するシェルスクリプトを書いてください。
Webhook のURLは環境変数 SLACK_WEBHOOK_URL から読み、コードに直書きしないでください。

もう一つ有効なのが、スプレッドシートの最終行の日付が今日かどうかを見るチェックです。通知の仕組み自体が止まっていても、シートを開けば記録が途切れた日がわかります。

関連する選択肢

毎日決まった時刻にスクリプトを動かすだけなら、共有のレンタルサーバーでも足ります。cronが使えるプランを選べば、自分でOSを管理する必要はありません。

レンタルサーバー エックスサーバー

※ 広告を含みます(A8.net)。リンク経由でお申し込みがあった場合、手数料を受け取ることがあります。

まとめ

ChatGPTやClaude Codeが書くgspreadのコードには、oauth2clientのServiceAccountCredentialsで認証する形と、update('A1', 値)の古い引数順がまだ残っています。gspread 6系では、認証を gspread.service_account() の1行にし、書き込みは update(values, range_name=...)、毎日の記録は append_row() にします。AIには「エラーを直して」ではなく、版・関数名・鍵の置き場所・失敗時の止め方まで指示文に書くと、一度で今の書き方が返ってきます。出てきたコードは、古いAPI・sleepの有無・例外の握りつぶし・鍵の直書きの4箇所を検索して確かめてください。動かし続ける場所は、1日1回の追記ならレンタルサーバーのcronで足ります。Seleniumを使う・件数が多いならVPSのsystemd timerにして、終了コードで失敗を通知する仕組みまで用意しておけば、記録が黙って途切れることを防げます。