選ぶ知識を、あなたの力に。
当サイトはプロモーションを含みます
記事一覧へPython

Pythonで株価を毎日自動取得する方法|CSV保存・重複防止・定期実行

公開 2026.09.29 約10分

Pythonとyfinanceで株価を定期取得する設計とサンプルコード。CSVの重複防止、ログ、Windowsのタスク設定、AWSへの移行時の注意点を整理します。

株価の自動取得は、「取得するPythonプログラム」と「毎日起動する設定」を組み合わせると作れます。まず手動で取得とCSV保存を確認し、その後Windowsのタスクスケジューラへ登録します。実行に失敗した日が分かるよう、ログと終了コードも残します。

本稿では、日本株1銘柄の日足を個人の学習・分析用に保存する例を扱います。売買注文は実行しません。Pythonの変数・関数とpipの基本操作が分かる方を想定しています。

株価自動取得の完成形を決める

毎回同じ処理を安全に繰り返す

今回のプログラムは、指定した開始日から前日までのデータを取り直して、銘柄別CSVを置き換える方式です。毎日1行ずつ追加する方法と比べ、同じ日付の重複や取得済み期間の訂正を扱いやすくなります。一方、長期間・大量銘柄になるほど通信量が増えるため、少数銘柄の入門用途を想定します。

処理順は「取得→空データ確認→列と日付の整形→重複除去→一時ファイル保存→CSV置き換え」です。取得できなかった場合は既存CSVを空のファイルで上書きしません。

保存対象の範囲を固定する

例では2025年1月1日以降を対象にします。これは取得範囲の例であり、分析に十分な期間を保証するものではありません。ファイル名には開始日も含め、開始日を変更したときは別のファイルになるようにします。

同じ銘柄でも、保存した時点によって提供元のデータが訂正されることがあります。過去に作成した分析を再現したい場合は、更新用CSVとは別に、分析に使用した時点のデータと取得日時を保管してください。

データの利用条件を確認する

yfinanceはYahooの公認製品ではなく、研究・教育を目的としたオープンソースのツールです。公式サイトではYahoo FinanceのAPIについて個人利用を想定する旨が案内されています。ライブラリを利用できることと、取得データを配布・商用利用できることは別なので、データそのものの利用条件も確認します。yfinance公式

Pythonと保存用フォルダを準備する

仮想環境を作成する

Windowsで作業用フォルダを用意し、そのフォルダでPowerShellを開きます。以下はPythonランチャーのpyを利用できる環境向けです。pyが見つからない場合はPythonのインストール状況を確認してください。

py -m venv .venv
.\.venv\Scripts\python.exe -m pip install yfinance pandas
.\.venv\Scripts\python.exe --version
.\.venv\Scripts\python.exe -m pip show yfinance pandas

仮想環境内のPythonを直接指定すると、普段使うPythonとタスクが使うPythonの取り違えを防げます。仮想環境の有効化を前提としないため、PowerShellの実行ポリシーを変更する必要もありません。

初回は取得できるバージョンが実行日により異なります。手動確認に成功した環境を記録し、更新後も同じ確認を行ってください。インストールできたことだけでは、株価の取得まで成功したことにはなりません。

ファイルの配置をそろえる

作業フォルダ内へ、この後のコードをdaily_prices.pyという名前で保存します。実行時に同じ場所へdataとlogsが作られます。フォルダの配置例は次のとおりです。

stock-daily/
  .venv/
  daily_prices.py
  data/
  logs/

保存先はコードの置き場所を基準に決まります。タスクスケジューラの作業フォルダが変わっても、意図せず別の場所へCSVを作るのを防ぐためです。

取得の基本操作やチャート表示から確認したい場合は、Pythonで株価を取得・保存・グラフ化する方法を先に読むと進めやすくなります。

株価を取得してCSVへ保存する完成版コード

daily_prices.pyへ保存する

次のコードは1ファイルで完結するサンプルです。対象銘柄、取得開始日、対象市場のタイムゾーンを先頭で設定します。例は日本株向けなので、米国株へ変更するときは銘柄だけでなくタイムゾーンと実行時刻も見直します。

from datetime import datetime
from logging.handlers import RotatingFileHandler
from pathlib import Path
from zoneinfo import ZoneInfo
import logging
import os
import tempfile
import time

import yfinance as yf

BASE = Path(__file__).resolve().parent
SYMBOL = "7203.T"
START = "2025-01-01"
MARKET_TZ = "Asia/Tokyo"
COLUMNS = ["Open", "High", "Low", "Close", "Volume"]


