ここまでの4回は、架空の2社で企業分析画面の仕組みを読んできました。最終回の今回は、ZENITH CodeのAPIキーを使って、実在する企業のデータをこの画面に表示します。
キーを使う画面を作るときに気になるのは、「APIキーがブラウザから漏れないか」と「失敗したときにおかしな数字を表示しないか」の2つです。今回はこの2つを中心に、ローカルのPythonサーバーがどのようにキーを預かり、取得したデータを確かめてから画面へ渡しているかを読みます。
上の画像は、記事の後半の変更を入れて、トヨタ自動車(7203)の5期分を取得した財務表です。
私たち株式会社Crabgrassは、企業の財務データなどをAPIで提供するZENITH Codeを開発しています。本記事は自社サービスのデータを使うためのテンプレートを題材にした実装例です。
この記事のコードは、公開サンプル集のcompany-analysisテンプレート(執筆時点の版)に対応しています。起動方法は実践④の「ローカルで開く準備」と同じです。前回の実践⑦では、画面の見た目と操作を整えました。
1. APIキーを用意する
ZENITH Codeの会員登録をして、利用規約とデータの利用条件に同意し、ダッシュボードでAPIキーを作成します。Freeプランでも、この画面で使う2つのAPIを呼び出せます。利用枠などの最新の条件は料金ページで確認してください。
作ったキーは、パスワードと同じように扱います。
- 記事、チャット、公開リポジトリ、スクリーンショットに貼らない
- コマンドの引数に直接書かない(シェルの履歴に残るため)
-
.envファイルに書いた場合は、Gitに含めない
2. 実データ接続で起動する
サンプル起動のサーバーを Ctrl+C で止め、--live を付けて起動し直します。
python3 templates/company-analysis/server.py --live
起動すると、ターミナルにキーの入力欄が出ます。入力した文字は画面に表示されません。
ZENITH API key (hidden):
環境変数 ZENITH_API_KEY にキーが入っていれば、入力欄は出ずにそちらを使います。サーバーは .env ファイルを自動では読み込みません。
キーを受け取る部分は server.py の main() です。
if args.live:
key = os.environ.get("ZENITH_API_KEY", "").strip()
if not key and sys.stdin.isatty():
key = getpass.getpass("ZENITH API key (hidden): ").strip()
if not key:
parser.error("Set ZENITH_API_KEY or run interactively. Do not pass a key on the command line.")
getpass は、入力した文字を画面に出さずに受け取るPythonの標準ライブラリです。キーを引数で受け取る --key のような指定は、あえて用意していません。
ブラウザで http://127.0.0.1:8765 を開き、「実データに接続」を押します。--live を付けずに起動していると、次の案内が表示されます。
3. キーはブラウザへ渡さない
証券コードに 7203 を入れて「データを取得」を押すと、ブラウザはZENITH CodeのAPIではなく、自分のパソコンの server.py に問い合わせます。
| 区間 | 通信先 | APIキー |
|---|---|---|
ブラウザ → server.py
|
http://127.0.0.1:8765/api/snapshot |
含まない |
server.py → ZENITH Code |
https://api.zenithcode.net/stable/... |
Pythonがヘッダーに付ける |
キーはPythonのプロセスの中にだけあります。ブラウザのJavaScript、HTML、localStorage のどこにも入りません。ブラウザの開発者ツールで通信を見ても、キーは出てきません。
ローカルの窓口を、ほかのページから使わせない
server.py は、/api/snapshot への問い合わせを受けるたびに、次の3つを確かめます。
expected = "http://127.0.0.1:%d" % self.server.server_port
if self.headers.get("Origin") != expected or not secrets.compare_digest(self.headers.get("X-Local-Token", ""), token):
self.reply(403, {"error": "local_only"})
return
| 確かめること | 防いでいること |
|---|---|
Host が 127.0.0.1:ポート番号 か |
別の名前でこのサーバーに届く問い合わせ(DNSリバインディング) |
Origin が同じ画面か |
開いている別のサイトが、裏でこの窓口を呼ぶこと |
X-Local-Token が起動時の合言葉と一致するか |
画面を開いていない相手からの問い合わせ |
合言葉(token)は、サーバーを起動するたびに secrets.token_urlsafe(32) で作り直します。画面は最初に /api/config から受け取り、取得のたびにヘッダーへ付けます。キーそのものではないので、画面が知っていても問題ありません。
さらに、サーバーは 127.0.0.1 でだけ待ち受けます。同じWi-Fiにある別の端末からは接続できません。実践④で「スマホから同じURLを開いてもつながらない」と書いたのはこのためです。
一度に1件だけ取得する
if not gate.acquire(blocking=False):
self.reply(429, {"error": "busy"})
return
ボタンを続けて押しても、取得中のあいだは次の取得を始めません。画面には「別の取得が進行中です。」と表示します。利用枠を意図せず使い切らないためです。画面の起動、言語の切り替え、グラフの指標の切り替えでは、APIを呼びません。
4. 1回の取得で呼ぶAPI
「データを取得」を1回押すと、python/zenith_examples.py の snapshot が2つのAPIを呼びます。
if mode == "snapshot":
result["latestClose"] = requester("/quote", {"symbol": symbol}, api_key)
result["annualFinancials"] = requester("/income-statement", {
"symbol": symbol, "period": "annual", "limit": 3,
}, api_key)
| API | 画面での用途 |
|---|---|
/stable/quote |
直近終値と株価の日付 |
/stable/income-statement |
最新3期の売上高・利益・出典 |
実践④で見た demo.json の annualFinancials と latestClose は、この2つの応答と同じ形にしてあります。だから、取得したデータは normalize() を通すだけで、グラフ、表、前期比、出典の処理をそのまま使えます。架空データで読んできたコードは、実データでも同じように動きます。
5. 取得したデータを確かめてから表示する
取得したデータは、画面に出す前に normalize() で確かめます。実践④で読んだ関数です。
| 確かめること | 満たさないとき |
|---|---|
symbol が文字列で、配列が2つそろっている |
表示を止める |
各期の symbol が、取得した企業と同じ |
表示を止める |
各期が通期(FY)で、決算期の形が YYYY-MM
|
表示を止める |
| 同じ決算期が2回出てこない | 表示を止める |
| 期の数が3以下、終値が1件以下 | 表示を止める |
1つでも満たさなければ invalid_response のエラーになり、画面には「想定外のデータ形式のため、表示を停止しました。」と表示します。おかしなデータを、見た目だけ整えて表示することはしません。別の企業の行が混ざったまま、もっともらしいグラフができてしまうほうが、エラーよりも気付きにくいためです。
エラーの中身を画面へ流さない
except ZenithError:
# Never relay exception bodies or upstream content into the UI.
self.reply(502, {"error": "upstream_failed"})
APIの呼び出しに失敗したとき、サーバーはAPIから返ってきた本文を画面へ渡しません。画面には決まった文言だけを出します。エラーの本文には、キーの一部や内部の情報が含まれる可能性があるためです。
| 画面の表示 | 主な原因 |
|---|---|
| 実データ接続の設定が必要です。 |
--live を付けずに起動した |
| 証券コードを確認してください。 | 4〜5文字の英数字(7203・130A など)ではない |
| 取得できませんでした。キー・利用条件への同意・利用枠・接続状態を確認してください。 | キーの誤り、利用条件への未同意、利用枠の超過、通信の失敗 |
| 想定外のデータ形式のため、表示を停止しました。 |
normalize() の確認を通らなかった |
| 別の取得が進行中です。 | 取得中に続けて押した |
| ローカル接続を確認し、ページを開き直してください。 | サーバーを起動し直した後、古い画面のまま操作した |
エラーのあとに、自動で取得し直すことはしません。原因がキーや利用枠にある場合、繰り返しても同じ結果になり、利用枠だけを使ってしまうためです。
6. 直近終値と決算期は別の時点
実データでは、数値カードの右端に直近終値が表示されます。
- 直近終値の下の「株価日付」は、その終値の取引日です。
- 売上高や営業利益のカードの「通期 2026-03」は、決算期です。
2つは別の時点の値です。そのため、このテンプレートは、直近の株価と過去の決算から独自のPERやPBRを計算していません。画面の「数字を読む前に」にも、そのことを書いています。
もう1つ大切な点があります。画面の上には、次の注意が表示されます。
実データ表示:ご自身の非公開環境で利用してください。株価・制限対象の書類等の公開はできません。
株価などの取引所のデータは、自分で分析に使うことはできても、第三者に見せる形で公開することはできません。実データの画面をスクリーンショットでSNSや記事に載せる場合は、株価が写っていないかを確認してください。この記事の冒頭の画像も、株価を含まない財務データの部分だけにしています。表示できる範囲はデータの表示ルールにまとめています。
7. 実データでの企業比較
実データ接続では、企業比較の欄が証券コードの入力欄に変わります。比較したい企業のコード(例:7267)を入れて「比較データを取得」を押すと、同じ /api/snapshot で2社目を取得します。
共通の決算期の選び方と、比較条件の確かめ方は、実践⑥で読んだ架空データのときと同じです。3月決算と12月決算の企業を選ぶと、共通の決算期がないと表示されます。
8. 取得する期を5期に増やす
最後に、取得する期を3期から5期に増やします。変更は2か所です。
1. 取得する件数(python/zenith_examples.py)。snapshot で損益計算書を呼ぶ部分の limit を5にします。
result["annualFinancials"] = requester("/income-statement", {
"symbol": symbol, "period": "annual", "limit": 5,
}, api_key)
2. 確認の上限(templates/company-analysis/model.mjs)。normalize() の期の数の上限を5にします。
if (years.length > 5 || snapshot.latestClose.length > 1)
throw new Error("invalid_response");
2つ目を忘れると、5期を取得した時点で「想定外のデータ形式」と表示されます。normalize() が、想定より多い期をエラーとして扱うためです。上限を設けておくと、APIの引数を間違えて大量の期を受け取ったときにも気付けます。
server.py を Ctrl+C で止め、--live で起動し直します。Pythonのファイルを変えたときは、ブラウザの再読み込みだけでは反映されません。7203を取得すると、冒頭の画像のように2022-03期から2026-03期までの5期が表示されます。グラフ、表、前期比、出典、CSVは、どれも期の数を決め打ちしていないので、そのまま5期分になります。
取得する期を増やしても、APIの呼び出しは1回のままです。limit は1回の応答に含める期の数で、呼び出しの回数ではありません。
limit の変更は、同じ関数を使うコマンドライン版の financials にも反映されます。テンプレートだけを変えたい場合は、snapshot のときだけ5にする条件を書いてください。
9. 連載のまとめ
全8回で、APIからデータを取得するところから、ブラウザで使う分析画面まで進みました。
| 回 | 扱ったこと |
|---|---|
| ① | Pythonで売上高・営業利益の推移をグラフにする |
| ② | 営業利益率を計算し、APIの値と照合する |
| ③ | 2社の売上高と営業利益率を比較する |
| ④ | 企業と指標を切り替えられる分析画面を作る |
| ⑤ | 財務表と出典をたどれる画面を作る |
| ⑥ | 決算期と前期比を分析画面に加える |
| ⑦ | PCとスマホで使いやすい分析画面に整える |
| ⑧ | ZENITH Codeの実データで分析画面を動かす(この記事) |
どの回にも共通していたのは、期間・単位・欠損・出典を最後まで手放さないことです。数字を表示する前に、それがいつの、どの範囲の、どの書類の値かを確かめる。欠損を0にせず、比べられないものは比べない。この画面は、その確認を毎回手作業でしなくて済むように作っています。
この画面を出発点に、見たい指標を足したり、自分の業務に合わせて表示を変えたりしてみてください。



