データがなければ、始まらない
前回、教科書デモから実用ツールへ進むには 3つの壁——実データ・時系列・不確実性——を越える必要がある、と書きました。この記事は最初の壁「実データ」に正面から取り組みます。
MNIST なら数行でロードできたデータが、実データでは取得すること自体が技術課題になります。
為替の値をブラウザから取りに行くと、いきなり CORS の壁に阻まれる。まずはこの壁の正体からです。
なぜブラウザから直接叩けないか
素朴には、こう書けば取れそうに見えます。
const r = await fetch( 'https://query1.finance.yahoo.com/v8/finance/chart/USDJPY=X?range=1y' ); // → CORS エラーで弾かれる
これは Yahoo が意地悪をしているのではありません。ブラウザの同一オリジンポリシーという安全装置が、 「iseeit.jp のページが、許可のない別ドメインのデータを勝手に読む」ことを止めているのです。 Yahoo 側も、iseeit.jp からのブラウザ直アクセスを許可する CORS ヘッダを返していません。
yf-proxy.php — 自分のVPSを一段挟む
ブラウザ → 自分のVPS → Yahoo、と一段挟みます。ブラウザ ⇄ VPS は同一サイト内なので CORS は通り、 VPS ⇄ Yahoo はサーバー間通信なので CORS は無関係。壁の両側を、それぞれ通れる経路でつなぐわけです。
CORS OK
制約なし
なぜ PHP プロキシか — Puppeteer を選ばない理由
「スクレイピング=ヘッドレスブラウザ(Puppeteer)」と考えがちですが、2GB VPS では重すぎて現実的ではありません。 やりたいのは単純なHTTP中継だけ。PHP の一枚のスクリプトで十分です。 これは、このサイト全体を貫く省リソース設計の一貫です。
それでも、落ちる
プロキシで CORS は越えました。でも、まだ安心できません。Yahoo の非公式エンドポイントは、 いつ形が変わるか・止まるか分からない。ここに全てを賭けると、Yahoo が転けた日にツールごと死にます。
そこで「取れなかったとき」の逃げ道を、段で用意します。上から順に試し、落ちたら次へ。
第1段 — Yahoo Finance(yf-proxy.php 経由)
実データ・自動取得・最優先。うまくいけばこれで完結します。
第2段 — CSV アップロード
Yahoo が使えないとき、ユーザーが手元のCSVを渡す。Yahoo 非依存で、著作権的にも確実です。
第3段 — 合成データ
何もなくても必ず動く最後の砦。デモや動作確認は、これだけで完全に成立します。
フォールバックは、こう落ちる
各段の可用性をトグルで切り替えて、「データ取得を試みる」を押してください。 リクエストが上から順に落ちていき、最初に生きている段で成功します。合成データは常時ON——最後の砦です。
Yahoo を OFF にすると第1段が「✖ 取得失敗」で赤くなり、リクエストは第2段へ。CSV も無ければ第3段の合成データへ。 どこかで必ず成功する——これが単一障害点を作らない、ということです。
yf-proxy.php とフォールバックの骨子
検証してから中継する — プロキシの守り
中継役は攻撃の入口にもなります。yf-proxy.php は素通しではなく、いくつものゲートを通してから Yahoo に取りに行きます。
# 自サイト以外の 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; # 秒
$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段とは別に、プロキシ内部にもう一段の冗長化が入っている形です。
$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 も「どこから来たデータか」を一切気にせずに済みます。
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)。
- 取得直後にデータ形状を正規化し、後段が取得元を気にしないようにする。