def fetch_prices():
    # endは含まれない。進行中の日足を避け、前日までを対象にする。
    end = datetime.now(ZoneInfo(MARKET_TZ)).date().isoformat()
    for attempt in range(3):
        try:
            frame = yf.Ticker(SYMBOL).history(
                start=START,
                end=end,
                interval="1d",
                auto_adjust=False,
                actions=False,
                timeout=20,
            )
            if frame.empty:
                raise ValueError("No price rows returned")
            return frame
        except Exception:
            if attempt == 2:
                raise
            logging.warning("Fetch failed; retry=%s", attempt + 1)
            time.sleep(10 * (attempt + 1))


def prepare_prices(frame):
    missing = set(COLUMNS) - set(frame.columns)
    if missing:
        raise ValueError(f"Missing columns: {sorted(missing)}")
    selected = COLUMNS + (["Adj Close"] if "Adj Close" in frame else [])
    result = frame[selected].copy()
    if result[COLUMNS].isna().any().any():
        raise ValueError("Missing OHLCV values")
    # 日足の市場日付を維持し、UTC変換で日付をずらさない。
    result.index = result.index.strftime("%Y-%m-%d")
    result.index.name = "Date"
    result = result[~result.index.duplicated(keep="last")].sort_index()
    if result.empty:
        raise ValueError("No rows after preparation")
    return result


def save_prices(frame, target):
    target.parent.mkdir(parents=True, exist_ok=True)
    temp_path = None
    try:
        with tempfile.NamedTemporaryFile(
            mode="w", encoding="utf-8-sig", newline="",
            dir=target.parent, suffix=".tmp", delete=False
        ) as handle:
            temp_path = Path(handle.name)
            frame.to_csv(handle)
        os.replace(temp_path, target)
    finally:
        if temp_path is not None and temp_path.exists():
            temp_path.unlink()


def main():
    log_dir = BASE / "logs"
    log_dir.mkdir(parents=True, exist_ok=True)
    logging.basicConfig(
        level=logging.INFO,
        format="%(asctime)s %(levelname)s %(message)s",
        handlers=[
            RotatingFileHandler(
                log_dir / "daily_prices.log", maxBytes=1_000_000,
                backupCount=3, encoding="utf-8"
            ),
            logging.StreamHandler(),
        ],
    )
    try:
        frame = prepare_prices(fetch_prices())
        safe_symbol = SYMBOL.replace(".", "_")
        target = BASE / "data" / f"{safe_symbol}_from_{START}.csv"
        save_prices(frame, target)
        logging.info(
            "Saved symbol=%s rows=%s latest=%s path=%s",
            SYMBOL, len(frame), frame.index[-1], target
        )
        return 0
    except Exception:
        logging.exception("Daily price download failed")
        return 1


if __name__ == "__main__":
    raise SystemExit(main())

変更する設定を理解する

SYMBOLは取得する銘柄、STARTは保存対象の開始日です。7203.Tは日本株の取得例で、投資先の推奨ではありません。MARKET_TZは「今日」の判定に使用します。

この例は終了日に今日を指定し、当日分を含めずに取得する設計です。当日取引中の未確定日足を混ぜることを避けるため、当日の終値をその日の夜に集めたい用途とは取得範囲が異なります。毎朝、前日までの情報で学習・分析する用途を想定してください。

auto_adjust=Falseを明示し、ライブラリの既定値だけに依存しないようにしています。ただし「無調整の取引所原票」を保証する指定ではありません。調整済み価格を分析に使う場合は、価格列の意味と提供元の調整方法を確認し、全期間で扱いを統一します。

CSVの重複と上書きをどう防ぐか

同じ日付は1行にする

prepare_pricesでは日付をキーに重複行を除去し、後から現れた行を残します。同じ日付に複数行あった場合、単純に両方を書き出すより扱いやすくなります。ただし、重複が提供元の異常を示すこともあるので、本格運用では除去前の重複件数をログに残す改善も有効です。

今回は過去CSVへ追記せず、取得範囲全体を置き換えます。同じ日に再実行しても、実行した回数だけ行が増える仕組みにはなりません。「同じ処理をやり直せる」ことが、予約失敗後の復旧を楽にします。

取得済み期間の訂正も取り込む

前回の最終日より新しい行だけ追加する方式では、過去日付の訂正が残ったままになる可能性があります。全期間を取り直す方式は、その取得範囲内の訂正を反映しやすい設計です。

