ChatGPTやClaude Codeに「収集した記事を毎日Geminiで要約するPythonスクリプト」を頼むと、今でも import google.generativeai as genai から始まるコードが出てきます。この旧SDKは2025年11月末でサポートが終わっています。動いたとしても、いつ止まるか分からない土台の上に毎日の処理を載せることになります。この記事では、AIが出した旧Gemini SDKのコードを google-genai の genai.Client() と client.models.generate_content() へ直させる指示文を、実物で示します。あわせて、直す前に自分で見つけるべき危うい箇所と、要約を毎日止めずに回すための置き場所までを順に説明します。
google.generativeai と google-genai の違い:AIが旧SDKを書く理由と見分け方
生成AIは学習した時点のコードを元に書きます。Gemini APIのPython向けライブラリは、旧来の google-generativeai から新しい google-genai へ切り替わりました。ただ、ネット上には旧SDKのサンプルが大量に残っているため、AIは今でも旧SDKの書き方を出しがちです。
旧SDKは2025年11月末でサポートが終了しています。不具合が見つかっても直らず、新しいモデルや機能への対応も入りません。毎日動かすスクリプトの土台としては使えない、と考えてください。
新旧の書き方の対応表
項目 | 以前の書き方(旧SDK) | 今の書き方(google-genai) |
|---|---|---|
インストール |
|
|
import |
|
|
APIキーの設定 |
|
|
モデルの指定 |
| 呼び出しごとに |
生成の呼び出し |
|
|
生成時の設定 |
|
|
APIのエラー | 個別に捕まえる書き方がまちまち |
|
結果のテキストを response.text で取り出す点は、新旧で変わりません。そのため「importと呼び出しの数行だけ直せば済む」ように見えます。実際には、APIキーの渡し方とモデルの指定場所が変わっています。AIに部分的に直させると、新旧が混ざった動かないコードになりやすいので注意してください。
同じ「古いAPIをAIに直させる」構図はOpenAI側でも起きています。OpenAIを使っている場合は ChatCompletion廃止をAIに直させる|26年10月 も参考になります。
AIへの最初の指示と、出てきたコードで確認すべき5箇所
最初の指示(よくある頼み方)
コードを書き慣れていない人が最初に出す指示は、だいたい次のような形です。
ニュースサイトの記事を集めて、Geminiで毎日要約するPythonスクリプトを書いてください。
要約は3行くらいで、結果は表示してくれればいいです。出てきがちなコード
この指示に対して、AIは次のようなコードを返すことがあります(説明用に簡略化しています)。
import google.generativeai as genai
import requests
from bs4 import BeautifulSoup
genai.configure(api_key="ここにAPIキーを貼る")
model = genai.GenerativeModel("gemini-pro")
urls = ["https://example.com/news/1", "https://example.com/news/2"]
for url in urls:
try:
html = requests.get(url).text
text = BeautifulSoup(html, "html.parser").get_text()
res = model.generate_content("次の記事を3行で要約して:\n" + text)
print(res.text)
except:
pass手元で1回試すだけなら動くこともあります。ただし、毎日動かす前提で見ると危うい箇所が5つあります。
自分で見つけるためのチェック表
コードが読めなくても、特定の文字列があるかどうかなら確認できます。エディタの検索で次の文字列を探してください。
探す文字列 | 何が危ういか | どう直させるか |
|---|---|---|
| サポートが終わった旧SDK | google-genai の |
| APIキーがファイルに直書きされている。共有やバックアップのたびに漏れる経路になる | 環境変数から読む形にさせる |
| 失敗しても何も記録されず、毎日「動いたつもり」で中身が空になる | ログに残し、全件失敗なら終了コード1で終わらせる |
| 相手が応答しないと、そのまま永久に待ち続ける |
|
ループ内に | 取得先へ連続でアクセスし、負荷をかける | 1件ごとに間隔を空けさせる |
Linux・Macなら、ターミナルで次のように実行すると該当行が行番号付きで出ます。
grep -nE "google.generativeai|configure|api_key=|except|timeout=|time.sleep" daily_summary.pyもう1つ注意したいのが "gemini-pro" のようなモデル名です。AIは学習時点のモデル名をそのまま書きます。2026年10月2日に「Gemini 2.5 Flash Image」が非推奨になって後継への移行が求められたように、モデル名は1年も経たずに入れ替わります。モデル名はコードに埋め込まず、外から渡す形にしておくと、入れ替わりのたびにコードを直さずに済みます。使えるモデル名は、実行する時点でGoogleの公式ドキュメントのモデル一覧で確認してください。
google-genaiのgenai.Client()で書き直させる指示文と完成コード
直させる指示(そのまま貼れる形)
「新しいSDKにして」とだけ頼むと、importだけ変わって残りが旧SDKのまま、ということが起きます。変えてほしい点を箇条書きで全部渡すのがコツです。
このスクリプトを次の条件で書き直してください。
1. Gemini のライブラリは google-generativeai ではなく google-genai を使う。
from google import genai / client = genai.Client() / client.models.generate_content(model=..., contents=...) の形にする。
旧SDKの書き方(genai.configure, GenerativeModel)は一切残さない。
2. APIキーはコードに書かず、環境変数 GEMINI_API_KEY から読む。
モデル名も環境変数 GEMINI_MODEL で渡し、どちらかが無ければログに書いて終了コード1で終わる。
3. 記事一覧は RSS(https://example.com/feed.xml)から取る。1回の実行で最大10件まで。
4. 取得前に robots.txt を確認し、不許可のURLは取りに行かない。
記事の取得は1件ごとに5秒以上空ける。requests には timeout を付け、User-Agent を明示する。
5. Gemini が 429 や 503 を返したら60秒待って2回まで再試行する。それ以外のエラーは再試行しない。
6. except: pass は使わない。失敗はURLとエラー内容を logging でファイルに残す。
1件も要約できなかったら終了コード1で終わる(cron や systemd から失敗が分かるように)。
7. 結果は スクリプトと同じフォルダの summaries/日付.md に UTF-8 で保存する。パスは __file__ 基準の絶対パスにする。
8. 処理のまとまりごとに関数に分け、何をしているかコメントを付ける。6と7は「毎日動かす」ための条件です。手元で1回動かすだけなら不要ですが、cronやsystemdから起動されたとき、黙って空振りしないようにする効果があります。
直った後のコード
上の指示で出てくるべき形を、そのまま動く状態で載せます。AIの出力と見比べる基準にしてください。
#!/usr/bin/env python3
"""RSSで集めた記事を Gemini で要約し、日付ごとのファイルに残す"""
import logging
import os
import sys
import time
import xml.etree.ElementTree as ET
from datetime import date
from pathlib import Path
from urllib import robotparser
from urllib.parse import urlparse
import requests
from bs4 import BeautifulSoup
from google import genai
from google.genai import errors, types
# ---- 設定 ----
FEED_URL = "https://example.com/feed.xml"
USER_AGENT = "daily-summary-bot/1.0 (+https://example.com/about)"
INTERVAL_SEC = 5
MAX_ARTICLES = 10
BASE_DIR = Path(__file__).resolve().parent
OUT_DIR = BASE_DIR / "summaries"
LOG_FILE = BASE_DIR / "daily_summary.log"
logging.basicConfig(
filename=LOG_FILE,
level=logging.INFO,
format="%(asctime)s %(levelname)s %(message)s",
encoding="utf-8",
)
# ---- 取得先への配慮 ----
def allowed(url, cache):
"""robots.txt で取得が許されているかを、サイトごとに1回だけ読んで判定する"""
parts = urlparse(url)
root = f"{parts.scheme}://{parts.netloc}"
if root not in cache:
rp = robotparser.RobotFileParser(root + "/robots.txt")
rp.read()
cache[root] = rp
return cache[root].can_fetch(USER_AGENT, url)
def fetch(session, url):
res = session.get(url, timeout=30)
res.raise_for_status()
return res
# ---- 記事の収集 ----
def article_urls(session):
"""RSS 2.0 の item/link を上から MAX_ARTICLES 件取り出す"""
root = ET.fromstring(fetch(session, FEED_URL).content)
links = [item.findtext("link") for item in root.iter("item")]
return [u for u in links if u][:MAX_ARTICLES]
def article_text(session, url):
"""article 要素があればその中、無ければ body 全体の文字だけを取る"""
soup = BeautifulSoup(fetch(session, url).text, "html.parser")
node = soup.find("article") or soup.body
return node.get_text("\n", strip=True)[:8000] if node else ""
# ---- 要約 ----
def summarize(client, model, text):
"""429/503 のときだけ待って再試行し、それ以外はそのまま上に投げる"""
for attempt in range(3):
try:
res = client.models.generate_content(
model=model,
contents=text,
config=types.GenerateContentConfig(
system_instruction="日本語で3行に要約する。本文に書かれていないことは書かない。",
),
)
return res.text or ""
except errors.APIError as e:
if e.code in (429, 503) and attempt < 2:
logging.warning("Gemini %s のため60秒待って再試行", e.code)
time.sleep(60)
continue
raise
return ""
def main():
model = os.environ.get("GEMINI_MODEL")
if not os.environ.get("GEMINI_API_KEY") or not model:
logging.error("GEMINI_API_KEY または GEMINI_MODEL が設定されていない")
return 1
client = genai.Client() # GEMINI_API_KEY を環境変数から読む
session = requests.Session()
session.headers["User-Agent"] = USER_AGENT
try:
urls = article_urls(session)
except (requests.RequestException, ET.ParseError) as e:
logging.error("RSSの取得に失敗: %s", e)
return 1
cache, results, failed = {}, [], 0
for url in urls:
if not allowed(url, cache):
logging.info("robots.txt で不許可のため飛ばす: %s", url)
continue
try:
text = article_text(session, url)
summary = summarize(client, model, text) if text else ""
if not summary:
raise ValueError("本文または要約が空")
results.append(f"## {url}\n{summary}\n")
except (requests.RequestException, errors.APIError, ValueError) as e:
failed += 1
logging.error("失敗: %s (%s)", url, e)
time.sleep(INTERVAL_SEC)
OUT_DIR.mkdir(exist_ok=True)
out = OUT_DIR / f"{date.today():%Y%m%d}.md"
out.write_text("\n".join(results), encoding="utf-8")
logging.info("要約 %d 件・失敗 %d 件 → %s", len(results), failed, out)
return 0 if results else 1
if __name__ == "__main__":
sys.exit(main())ブロックごとの説明
- 設定:取得先、間隔、件数、保存先をファイル冒頭にまとめています。
BASE_DIRは__file__から作った絶対パスです。cronのように別のフォルダから起動されても、保存先がずれません。 - logging.basicConfig:
printの代わりにログファイルへ時刻付きで書きます。夜中に失敗しても、翌朝ログを見れば何が起きたか分かります。 - allowed():標準ライブラリの
urllib.robotparserで robots.txt を読み、そのURLを取りに行ってよいかを判定します。同じサイトの robots.txt は1回しか読みません。 - fetch():
timeout=30で応答待ちに上限を付けます。raise_for_status()は、404や500のときに例外を出させる処理です。これが無いと、エラーページを本文として要約してしまいます。 - article_urls() / article_text():RSSから記事URLを取り、ページから本文の文字だけを抜き出します。RSSを使うのは、サイト側が「更新情報はここから取ってよい」と用意している入口だからです。HTMLの一覧ページを読むより、サイトの構造変更にも強くなります。本文は8,000文字で切っています。送る量を抑えることが、そのままAPIの利用料を抑えることになるためです。
- summarize():新SDKの中心部分です。
client.models.generate_content()に、モデル名・本文・設定を渡します。要約の指示はsystem_instructionに分けて渡しています。混雑を示す429・503のときだけ待って再試行し、APIキーの誤りのように待っても直らないエラーはすぐ上に返します。 - main():設定の確認、収集、要約、保存の順に進めます。1件ずつの失敗は記録して次の記事へ進みます。1件も成功しなければ終了コード1を返すので、cronやsystemdの側から「失敗した日」が分かります。
実行環境と準備
- Python:まず
python3 -Vで手元のバージョンを確認してください。2026年10月9日に Python 3.15 が正式リリースされています。google-genai が対応しているPythonのバージョンは、PyPIの google-genai のページで確認してから仮想環境を作ってください。 - パッケージ:
pip install google-genai requests beautifulsoup4(入ったバージョンはpip show google-genaiで確認できます) - 旧SDKが残っていると、AIが書いた古いコードが偶然動いてしまい、取り違えの原因になります。不要なら
pip uninstall google-generativeaiで外しておきます。 - 文字コード:Python 3.15 ではUTF-8が既定のエンコーディングになりました。古いPythonで動かす環境もありうるので、コード側では
encoding="utf-8"を明示しています。
料金面の注意も1つあります。2026年3月以降、Gemini APIの利用料はGoogle Cloudの$300無料試用プログラムの対象外になっています。「試用クレジットがあるから無料のはず」と思い込まず、Google AI Studio側の料金と上限を事前に確認してください。料金は入力より出力のほうが単価が高い傾向があります。「3行で」のように出力を短く指示すること自体が節約になります。
取得先サイトへの配慮:robots.txt・利用規約・アクセス間隔
AIが書くスクレイピングのコードには、相手のサーバーへの配慮がほとんど入っていません。スクレイピングという技術そのものに違法性はありません。ただし、利用規約違反は民事上の契約違反の根拠になりえます。サーバーに過負荷をかければ、業務妨害に問われるリスクもあります。生成AIによる無断収集への警戒から、利用規約を厳しくするサイトも増えています。
- 利用規約を先に読む:自動取得や転載を禁じているサイトは、技術的に取れても対象にしません。要約を自分以外の人に公開する場合は、著作権の扱いも確認が必要です。
- robots.txt に従う:上のコードは
allowed()で不許可のURLを飛ばします。robots.txt の判定を外すような改造は、AIに頼まれても入れないでください。 - アクセス間隔を空ける:1件ごとに5秒、1日1回、最大10件という控えめな設定にしています。件数を増やすより先に、間隔を広げることを考えます。
- 名乗る:User-Agentに、何のための取得かが分かる文字列と連絡先のURLを入れます。ブラウザになりすます書き方はしません。
- 制限は回避しない:ログインが必要なページ、アクセス制限やbot対策がかかっているページは対象外にします。AIが回避する方法を提案してきても採用しないでください。
- 個人情報を流さない:取得した本文に個人情報が含まれる場合、それを生成AIに送ることにも注意が要ります。提供元が入力を学習に使う設定なら、個人データを提供したことになる、と個人情報保護委員会が注意喚起しています。
Geminiの毎日要約を止めずに回す置き場所:PC・共有サーバーのcron・VPSのsystemd timer
スクリプトが動いても、置く場所を間違えると「気づいたら1週間止まっていた」になります。置き場所ごとの止まる条件を先に把握してから選んでください。
置き場所ごとの止まる条件
置き場所 | 向いている条件 | 止まる条件 |
|---|---|---|
手元のPC(cron・タスクスケジューラ) | まず試す段階。1日1回、数分で終わる処理 | スリープ中・電源オフの時刻は実行されない。PCを閉じていた日はその日の分が丸ごと飛ぶ |
共有レンタルサーバーのcron | 今回のように、数分で終わる処理を1日1回回すだけの場合 | Pythonのバージョンを選べない・pipで入れられない・外部への通信や実行時間に制限がある、といったプランだと動かない。仕様は提供元の案内で確認が必要 |
VPS(systemd timer) | ブラウザ自動操作のように重い処理、件数が多い処理、自分で環境を決めたい場合 | OS更新後の再起動、ディスクの空き不足、契約の更新漏れ。管理はすべて自分の責任になる |
今回のスクリプトは、requestsで数件取ってAPIを数回呼ぶだけの短い処理です。cronが使える共有サーバーで十分です。PlaywrightやSeleniumでブラウザを動かす処理に広げるなら、メモリや追加パッケージの都合でVPSが必要になります。
cron で動かす場合
cronは、ログイン時に読み込まれる .bashrc などを読みません。そのため、APIキーの環境変数が渡らずに失敗しがちです。キーは権限600の別ファイルに置き、起動用のシェルスクリプトで読み込ませます。
# /home/username/.config/daily_summary.env (chmod 600 にしておく)
GEMINI_API_KEY=ここにAPIキー
GEMINI_MODEL=ここに公式一覧で確認したモデル名#!/bin/sh
# /home/username/daily_summary/run.sh
set -a
. /home/username/.config/daily_summary.env
set +a
cd /home/username/daily_summary
exec /home/username/daily_summary/.venv/bin/python daily_summary.py# crontab -e で追加(毎日7:00)
0 7 * * * /home/username/daily_summary/run.sh >> /home/username/daily_summary/cron.log 2>&1Pythonは仮想環境(.venv)の中のものを絶対パスで指定します。単に python3 と書くと、cronが見ているPATHにある別のPythonが使われます。そのPythonには google-genai が入っていない、というのがよくある失敗です。
VPS で systemd timer を使う場合
systemd timerは、サーバーが止まっていた間に逃した回を、起動後にまとめて実行できます(Persistent=true)。cronより取りこぼしが少なくなります。
# ~/.config/systemd/user/daily-summary.service
[Unit]
Description=Daily article summary with Gemini
[Service]
Type=oneshot
WorkingDirectory=/home/username/daily_summary
EnvironmentFile=/home/username/.config/daily_summary.env
ExecStart=/home/username/daily_summary/.venv/bin/python daily_summary.py# ~/.config/systemd/user/daily-summary.timer
[Timer]
OnCalendar=*-*-* 07:00
Persistent=true
[Install]
WantedBy=timers.targetsystemctl --user daemon-reload
systemctl --user enable --now daily-summary.timer
loginctl enable-linger username # ログアウト中もユーザーのtimerを動かす
systemctl --user list-timers # 次回の実行予定が出ているか確認
journalctl --user -u daily-summary.service # 実行ログ失敗に気づける形にしておく
終了コード1で終わるようにしても、誰も見ていなければ止まっていないのと同じです。最低限、次の3つを習慣にしてください。
- 成果物で確認する:
summaries/に今日の日付のファイルがあるか、中身が空でないかを見ます。「実行された」より「結果がある」で判断します。 - timerが有効なままか確かめる:
systemctl --user list-timersに載っていなければ、処理は一度も起動していません。この場合はエラーも出ないので、定期的に見る必要があります。 - 失敗を通知する:最後に、失敗件数が0でなければSlackなどへ送る処理を足します。通知の仕組みをAIに書き直させる手順は LINE Notify代替をAIで書き直す|26年10月 にまとめています。
関連する選択肢
毎日決まった時刻にスクリプトを動かすだけなら、共有のレンタルサーバーでも足ります。cronが使えるプランを選べば、自分でOSを管理する必要はありません。
※ 広告を含みます(A8.net)。リンク経由でお申し込みがあった場合、手数料を受け取ることがあります。
まとめ:旧Gemini SDKのコードは「指示の箇条書き」で一度に直させる
ChatGPTやClaude Codeが出す import google.generativeai as genai は、2025年11月末でサポートが終わった旧SDKです。直すときは「新SDKにして」とだけ頼まないでください。from google import genai・genai.Client()・client.models.generate_content(model=..., contents=...) の形を名指しし、旧SDKの書き方を残さないよう指示します。そのうえで、APIキーとモデル名を環境変数から読むこと、robots.txtと取得間隔、except: pass の禁止、全件失敗時の終了コード1まで、箇条書きで一度に渡すのが確実です。AIの出力は、google.generativeai・api_key="・except・timeout=・time.sleep を検索すれば、コードが読めなくても危うい箇所を見つけられます。置き場所は、今回のような短い処理なら共有サーバーのcronで足ります。重い処理ならVPSのsystemd timerを選びます。どちらでも、今日の要約ファイルがあるかを確認できる形にしておくことが、毎日止めずに動かし続ける条件です。