証券会社のCSVをpandasで読み込む
Python経験者向けの投資分析シリーズ第3回。SBI証券・楽天証券などからダウンロードした保有銘柄CSVを、pandas で正しく読み込む方法を扱います。日本の証券会社CSVでつまずく文字コード(cp932)・ヘッダー上の説明行・「1,234,567」の桁区切りカンマ・日付フォーマットの差異を、どの会社のCSVでも壊れない形で吸収するのがゴールです。
1. この記事のゴールと連載の位置づけ
本記事は「Python経験者が、自分の投資ポートフォリオを管理・可視化するアプリを作る」連載の第3回です。第1回のyfinanceで株価データを取得するで「最新価格を取る部品」を、第2回の株価スクレイピングの前にで「データは公式・正規の手段で取る」という判断軸を固めました。今回は連載のもう一方の柱、「自分がいま何を・何株・いくらで持っているか(保有データ)」をPythonに取り込むところを扱います。
保有データの入手先はシンプルです。あなたが使っている証券会社(SBI証券・楽天証券など)が、保有銘柄をCSVでエクスポートする機能を用意しています。これをダウンロードして pandas で読み込めば、価格(第1回)と保有(今回)が揃い、評価額や損益が計算できるようになります。難易度は易しめ(★☆☆)ですが、日本の証券会社CSV特有の「つまずきポイント」が3つあり、そこを知らないと最初の read_csv() でいきなり止まります。本記事はその3つ——文字コード・説明行・桁区切り——を確実に越えることを目標にします。
どの証券会社のCSVでも、文字化けせず・正しい型(日付は日付・金額は数値)で DataFrame に読み込めるようになること。列名や書式は会社ごとに違うので、「自分のCSVを見て確かめてから合わせる」やり方を身につけます。
2. なぜ「CSV」なのか — スクレイピングでもAPIでもなく
「保有データもプログラムで自動取得したい」と思うかもしれません。ですが第2回で確認したとおり、ログイン後のマイページをスクレイピングするのは規約・セキュリティの両面で避けるべきです。証券会社の個人口座ページは自動取得を想定しておらず、二段階認証もあり、なにより自分の資産情報を扱う場所です。ここは公式が用意したCSVエクスポート機能を使うのが、最も安全で確実です。
CSVエクスポートは、多くのネット証券で「口座管理」「保有証券」「ポートフォリオ」といったメニューから行えます(画面名称や導線は各社で異なり、変更されることもあるため、最新の手順はご利用の証券会社のヘルプで確認してください)。手動で数クリックする手間はありますが、月に一度スナップショットを取る運用なら十分現実的です。「全自動」にこだわってログイン自動化に踏み込むより、安全なCSV取り込みを綺麗に作るほうが、長く使えるアプリになります。
ダウンロードしたCSVには保有銘柄・数量・評価額といったセンシティブな個人資産情報が入っています。クラウドの共有フォルダや公開リポジトリ(GitHub等)に誤ってコミットしないよう注意してください。.gitignore に *.csv やデータ用フォルダを登録しておくと事故を防げます。
3. まず自分のCSVの中身を確認する
いきなり pd.read_csv() に渡す前に、必ず一度、ダウンロードしたCSVを「素のテキスト」として目で見てください。証券会社CSVは、先頭に日付やタイトルの行が入っていたり、表が複数のブロックに分かれていたりと、「1行目がそのまま列名」とは限らないからです。列名や行構成は会社・エクスポート種別・時期によって変わるので、最初の確認を飛ばすと、間違った前提のコードを書いてしまいます。
エディタ(VS Codeなど。環境がまだならVS Codeのセットアップを参照)で開くのが手軽ですが、文字化けする場合もあるので、Pythonで先頭数行だけ生バイトを覗くのが確実です。
from pathlib import Path
path = Path("holdings.csv") # ダウンロードしたCSVのパスに置き換える
# エンコーディングを決め打ちせず、まずバイト列の先頭を見る
raw = path.read_bytes()[:400]
print(raw) # b'...' 形式で中身が見える
ここで \x83\x8a のような日本語らしきバイトが見えたら、次章のエンコーディング指定に進みます。ポイントは、「何行目に列名(銘柄コード・保有数量…)があるか」「その上に説明行が何行あるか」を数えておくことです。この2点さえ掴めば、あとは機械的に処理できます。
本記事では説明のために、次のような架空の保有データを例に進めます(実在の推奨銘柄ではなく、書式を示すためのサンプルです)。あなたのCSVとは列名も行数も違うはずなので、ご自身のファイルの内容に読み替えてください。
"2026/07/13 時点 保有証券一覧"
"口座区分: 特定/NISA"
銘柄コード,銘柄名,保有数量,取得単価,現在値,評価額,評価損益
7203,トヨタ自動車,100,"2,500","3,120","312,000","62,000"
6758,ソニーグループ,30,"11,200","13,400","402,000","66,000"
1306,TOPIX連動型ETF,50,"2,300","2,540","127,000","12,000"
この例では、先頭にタイトル行・口座区分・空行の計3行があり、4行目が列名、金額は 「2,500」のようにダブルクオート+桁区切りカンマで書かれています。これがまさに、日本の証券会社CSVでよく出会う3つの壁(文字コード・説明行・桁区切り)を凝縮した形です。順番に越えていきましょう。
4. 文字コードの壁:cp932で読む
最初の壁が文字コード(エンコーディング)です。近年のPythonやWeb系ツールは UTF-8 が既定ですが、日本の証券会社がWindows向けに出力するCSVは、多くが Shift_JIS 系(Pythonでは cp932)です。SBI証券・楽天証券からダウンロードしたCSVを扱った複数の実践記事でも、Shift_JIS/cp932 として読み込む必要があることが報告されています(SBI証券CSVの文字コード変換の例、楽天証券CSVをPythonで扱う例)。pd.read_csv() は既定で UTF-8 として読もうとするため、そのままだと日本語が文字化けするか、UnicodeDecodeError で止まります。
対処は簡単で、encoding="cp932" を明示するだけです。
import pandas as pd
# 日本の証券会社CSVは cp932 を明示するのが安全
df = pd.read_csv("holdings.csv", encoding="cp932")
print(df.head())
shift_jis ではなく cp932 を使う理由encoding="shift_jis" でも多くは読めますが、cp932 のほうが安全です。cp932 はMicrosoftが定めた Shift_JIS の拡張版で、Windowsで使われるNEC特殊文字・IBM拡張文字(丸数字①②や﨑・㈱などの機種依存文字)まで含む上位互換だからです。証券会社CSVの銘柄名にこうした文字が混ざっていると、素の shift_jis では読めずにエラーになることがあります。日本語Windows由来のCSVは、まず cp932 で読む——これを基本方針にすると安定します。
これで日本語がきちんと表示されれば、最初の壁は突破です。pandas というライブラリ自体の位置づけや、他のデータ分析ライブラリとの関係はよく使うPythonライブラリ50選でも触れています。
5. ヘッダーより上の説明行を読み飛ばす
文字コードを解決しても、先頭のタイトル行や口座区分の行がそのまま読み込まれると、列名がぐちゃぐちゃになります。前章の例では、実際の列名(銘柄コード・保有数量…)は4行目にありました。上の3行(タイトル・口座区分・空行)を読み飛ばす必要があります。
これは skiprows= で行います。読み飛ばす行数を数値で渡すと、その行数ぶんを無視して読み込みます。
# 先頭3行(タイトル・口座区分・空行)を飛ばし、4行目を列名として読む
df = pd.read_csv("holdings.csv", encoding="cp932", skiprows=3)
print(df.columns.tolist())
# 例: ['銘柄コード', '銘柄名', '保有数量', '取得単価', '現在値', '評価額', '評価損益']
ここで大切なのは、この「3」はサンプルの値であって、あなたのCSVでは違うということです。だからこそ第3章で「列名が何行目にあるか」を先に数えておいたわけです。とはいえ、説明行の数が毎回変わったり、手で数えるのが面倒だったりする場合は、列名の行をプログラムで自動検出すると壊れにくくなります。
import pandas as pd
MARKER = "銘柄コード" # 自分のCSVの列名に含まれる語に置き換える
# 列名の行が何行目かを自動で探す
with open("holdings.csv", encoding="cp932") as f:
lines = f.readlines()
header_row = next(i for i, line in enumerate(lines) if MARKER in line)
df = pd.read_csv("holdings.csv", encoding="cp932", skiprows=header_row)
print(df.head())
こうしておけば、説明行が1行増えても2行増えても、「銘柄コード」という語を含む行を列名として拾うので自動で追従します。証券会社が将来レイアウトを少し変えても壊れにくい、実用的な書き方です。なお、表の下に合計行や参考為替レートなどの余分な行が付くCSVもあります。その場合は読み込んだ後に df = df.dropna(subset=["銘柄コード"]) のように、キー列が空の行を落とすときれいになります。
DataFrame になる。6. 日付と金額を「使える型」に変換する
読み込めても、まだ安心はできません。df.dtypes を見ると、金額が数値ではなく文字列(object型)になっていることがよくあります。「2,500」のように桁区切りのカンマが入っていると、pandasはそれを数値と認識できず、文字列として読み込むためです。このまま合計しようとすると足し算にならず、文字列の連結になってしまいます。
桁区切りカンマを数値にする(thousands)
一番きれいなのは、読み込み時に thousands="," を渡すことです。これで「1,234,567」のような値が自動的に数値(int/float)として読み込まれます。
df = pd.read_csv(
"holdings.csv",
encoding="cp932",
skiprows=3,
thousands=",", # "2,500" → 2500 と数値化
)
print(df.dtypes) # 取得単価・評価額などが int64/float64 になる
もし何らかの事情で thousands がうまく効かない列(通貨記号や空白が混ざるなど)があれば、後処理で明示的に変換します。str.replace() で不要な文字を除いてから数値化する、汎用的なやり方です。
# 特定の列だけ手動でクレンジングする例
col = "評価額"
df[col] = (
df[col].astype(str)
.str.replace(",", "", regex=False) # 桁区切りを除去
.str.replace("円", "", regex=False) # 単位が混ざる場合
)
df[col] = pd.to_numeric(df[col], errors="coerce") # 数値化。ダメな値はNaNに
pd.to_numeric(..., errors="coerce") は、変換できない値をエラーで止めず NaN にするので、想定外の行が混じっていても処理が止まりません。実データは何が入っているか分からないので、この「止まらない」設計が効いてきます。
日付列を日付型にする(to_datetime)
取引日や基準日といった日付列も、そのままでは文字列です。時系列で並べ替えたり期間で絞ったりするには、日付型(datetime)に変換します。日本の証券会社CSVは「2026/07/13」のようなスラッシュ区切りが多いので、format= を明示すると速く・確実です。
# 例: "基準日" 列が "2026/07/13" 形式のとき
df["基準日"] = pd.to_datetime(df["基準日"], format="%Y/%m/%d")
# 読み込み時にまとめて指定してもよい
df = pd.read_csv(
"holdings.csv", encoding="cp932", skiprows=3,
thousands=",", parse_dates=["基準日"],
)
証券会社CSVには、銘柄名だけでなく数値や日付に全角文字が混ざることがまれにあります(例: 2026/07/13)。その場合は数値化・日付変換の前に df[col].str.normalize("NFKC") で全角→半角に正規化してから処理すると安定します。NFKC 正規化は全角英数字・記号を半角相当に揃えてくれるので、地味ですが実データでは効きます。
銘柄コードは文字列で読む(dtype)
金額や日付とあわせて注意したいのが、銘柄コードのようなID列は数値ではなく文字列(str)で読むことです。数値として読み込むと、先頭にゼロが付くコードでそのゼロが落ちて桁がずれたり、2024年以降に登場した英字を含む新しいコード(例: 130A)が数値に変換できずエラーになったりします。桁区切りは数値化する一方で、コードのようなIDは dtype= で列を指定し、文字列のまま保つのが安全です。
df = pd.read_csv(
"holdings.csv", encoding="cp932", skiprows=3,
thousands=",",
dtype={"銘柄コード": str}, # ID列は文字列で読み、先頭ゼロ・英字コードを保つ
)
ここでの列名 "銘柄コード" はあくまで例です。「コード」「証券コード」など各社で名前が違うので、自分のCSVの列名に置き換えてください。
ここまでで、文字化けせず・金額は数値・日付は日付、という「計算に使える DataFrame」が手に入りました。あとは第1回で作った価格取得と組み合わせるだけです。
7. 文字化け・UnicodeDecodeErrorが出たとき
encoding="cp932" を指定してもエラーになる場合、原因はたいてい「そのファイルが実は別のエンコーディングだった」です。証券会社やエクスポート種別によっては UTF-8(BOM付きの utf-8-sig を含む)や euc-jp のこともあります。代表的な症状と対処を整理します。
| 症状 | 考えられる原因 | 対処 |
|---|---|---|
UnicodeDecodeError で止まる | 指定した encoding がファイルと不一致 | cp932 → utf-8-sig → utf-8 → euc-jp の順で試す |
| 日本語が「譁�蟄怜喧縺�」のように化ける | UTF-8のファイルをcp932で読んだ等の取り違え | 正しい encoding に変更する |
先頭列名に が付く | UTF-8 BOM付きファイル | encoding="utf-8-sig" で読む |
どれか分からないときは、候補を順に試す小さなループを書くのが手っ取り早いです。
import pandas as pd
def read_broker_csv(path, **kwargs):
"""よくあるエンコーディングを順に試して読み込む。"""
for enc in ("cp932", "utf-8-sig", "utf-8", "euc-jp"):
try:
return pd.read_csv(path, encoding=enc, **kwargs)
except UnicodeDecodeError:
continue
raise ValueError("既知のエンコーディングでは読み込めませんでした")
df = read_broker_csv("holdings.csv", skiprows=3, thousands=",")
より確実に判定したいなら、文字コードを推定するライブラリ charset-normalizer(pip install charset-normalizer。requests の依存として入っていることも多い)や chardet を使う手もあります。ただ実務では、上のように「日本語CSVの定番エンコーディングを順に試す」だけで大半は解決します。まずはこのシンプルな方法で十分でしょう。UnicodeDecodeError をはじめ、Pythonでよく出るエラーの読み解き方はPythonのよくあるエラーと対処法にまとめてあるので、詰まったときの辞書として使ってください。
8. 読み込んだ保有データを次につなげる
ここまでで、証券会社CSVを正しい型の DataFrame にできました。連載の流れに戻すと、これで「価格(第1回のyfinance)」と「保有(今回のCSV)」というポートフォリオ管理アプリの2大インプットが揃ったことになります。イメージとしては、CSVから読んだ保有一覧の各銘柄について、第1回の fetch_latest_price() で最新価格を引き、掛け合わせれば評価額が出る——という接続です。
# CSVから読んだ保有データ(df)を、辞書のリストに変換しておくと後段で扱いやすい
holdings = df[["銘柄コード", "保有数量", "取得単価"]].to_dict(orient="records")
for h in holdings:
print(h) # {'銘柄コード': '7203', '保有数量': 100, '取得単価': 2500} など
ただし、CSVの銘柄コード(例: 7203)と、yfinanceで使うティッカー(例: 7203.T)は表記が違う点に注意が必要です。この「保有データの銘柄コードを価格取得用のティッカーに変換し、実際に評価額・評価損益を計算する」ところは、次回以降で扱います。CSVの読み込みという土台がしっかりできていれば、その先はぐっと楽になります。
今回の「型を整える」処理は入口にすぎません。merge での結合や欠損処理、groupby での集計と、保有データを扱い始めると、断片的な検索より一冊を通読するほうが結局は早いことが多くなります。
本記事で触れた read_csv の型変換・クレンジングから groupby 集計までを、順を追って学べます。用途別のおすすめはread_csvから集計まで学べるpandas・データ分析の書籍で紹介しています。
また、複数銘柄・長期の時系列を扱い始めるとメモリや処理時間が効いてくるので、腰を据えて分析環境を整えるならPython学習・分析向けのPC選びも参考にしてください。連載全体の地図は投資×Python シリーズ一覧にあります。
本連載は「自分の資産を自分で管理・可視化するツールを、Pythonの学習題材として自作する」ことが目的です。特定銘柄の売買や投資手法を勧めるものではなく、記事中のデータもすべて書式説明のための架空例です。投資判断はご自身の責任で行ってください。
9. よくある質問(FAQ)
証券会社CSVのpandas読み込みでよくいただく質問をまとめました。
Q. read_csvで日本語が文字化けします。どうすればいいですか?
日本の証券会社CSVの多くは Shift_JIS 系(Pythonでは cp932)で出力されるため、pd.read_csv(path, encoding="cp932") のようにエンコーディングを明示してください。既定のUTF-8のまま読むと文字化けや UnicodeDecodeError の原因になります。それでも化ける場合は、utf-8-sig や euc-jp など他のエンコーディングを順に試してみてください。
Q. cp932とshift_jisはどちらを指定すべきですか?
cp932 をおすすめします。cp932 はMicrosoftによる Shift_JIS の拡張版で、Windowsで使われる機種依存文字(丸数字①②や﨑・㈱など)まで含む上位互換です。銘柄名にこうした文字が含まれると素の shift_jis では読めないことがあるため、日本語Windows由来のCSVはまず cp932 で読むのが安全です。
Q. 金額の「1,234,567」が数値にならず、計算できません。
桁区切りのカンマが入っていると文字列として読み込まれます。pd.read_csv(..., thousands=",") を指定すると数値として読み込まれます。効かない列は、str.replace(",", "") で桁区切りを除いてから pd.to_numeric(..., errors="coerce") で数値化してください。
Q. CSVの1行目が列名になっていません。上に日付やタイトルがあります。
証券会社CSVは先頭に説明行が入ることがよくあります。pd.read_csv(..., skiprows=N) で説明行の数だけ読み飛ばしてください。行数が変わる可能性があるなら、列名に含まれる語(例:「銘柄コード」)を含む行をプログラムで探して、その行を skiprows に渡すと壊れにくくなります。
Q. 保有データをスクレイピングで自動取得してはいけませんか?
ログイン後の口座ページのスクレイピングは、規約・セキュリティの両面で避けるべきです。証券会社が公式に用意しているCSVエクスポート機能を使うのが安全で確実です。スクレイピングの可否の考え方は株価スクレイピングの前にを参照してください。
本記事は、生成AIを活用して下書きし、運営者が内容を確認・編集したうえで公開しています。証券会社のCSVエクスポートの仕様(画面導線・文字コード・列構成)は各社の変更により異なる場合があるため、実際のファイルをご確認のうえ適宜読み替えてください。本記事は情報提供を目的としたもので、特定の銘柄の売買や投資手法を推奨するものではありません。投資判断はご自身の責任で行ってください。AIの利用方針は免責事項をご覧ください。