ただし、提供元から一部の日付が欠けた状態で返ってきても、このコードは完全性まで判定しません。空データと必須列の欠損は検出しますが、前回より件数が減った場合の警告や取引日カレンダーとの照合は追加課題です。日々の更新用CSVとは別に、復元用コピーを残しておきましょう。

保存途中の失敗で既存ファイルを壊しにくくする

最初に一時ファイルへ書き込み、書き込みが完了してからos.replaceで対象CSVを置き換えます。元のCSVを開いて先頭から書き直す方式に比べ、途中で例外が起きたときに元データを残しやすくなります。

一時ファイルは対象CSVと同じディレクトリに作ります。この仕組みだけでバックアップや同時実行の排他制御を実現できるわけではありません。2つの処理を同時に動かすと、後から保存した処理が勝つ可能性があります。予約設定でも二重起動を防いでください。

手動で実行し、ログとCSVを確認する

仮想環境のPythonで起動する

コードを保存したフォルダで、次を実行します。

.\.venv\Scripts\python.exe .\daily_prices.py
$LASTEXITCODE

正常終了時の終了コードは0、取得・整形・保存の例外を検出した場合は1です。Python自体が見つからない、ログフォルダを作れないなど、処理開始前の失敗は別途端末のメッセージを確認します。

成功ログの見方

次は実測値ではなく、表示形式を説明するための例です。取得行数や最終日は実行日・休場日・取得結果で変わります。

2026-09-08 08:00:00 INFO Saved symbol=7203.T rows=400 latest=2026-09-07 path=...

rowsは保存件数、latestは返されたデータの最終日です。ログの日時が新しいだけで判断せず、データの最終日も確認してください。休日の翌朝は最新取引日が前日でないこともあるため、日付が飛んでいるだけで失敗とは限りません。

CSVでは、Dateが日付順に並んでいるか、同じ日付が重複していないか、価格や出来高の列があるかを見ます。続けてもう一度実行し、不自然に件数が増えないことを確認してから予約へ進みます。

Windowsで毎日自動実行する

タスクへ登録する値

Windowsのタスクスケジューラは、指定時刻やイベントをきっかけにプログラムを起動する仕組みです。Microsoft公式資料

タスクを作成し、トリガーを毎日、操作をプログラムの開始に設定します。次のパスは配置例です。自分のフォルダに置き換えてください。

横にスクロールできます →
項目設定例
タスク名株価データ日次取得
起動時刻毎日08:00・PCのタイムゾーンを確認
プログラムC:\stock-daily\.venv\Scripts\python.exe
引数"C:\stock-daily\daily_prices.py"
開始フォルダC:\stock-daily

引数に渡すファイルパスに空白がある場合は引用符で囲みます。Pythonは仮想環境内の絶対パスを指定し、普段の端末で有効になっている環境に頼らない構成にします。

二重起動と実行ユーザーを確認する

既に実行中の場合は新しいインスタンスを起動しない設定を選びます。実行アカウントには、コードの読み取り、CSVとログの書き込み権限が必要です。手動実行したユーザーと予約を実行するユーザーが違う場合は、特に保存権限を確認します。

ログオンしていない間も動かす設定は認証方法や端末の管理方針によって異なります。自分のWindows環境に合う設定で試し、登録直後にタスクの実行操作から起動してください。タスクの最終実行結果とログの更新を両方確認します。

PCの電源と実行漏れを考える

起動時刻にPCが電源オフなら、その時刻にPythonを実行できません。スリープからの復帰もPCや設定に依存するため、実際の利用条件で確認します。

このコードは開始日から取り直すため、数日実行できなくても、次回正常取得時に対象期間をまとめて再取得できます。ただし、実行漏れを翌日気付ける運用は必要です。最終成功ログが一定期間更新されていないことを監視すると、プロセス自体が起動しなかったケースにも対応しやすくなります。

取得できないときの切り分け方

通信・空データ・保存を分ける

横にスクロールできます →
症状確認すること最初の対応
ModuleNotFoundErrorタスクが使うPythonの場所同じPythonでpip showを実行
No price rows returned銘柄、期間、通信、提供元の応答少数銘柄・短い期間で手動確認
PermissionErrorCSVをExcelで開いていないか、権限対象ファイルを閉じて再試行
ZoneInfoNotFoundErrorタイムゾーンデータ仮想環境へtzdataを導入
ログが更新されないタスクの履歴、起動パスタスクから手動で実行
最新日が古い休場か、提供遅延か取引日と提供元の状態を確認

Windowsなどでタイムゾーンデータが不足する場合は、同じ仮想環境に追加します。

.\.venv\Scripts\python.exe -m pip install tzdata

