ChatGPTに書かせた日次のCSV取り込みスクリプトが、ある朝から何も取り込まなくなった。原因を探すと、pandasのread_csv(date_parser=...)がpandas 3.0で削除され、TypeErrorになっていた。しかもAIのコードにはexcept Exception: passが入っていて、エラーすら表示されていなかった。この記事では、AIへの指示文を実物で見せながら、pandas date_formatへの移行とpd.to_datetime(format=...)への書き換えを進めます。そのうえで、毎朝止まらずに取り込み続けるための置き場所まで解説します。
pandas 3.0でread_csvのdate_parserは削除済み。AIは今も古い書き方を出す
以前の書き方と今の書き方
date_parserは、日付の列を1件ずつ関数に渡して変換させる引数です。pandas 2系では「将来削除する」という趣旨のFutureWarningが出るだけで、処理は動いていました。pandas 3系では引数そのものが無くなったため、渡した時点で止まります。
TypeError: read_csv() got an unexpected keyword argument 'date_parser'このメッセージは「その名前の引数は存在しない」という意味です。書式を直すのではなく、引数の名前を差し替える必要があります。
やりたいこと | 以前の書き方(AIがよく出す) | 今の書き方 |
|---|---|---|
CSV読み込み時に日付列を変換 |
|
|
書式を推測させる |
| 書式を |
読み込み後に1列だけ変換 |
|
|
行を追記 |
|
|
pandasではほかにもapplymapの改名やfillna(method=...)の非推奨化などが同じ時期に進んでいます。こちらはpandas applymap→map・ffill|26年10月にまとめています。
AIが古い書き方を出す理由
生成AIは、学習した時点のコードの書き方を再現します。ネット上にはdate_parserを使ったサンプルが大量に残っているので、「CSVを日付付きで読み込んで」と頼むと、今でもこの書き方が出てきます。
AI側の入れ替わりも速くなっています。GitHub Copilotでは2026年10月2日と10月19日に、Claude Opus 4.7・GPT-5.5・Gemini 3.5 Flashなど複数の旧モデルが提供終了になりました。モデルは数か月で替わり、出てくるコードの癖も変わります。だからこそ、出てきたコードを自分で点検する手順を持っておくほうが確実です。
ChatGPTへの最初の指示と、出てきたコードの危うい5箇所
最初の指示(よくある頼み方)
毎朝6時に https://example.com/export/daily.csv からCSVをダウンロードして、
master.csv に追記するPythonスクリプトを書いてください。
date列は 2026/10/17 の形式です。認証にはトークンが必要です。AIが出したコード
import pandas as pd
import requests
from datetime import datetime
API_TOKEN = "xxxxxxxxxxxxxxxx" # ここにトークンを入れてください
URL = "https://example.com/export/daily.csv"
def main():
try:
r = requests.get(URL, headers={"Authorization": f"Bearer {API_TOKEN}"}, verify=False)
with open("today.csv", "wb") as f:
f.write(r.content)
df = pd.read_csv(
"today.csv",
parse_dates=["date"],
date_parser=lambda x: datetime.strptime(x, "%Y/%m/%d"),
)
master = pd.read_csv("master.csv")
for _, row in df.iterrows():
master = master.append(row)
master.to_csv("master.csv", index=False)
except Exception:
pass
main()一見すると動きそうです。しかしpandas 3系ではdate_parserのTypeErrorがexcept Exception: passに飲み込まれます。その結果、何も表示されず、終了コード0で正常終了したように見えます。cronは「成功」と扱うので、取り込みが止まっていることに何日も気づけません。
自分で見つけられる確認ポイント
コードが読めなくても、エディタの検索で次の語を探せば危うい箇所はほぼ拾えます。
検索する語 | 何が起きるか | 直し方 |
|---|---|---|
| pandas 3系ではTypeErrorで止まる |
|
| pandas 2.0で削除済み。AttributeErrorになる |
|
| 失敗が見えなくなる。最も危険 | ログに残し、終了コード1で終える |
| コードを共有した時点で漏れる | 環境変数から読む |
| 証明書の検証を切る。中間者攻撃に弱くなる | 外す |
| 相手が応答しないと永久に待ち続ける |
|
| cronは別のディレクトリから起動するので、ファイルが見つからない | スクリプトの場所を基準にしたパスにする |
今回のコードはdate_parser・.append(・except: pass・トークンの直書き・verify=Falseの5つに該当します。タイムアウトと相対パスも問題です。
直させる指示文と、pandas 3.0で動く日次CSV取り込みコード
直させる指示(そのまま使える形)
「エラーが出たので直して」だけだと、AIはdate_parserだけを別の古い書き方に置き換えることがあります。実行環境のバージョンと、禁止する書き方を明示するのがコツです。
次のスクリプトを直してください。条件は以下のとおりです。
【環境】pip show pandas の結果を貼ります(ここに実際の出力を貼る)。pandas 3系です。
【必須の修正】
- read_csv の date_parser は削除されているので使わない。date_format= で書式を指定する
- 時刻を含む updated_at 列は pd.to_datetime(format="%Y/%m/%d %H:%M") で変換し、
読めない行が1行でもあれば例外にして止める
- 日付列が日付型になっていなければ例外にする(書式違いで文字列のまま残るのを防ぐ)
- DataFrame.append は使わず pd.concat。id と date が同じ行は最新で上書き
- トークンはコードに書かず環境変数 DATA_API_TOKEN から読む。未設定なら終了コード1
- verify=False を外し、timeout=30 を付ける。失敗時は60秒空けて最大3回まで
- except Exception: pass は禁止。失敗は logging でスタックトレースごと残し、終了コード1を返す
- cron から実行するので、パスはスクリプトの置き場所を基準にする
- master.csv は一時ファイルに書いてから置き換える(途中で落ちても壊れないように)
最後に、修正した箇所と理由を箇条書きで添えてください。実行環境
- Python 3系(バージョンは
python3 -Vで確認する。pandas 3系が入るかどうかはpipが判断する) pip install "pandas>=3,<4" requests- 上限の
<4を付ける理由は、次のメジャー版で今回のように引数が削除されても、ある朝突然止まらないようにするためです。版を上げるときは手元で試してから上限を外します - 入った版は
pip show pandasで確認し、pip freeze > requirements.txtで記録しておく
取り込むCSVはid,date,updated_at,valueの4列で、dateは2026/10/17、updated_atは2026/10/17 05:40の形式とします。
直った後のコード
import logging
import os
import sys
import time
from pathlib import Path
import pandas as pd
import requests
BASE_DIR = Path(__file__).resolve().parent
RAW_DIR = BASE_DIR / "raw"
MASTER = BASE_DIR / "master.csv"
URL = "https://example.com/export/daily.csv"
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s %(levelname)s %(message)s",
)
log = logging.getLogger("daily_import")
def download(token: str) -> Path:
RAW_DIR.mkdir(exist_ok=True)
headers = {
"Authorization": f"Bearer {token}",
"User-Agent": "daily-csv-import/1.0 (+https://example.com/contact)",
}
for attempt in range(1, 4):
try:
r = requests.get(URL, headers=headers, timeout=30)
r.raise_for_status()
break
except requests.RequestException as e:
log.warning("取得失敗 %d/3: %s", attempt, e)
if attempt == 3:
raise
time.sleep(60)
path = RAW_DIR / f"{pd.Timestamp.now():%Y%m%d}.csv"
path.write_bytes(r.content)
return path
def load(path: Path) -> pd.DataFrame:
df = pd.read_csv(
path,
parse_dates=["date"],
date_format="%Y/%m/%d",
encoding="utf-8",
)
if not pd.api.types.is_datetime64_any_dtype(df["date"]):
raise ValueError("date 列を日付として読めませんでした")
df["updated_at"] = pd.to_datetime(
df["updated_at"], format="%Y/%m/%d %H:%M", errors="coerce"
)
bad = int(df["updated_at"].isna().sum())
if bad:
raise ValueError(f"updated_at を読めない行が {bad} 行あります")
return df
def merge(df: pd.DataFrame) -> int:
if MASTER.exists():
master = pd.read_csv(
MASTER, parse_dates=["date", "updated_at"], date_format="ISO8601"
)
before = len(master)
merged = pd.concat([master, df], ignore_index=True)
else:
before = 0
merged = df
merged = merged.drop_duplicates(subset=["id", "date"], keep="last")
merged = merged.sort_values(["date", "id"])
tmp = MASTER.with_suffix(".tmp")
merged.to_csv(tmp, index=False)
tmp.replace(MASTER)
return len(merged) - before
def main() -> int:
token = os.environ.get("DATA_API_TOKEN")
if not token:
log.error("環境変数 DATA_API_TOKEN が未設定です")
return 1
try:
path = download(token)
df = load(path)
added = merge(df)
except Exception:
log.exception("取り込みに失敗しました")
return 1
log.info("取り込み完了: %d 行読込 / %d 行追加", len(df), added)
return 0
if __name__ == "__main__":
sys.exit(main())ブロックごとの説明
- 冒頭の定数:
BASE_DIRはスクリプト自身の置き場所です。cronがどのディレクトリから起動しても、raw/とmaster.csvを同じ場所に読み書きできます。 download():トークンは引数で受け取り、コードには書きません。timeout=30で応答しない相手を待ち続けないようにしています。raise_for_status()は、403や500のときにエラーページをCSVとして保存しないための処理です。失敗したら60秒空けて最大3回まで試し、それでも駄目なら例外を上に投げます。取得した生データは日付名でraw/に残すので、後で取り込み直せます。load():ここが今回の主役です。parse_dates=["date"]で変換対象の列を指定し、date_format="%Y/%m/%d"で書式を渡します。date_parserのように関数を1件ずつ呼ばないので、速くもなります。注意点は、書式が合わないとread_csvはエラーを出さず、列を文字列のまま残すことです。そこで日付型になったかをis_datetime64_any_dtypeで確かめ、なっていなければ止めます。pd.to_datetime(format=...):時刻付きの列は読み込み後に変換しています。errors="coerce"は、読めない値をNaT(日時の欠損値)にする指定です。そのままでは黙って欠損するので、NaTの件数を数えて1件でもあれば止めます。元データの空欄もNaTになるため、空欄が正常にありうる列ではこの判定を外してください。merge():appendの代わりにpd.concatで一度に結合します。同じidとdateの行は最新を残すので、同じ日に2回実行しても行が増えません。保存済みのmaster.csvはto_csvがISO形式(2026-10-17)で書き出すため、読み戻すときはdate_format="ISO8601"を指定します。書き込みは一時ファイルに出してから置き換えるので、途中で落ちてもmaster.csvは壊れません。main():失敗したらlog.exceptionでスタックトレースごと記録し、終了コード1を返します。これでcronやsystemdが「失敗」と認識できます。元のコードのexcept: passとの最大の違いはここです。
date_format と pd.to_datetime の使い分け
場面 | 使うもの |
|---|---|
読み込みと同時に、書式が1種類の列を変換する |
|
変換に失敗した行を数えたい・止めたい |
|
1列に複数の書式が混ざっている |
|
取得元サイトへの配慮:robots.txt・利用規約・アクセス間隔
- まず公式の手段を使う:CSVのエクスポート機能やAPIが用意されているなら、それを使います。画面をスクレイピングするより相手の負荷が小さく、仕様変更にも強くなります。
- 利用規約を読む:自動取得を禁止しているサービスは対象にしません。トークンを発行するサービスなら、利用目的や回数の上限が書かれていることが多いです。
- robots.txtを確認する:
urllib.robotparserで取得先のパスが許可されているかを確かめられます。 - 1日1回で足りるものは1日1回:再試行は60秒空けて3回までにしています。間隔を詰めたり、失敗のたびに即座に叩き直したりはしません。User-Agentには、身元が分かる文字列を入れておきます。
- 認証や回数制限をすり抜ける書き方は、AIが提案してきても採用しません。
毎朝止めずに取り込み続ける場所:手元PC・レンタルサーバーのcron・VPS
止まる条件で選ぶ
置き場所 | 向いている条件 | 止まる条件 |
|---|---|---|
手元のPC | 試運転。毎朝決まった時刻にPCが起きている | スリープ中・電源オフ中は発火しない。ノートPCを閉じた日は丸ごと飛ぶ |
共有レンタルサーバーのcron | 1日1回、数十秒で終わる取り込み | Pythonの版を自分で選べず、pandas 3系が入らないことがある。実行時間やメモリの制限で強制終了される。失敗通知を設定しないと気づけない |
VPS | 取り込み後に集計・通知まで続けて回す。版を自分で固定したい | 管理は自分の責任。ディスクの空き不足、OS更新後の再起動忘れ、契約更新の失念で止まる |
サーバーを持たない方法としてはGitHub Actionsの定期実行もあります。こちらはGitHub Actions cronスクレイピング|26年10月で詳しく書いています。
共有サーバーのcronに登録する例
先にpython3 -Vとpip show pandasをサーバー上で実行し、pandas 3系が入ることを確かめます。入らない場合は、pandas 2系の範囲でdate_format=を使えば同じコードが動きます。date_formatはpandas 2.0で追加された引数です。
DATA_API_TOKEN=ここに実際のトークン
MAILTO=you@example.com
0 6 * * * /home/username/csvjob/.venv/bin/python /home/username/csvjob/daily_import.py >> /home/username/csvjob/import.log 2>&1- Pythonは仮想環境のフルパスで指定します。
python3とだけ書くと、サーバー既定の古い版で動くことがあります。 - 終了コード1で終わったときにメールが届くかは、サーバーのcron設定によります。届かない環境では
import.logを見るか、失敗時にSlackなどへ通知する処理をAIに追加させます。
VPSならsystemd timerで「止まっていた分」も拾う
# /home/username/.config/systemd/user/csvjob.service
[Service]
Type=oneshot
WorkingDirectory=/home/username/csvjob
EnvironmentFile=/home/username/csvjob/.env
ExecStart=/home/username/csvjob/.venv/bin/python daily_import.py
# /home/username/.config/systemd/user/csvjob.timer
[Timer]
OnCalendar=*-*-* 06:00:00
Persistent=true
[Install]
WantedBy=timers.targetPersistent=trueを付けると、サーバーが止まっていて6時を逃した日も、起動した時点で1回実行されます。トークンは権限600の.envに置き、コードにもリポジトリにも入れません。ログはjournalctl --user -u csvjob.serviceで見られます。終了コード1はfailedとして記録されます。
ユーザー単位のtimerは、ログアウトすると止まります。loginctl enable-linger usernameを実行しておくと、ログアウト後も動き続けます。
関連する選択肢
毎日決まった時刻にスクリプトを動かすだけなら、共有のレンタルサーバーでも足ります。cronが使えるプランを選べば、自分でOSを管理する必要はありません。
※ 広告を含みます(A8.net)。リンク経由でお申し込みがあった場合、手数料を受け取ることがあります。
毎朝の取り込みを止めないための3つの確認
AIに書かせたCSV取り込みが止まる原因は、多くの場合ひとつに絞れます。pandas 3.0で削除されたread_csv(date_parser=...)のような古い書き方を、AIが今も出してくることです。直すときはdate_format=とpd.to_datetime(format=...)に置き換えます。加えて、日付型になったかを確かめ、読めない行があれば止める処理を入れます。指示文にはpip show pandasの結果と、禁止する書き方(date_parser・append・except: pass・直書きのトークン)を明記します。受け取ったコードは、確認ポイントの語で検索して点検してください。置き場所は止まる条件で選びます。試運転は手元のPC、短い日次処理は共有サーバーのcron、版を固定して確実に回したいならVPSのsystemd timerです。どこで動かす場合も、失敗を終了コード1で返し、それに気づける経路を用意して初めて「毎朝止まらない」状態になります。