ファイナンス、情報通信技術のスキル・アグリゲーション・サイト

' . iseeit.jp 情報通信技術 . '
 
TensorFlow.js / MNIST → 為替 ②

実データを、どう取るか
yf-proxy.php と3段フォールバック

為替の値を使いたいだけなのに、ブラウザから Yahoo Finance を叩くと、いきなり赤いエラーで弾かれます。 最初の壁は「実データ」。VPS側に置いた軽量プロキシと、Yahoo → CSV → 合成データの3段フォールバックで、 止まらないデータ取得を作ります。

CORS yf-proxy.php 3-tier fallback 実コード カスケード可視化

§ 0 — 導入

データがなければ、始まらない

前回、教科書デモから実用ツールへ進むには 3つの壁——実データ・時系列・不確実性——を越える必要がある、と書きました。この記事は最初の壁「実データ」に正面から取り組みます。

MNIST なら数行でロードできたデータが、実データでは取得すること自体が技術課題になります。 為替の値をブラウザから取りに行くと、いきなり CORS の壁に阻まれる。まずはこの壁の正体からです。

§ 1 — CORS の壁

なぜブラウザから直接叩けないか

素朴には、こう書けば取れそうに見えます。

// 素朴な期待(ブラウザ内で実行)
const r = await fetch(
  'https://query1.finance.yahoo.com/v8/finance/chart/USDJPY=X?range=1y'
);  // → CORS エラーで弾かれる
症状 / コンソールに「blocked by CORS policy」。データは1バイトも届かない。

これは Yahoo が意地悪をしているのではありません。ブラウザの同一オリジンポリシーという安全装置が、 「iseeit.jp のページが、許可のない別ドメインのデータを勝手に読む」ことを止めているのです。 Yahoo 側も、iseeit.jp からのブラウザ直アクセスを許可する CORS ヘッダを返していません。

回避ではなく、正しい経路を作る。 CORS はブラウザ間の話。サーバー同士の通信には、そもそも CORS の制約がありません。 だから「自分のサーバーを一段挟む」——これが素直な解決策になります。
§ 2 — 中継役を置く

yf-proxy.php — 自分のVPSを一段挟む

ブラウザ → 自分のVPS → Yahoo、と一段挟みます。ブラウザ ⇄ VPS は同一サイト内なので CORS は通り、 VPS ⇄ Yahoo はサーバー間通信なので CORS は無関係。壁の両側を、それぞれ通れる経路でつなぐわけです。

🌐
ブラウザ
iseeit.jp
🔀
yf-proxy.php
自VPS
📈
Yahoo Finance
query1/2

なぜ PHP プロキシか — Puppeteer を選ばない理由

「スクレイピング=ヘッドレスブラウザ(Puppeteer)」と考えがちですが、2GB VPS では重すぎて現実的ではありません。 やりたいのは単純なHTTP中継だけ。PHP の一枚のスクリプトで十分です。 これは、このサイト全体を貫く省リソース設計の一貫です。

ただの素通しにはしない。 中継役は攻撃の入口にもなり得ます。だから yf-proxy.php は「受けたものをそのまま流す」のではなく、 検証してから中継するゲートとして作ります。具体的な守りは §5 で。
§ 3 — 3段フォールバック

それでも、落ちる

プロキシで CORS は越えました。でも、まだ安心できません。Yahoo の非公式エンドポイントは、 いつ形が変わるか・止まるか分からない。ここに全てを賭けると、Yahoo が転けた日にツールごと死にます。

設計上の危険 / 単一のデータソースへの全依存=単一障害点(SPOF)。1つ落ちれば全部止まる。

そこで「取れなかったとき」の逃げ道を、段で用意します。上から順に試し、落ちたら次へ。

第1段 — Yahoo Finance(yf-proxy.php 経由)

実データ・自動取得・最優先。うまくいけばこれで完結します。

第2段 — CSV アップロード

Yahoo が使えないとき、ユーザーが手元のCSVを渡す。Yahoo 非依存で、著作権的にも確実です。

第3段 — 合成データ

何もなくても必ず動く最後の砦。デモや動作確認は、これだけで完全に成立します。

実データ優先・でも必ず動く。 この3段があるかぎり、iseeit.jp のツールは常に何かを表示できる。 「Yahoo が落ちていて真っ白」という最悪を、構造で防ぎます。
§ 4 — 体感デモ

フォールバックは、こう落ちる

各段の可用性をトグルで切り替えて、「データ取得を試みる」を押してください。 リクエストが上から順に落ちていき、最初に生きている段で成功します。合成データは常時ON——最後の砦です。