休場日は取得対象に新しい行がなくても不自然ではありません。ただし、このコードは過去からの範囲を要求しているため、結果が完全に空なら休場日だけでは説明できません。銘柄や開始日を含めて確認します。

再試行を無制限にしない

サンプルでは取得の失敗を最大3回まで試し、試行の間に待機します。広い例外を取得処理の境界で捕まえる簡略版なので、銘柄設定の誤りなど再試行で直らない問題も再試行します。本格運用では通信障害と設定不備を分類します。

アクセス制限が続く場合は、短時間に何度も起動しないでください。再試行が終わった後はログを確認して原因を解消します。制限を避けるためにアクセス元を切り替えるような運用を前提にしないでください。

AWSで定期実行する場合の設計

Scheduler・Lambda・S3の役割を分ける

PCの電源に依存しない構成の一例は、EventBridge SchedulerからLambdaを起動し、結果をS3へ保存する方式です。Schedulerが起動時刻、Lambdaが処理、S3が永続保存を担当します。

毎朝8時に起動するなら、SchedulerでタイムゾーンをAsia/Tokyo、式をcron(0 8 * * ? *)とする設計が考えられます。Schedulerはタイムゾーンを指定でき、柔軟な時間枠を使用しない場合も呼び出しは秒単位の完全一致ではありません。AWS Scheduler公式

ローカルコードをそのまま配置しない

Lambdaの/tmpは実行環境ごとの一時保存領域です。毎回同じファイルが残る前提で累積CSVを管理できません。成果物はS3などへ保存する必要があります。AWS Lambda公式

今回のコードはスクリプトの隣へ保存するため、そのままLambdaへ貼り付ける完成版ではありません。Lambda用には、ハンドラー、依存パッケージ、書き込み先、S3へのアップロード、実行ロール、失敗通知を実装します。この記事では移行の設計までを扱い、AWS用コードの検証は別工程にします。

費用と保存先を確認する

利用料の見積もりでは、実行回数・処理時間・メモリ、S3の保存量とリクエスト、ログ保存量、通信構成を確認します。無料になると断定せず、採用する構成で現行料金を確認してください。

ブログ公開用S3と株価データ保存先は分ける設計が分かりやすくなります。取得したCSVをブログの公開ファイルに混ぜず、個人分析用のデータはアクセス範囲を限定して保存します。

自動取得後に確認したいデータ品質

欠損と休場を区別する

土日祝日を含めてすべての日付を埋める必要はありません。分析に必要な取引日のデータがあるかを確認します。欠けた日を前日の終値で埋める処理は、目的によって結果を変えるため、自動的に適用しない方が原因を追いやすくなります。

調整方法と分析時点を残す

保存ファイルだけでは、いつ取得し、どの設定で価格を調整したかが分かりにくくなります。検証を繰り返すなら、取得時刻、銘柄、期間、ライブラリのバージョン、調整設定をCSVと一緒に記録する方式へ拡張します。

予測モデルに使う場合は、後日訂正されたデータや当時まだ分からなかった情報をどう扱うかも課題になります。モデル評価の進め方はPythonで株価予測する方法と合わせて確認してください。

よくある質問

同じ日に2回動かしてもよいですか?

順番に実行する限り、毎回同じ範囲を置き換えるため、実行回数だけ行が増える設計ではありません。取得結果の訂正で値や件数が変わる場合はあります。同時実行は避けてください。

複数銘柄に増やせますか?

銘柄ごとの取得・保存関数に分けることで拡張できます。その際は銘柄ごとの成功・失敗、待機、保存ファイル、タイムゾーンを管理します。1銘柄の成功を確認してから増やすと、問題を切り分けやすくなります。

データをそのまま自動売買へ使えますか?

このコードは日次データの学習・分析用です。注文に必要な価格の適時性や注文状態の照合は実装していません。取得先を選ぶ際は、株価取得APIの比較も参照し、目的に合う利用条件と仕様を確認してください。

ログが成功ならデータも完全ですか?

成功ログは、このプログラムの取得・整形・保存が例外なく完了したことを示します。すべての取引日がそろっていることや価格が正しいことまでは保証しません。最終日、件数、欠損、前回との差分も確認します。

運用を始める前の確認

まず1銘柄で手動実行し、CSVとログを確認します。次に同日再実行で重複が増えないことを確かめ、取得に失敗した場合も既存CSVが残ることを検証します。その後、Windowsの予約から実行して、普段のPCの利用状態でも継続できるか確認してください。

本記事は情報提供を目的としたもので、契約・投資判断はご自身の責任で行ってください。