Interactive · 3-tier fallback cascade
3段フォールバック カスケード
サーバー通信なし。取得の成否はトグルで模擬しています。どれか1段でも生きていれば必ずデータを得ます。
Yahoo を利用可能に
CSV を用意
合成データ(常時ON)
第1段📈 Yahoo Financeyf-proxy.php 経由 · 実データ
待機
▼ 落ちたら
第2段📄 CSV アップロードユーザー提供 · Yahoo 非依存
待機
▼ 落ちたら
第3段🧪 合成データ最後の砦 · 必ず成功
待機
トグルで各段の可用性を決めて、「データ取得を試みる」を押してください。まずは全部OFF(合成のみ)から試すのがおすすめです。

Yahoo を OFF にすると第1段が「✖ 取得失敗」で赤くなり、リクエストは第2段へ。CSV も無ければ第3段の合成データへ。 どこかで必ず成功する——これが単一障害点を作らない、ということです。

§ 5 — 実装の要点

yf-proxy.php とフォールバックの骨子

検証してから中継する — プロキシの守り

中継役は攻撃の入口にもなります。yf-proxy.php は素通しではなく、いくつものゲートを通してから Yahoo に取りに行きます。

# yf-proxy.php — 守りの設定(抜粋)
# 自サイト以外の Origin は拒否
ALLOWED_ORIGINS      = ['https://www.iseeit.jp', 'https://iseeit.jp'];
# シンボルは英大文字・数字・記号のみ(インジェクション対策)
SYMBOL_PATTERN       = '/^[A-Z0-9=^.\-]{1,20}$/';
# 期間はホワイトリスト方式
ALLOWED_RANGES       = ['1mo','3mo','6mo','1y','2y'];
RATE_LIMIT_PER_MINUTE = 30;   # APCu で 1IP/分
FETCH_TIMEOUT        = 10;   # 秒
# CORS ゲート:許可した Origin にだけ許可ヘッダを返す
$origin = $_SERVER['HTTP_ORIGIN'] ?? '';
if (in_array($origin, ALLOWED_ORIGINS, true)) {
    header('Access-Control-Allow-Origin: ' . $origin);
} elseif (!empty($origin)) {
    http_response_code(403);   # 自サイト以外は拒否
    exit;
}

フォールバックは、実は二重

プロキシは Yahoo の query1 に取りに行き、失敗したら query2 に切り替えます。 クライアント側の3段とは別に、プロキシ内部にもう一段の冗長化が入っている形です。

# yf-proxy.php — query1 → query2 の二重化
$result = fetchYahoo(YF_BASE, ...);        # query1
if ($result['body'] === false || $result['status'] >= 400) {
    $result = fetchYahoo(YF_FALLBACK, ...);  # query2 へ
}

クライアントの3段 — try/catch の段構え

ブラウザ側は、取れなかったら次の段へ、を try/catch で素直に書きます。

// フォールバックの骨子(ブラウザ側)
async function loadFX(symbol){
  try {
    return normalize(await fetchViaProxy(symbol));  // 第1段: 実データ
  } catch (e1) {
    try {
      return normalize(await readCSV());        // 第2段: CSV
    } catch (e2) {
      return normalize(genSynthetic());          // 第3段: 合成(必ず成功)
    }
  }
}

データ形状を、揃える

最後の肝。3ソースは返す形がバラバラですが、取得直後に同じ形(日付×終値の配列)へ正規化しておきます。 こうすれば、後段の LSTM も AutoEncoder も「どこから来たデータか」を一切気にせずに済みます。

// 3ソースを { date, close }[] に正規化
function normalize(raw){
  return raw.map(d => ({ date: d.date, close: +d.close }));
}

この記事のまとめ

  • ブラウザからの外部API直叩きは CORS で弾かれる。回避ではなく正しい経路を作る。
  • 自VPSに軽量プロキシ yf-proxy.php を一段挟む。Puppeteer 不要、PHP一枚で足りる。
  • プロキシは素通しにせず、Origin許可リスト・シンボル検証・レート制限・タイムアウトで守る。
  • 単一障害点を作らない:Yahoo → CSV → 合成の3段フォールバック(+内部で query1→query2)。
  • 取得直後にデータ形状を正規化し、後段が取得元を気にしないようにする。

関連記事

Next — TensorFlow.js 実用化③
2つのAIを、同じグラフに — LSTM予測 × AutoEncoder異常検知

取れたデータで、いよいよ2つのAIを動かします。LSTMの予測が外れた点と、AutoEncoderが異常と判定した点は一致するのか。 予測と異常検知を同一グラフに重ね、CPUバックエンド固定の理由まで踏み込みます。