コンテンツにスキップ

配当スクリーナー: 週次クロール + Cloudflare D1 + Access 構成の整理

作成日: 2026-10-04
出典 / きっかけ: 「日本株の配当性向で銘柄を物色したい。毎週クローリングしてサイト化したい」という要望から、実装と設計書を作った(リポジトリ yk0817/japan-dividend-screener、Private)。本ノートは、その設計判断と使っている各サービスの仕様を、一次資料で照合できる形にまとめたもの。
関連: ホタルシステム_個人業務のai自動化_整理


0. 要点(3行)

  • 構成は「取得は GitHub Actions(Python / yfinance)、保存は Cloudflare D1、公開は Pages + Access」。取得を Workers に寄せないのは、無料枠の外部リクエスト上限(50 回/実行)と Yahoo 側の遮断リスクを避けるため。
  • 「自分専用」の鍵は Cloudflare Access。GitHub Pages の非公開公開は Enterprise Cloud 限定なので、個人の無料枠では使えない。Pages の本番 *.pages.dev を Access で守る手順はドキュメントにあるが、既定ではプレビューのみが対象(§5)。
  • データ源は非公式(yfinance)で、規約上のグレーが残る。Yahoo! JAPAN 側の規約原文には当たれていない(§6)。J-Quants 無料プランは株価が 12 週間遅延で、利回りの週次更新には向かない。

1. 何を作るか

東証プライム内国株(約 1,550 銘柄)の配当性向・利回りなどを毎週取得して D1 に溜め、自分だけがブラウザで絞り込み・並べ替え・推移確認できるようにする。

# 受け入れ条件 確認方法
A1 毎週月曜 07:00 JST に、PC 非起動で全件取得が走る Actions の実行ログ
A2 取得結果が D1 に週次スナップショットとして残る snapshots に 2 週分以上ある
A3 利回り / 配当性向 / 時価総額 / 業種で絞り込める 手動確認
A4 銘柄ごとに配当性向・利回りの週次推移が見られる 銘柄詳細で折れ線が出る
A5 本人以外は閲覧できない シークレットウィンドウで確認
A6 取得率 80% 未満の週は既存データを汚さない runs.status = 'rejected'

非ゴールは、投資助言・銘柄推奨、リアルタイム株価、一般公開、プライム以外の市場。

2. 全体構成

[JPX 銘柄一覧 xlsx]──┐
                     ├─▶ GitHub Actions(週次 cron)
[yfinance 非公式] ───┘     ├─ fetch.py   取得(失敗銘柄は None で継続)
                           ├─ screen.py  指標計算(純粋関数・テスト済み)
                           ├─ 取得率 80% 以上か? ── 未達 ─▶ runs に rejected を記録して終了
                           └─ emit_sql.py ─ wrangler d1 execute --remote ─▶ [Cloudflare D1]
                                                                                │
本人のブラウザ ─▶ [Cloudflare Access] ─▶ [Pages: 静的 UI + Functions /api] ◀─────┘
層 担当 この分け方にした理由
取得・計算 GitHub Actions(Python) yfinance は Python 前提で既に動く。Workers へ移すと crumb/cookie 認証の自前実装と、実行あたりの外部リクエスト上限への対策が要る
保存 Cloudflare D1(SQLite) 週次スナップショットを溜めて推移を引ける。容量は無料枠に余裕で収まる(§3)
公開 Pages + Pages Functions D1 を読む薄い API と静的 UI を 1 プロジェクトに置ける
認証 Cloudflare Access 自分のメールだけ許可。アプリ側に認証コードを書かない

取得を Workers へ寄せる案は、Actions 側で 429 が実際に問題になってから検討する。

2-1. Cloudflare 側の構成(作るリソース)

Cloudflare アカウント
├─ D1 データベース: dividend-screener
│    └─ テーブル: stocks / runs / snapshots(§4)
├─ Pages プロジェクト: dividend-screener(GitHub 連携で main を自動デプロイ)
│    ├─ 静的: site/index.html
│    ├─ Functions: functions/api/*.js ──(binding: DB)──▶ D1
│    └─ ドメイン: dividend-screener.pages.dev(本番)/ <hash>.dividend-screener.pages.dev(プレビュー)
├─ Zero Trust → Access
│    ├─ Application 1: dividend-screener.pages.dev(本番)
│    ├─ Application 2: *.dividend-screener.pages.dev(プレビュー)
│    └─ Policy: Allow / Include: Emails = 本人のアドレス / ログイン方式: One-time PIN
└─ API トークン: gha-d1-writer(D1 の編集権限のみ)── GitHub Secrets へ
リソース 名前(案) 作り方 備考
D1 データベース dividend-screener wrangler d1 create dividend-screener 出力の database_id を wrangler.toml に書く
スキーマ migrations/0001_init.sql wrangler d1 migrations apply dividend-screener --remote 失敗した移行はロールバックされる(§3-4)
Pages プロジェクト dividend-screener ダッシュボードで GitHub リポジトリ連携 GitHub 連携にすると、デプロイ用トークンが不要になる。Actions のトークンは D1 書き込みだけに絞れる
D1 binding DB wrangler.toml の [[d1_databases]](またはダッシュボードの Settings → Bindings) Functions から context.env.DB で参照
Access 本番用・プレビュー用の 2 アプリ Pages の Settings → Enable access policy → subdomain の * を外す(§3-3) /api/* も同じホスト名なので一緒に守られる(実機での確認は未実施)
API トークン gha-d1-writer My Profile → API Tokens → カスタムトークン。D1 の編集権限のみ 権限名の正確な表記は未確認(公式の CI 解説は Workers 編集の例しか載せていない)
GitHub Secrets CLOUDFLARE_API_TOKEN / CLOUDFLARE_ACCOUNT_ID リポジトリの Settings → Secrets 手元では 1Password に保管し、op run で注入

リポジトリのディレクトリ構成(フェーズ 1〜2 完了時の想定)

japan-dividend-screener/
├─ wrangler.toml            Pages + D1 binding の設定
├─ migrations/
│   └─ 0001_init.sql        §4 の DDL
├─ functions/
│   └─ api/
│       ├─ snapshot.js      GET /api/snapshot
│       ├─ history.js       GET /api/history?code=8058
│       └─ runs.js          GET /api/runs
├─ site/
│   └─ index.html           静的 UI(data.json → /api/snapshot に切り替え)
├─ src/screener/            取得・計算(現行)+ emit_sql.py(新規)
└─ .github/workflows/weekly.yml

Functions はファイルパスがそのまま URL になる(functions/api/snapshot.js → /api/snapshot)。

wrangler.toml(公式の書式に合わせた案)

name = "dividend-screener"
pages_build_output_dir = "./site"
compatibility_date = "2026-10-04"

[[d1_databases]]
binding = "DB"
database_name = "dividend-screener"
database_id = "<wrangler d1 create の出力>"

name / pages_build_output_dir / compatibility_date は必須項目。wrangler 3.45.0 以上が必要(出典: https://developers.cloudflare.com/pages/functions/wrangler-configuration/ )。database_id は秘密情報ではないのでコミットしてよい。

Functions の例(functions/api/history.js)

export async function onRequestGet({ request, env }) {
  const code = new URL(request.url).searchParams.get("code") ?? "";
  // 銘柄コードは英数字4〜5桁。検証してからプレースホルダでバインドする
  if (!/^[0-9A-Z]{4,5}$/.test(code)) {
    return Response.json({ error: "invalid code" }, { status: 400 });
  }
  const { results } = await env.DB.prepare(
    `SELECT s.as_of, s.price, s.dividend, s.yield_pct, s.payout_pct
       FROM snapshots s JOIN runs r ON r.as_of = s.as_of
      WHERE s.code = ?1 AND r.status = 'ok'
      ORDER BY s.as_of`
  ).bind(code).all();
  return Response.json(results);
}

週次ワークフローの D1 反映部分(案)

      - run: uv run python -m screener.emit_sql > out/week.sql   # 1 文 100 KB 未満に分割済み
      - uses: cloudflare/wrangler-action@v4
        with:
          apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
          accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
          command: d1 execute dividend-screener --remote --file out/week.sql

cloudflare/wrangler-action@v4 と 2 つの Secrets の組み合わせは公式の CI 解説に沿う(出典: https://developers.cloudflare.com/workers/ci-cd/external-cicd/github-actions/ )。command: 入力の書式は実装時に確認する。

費用

項目 想定利用量 無料枠 費用
D1 ストレージ 10 年で約 80 MB 500 MB / DB 0 円
D1 書き込み 週 1 回・数千行 10 万行 / 日 0 円
D1 読み取り 自分の閲覧のみ 500 万行 / 日 0 円
Pages / Functions 個人の閲覧のみ 無料枠内の見込み(Functions の無料枠は今回未確認) 0 円の見込み
Access 1 ユーザー 50 ユーザーまで(二次情報) 0 円の見込み
GitHub Actions 週 1 回・最大 90 分 Private リポの無料枠の分数は今回未確認 0 円の見込み

3. 仕様チェック表(一次資料で照合した値)

数値は 2026-10-04 に各公式ドキュメントを取得して確認した。変わりうるので、設計を詰める前に出典 URL で再確認すること。

3-1. Cloudflare D1

項目 Free Paid 本構成での意味
1 DB の最大サイズ 500 MB 10 GB 1 週 約 150 KB(約 1,550 行 × 約 100 B)なので、10 年分でも約 80 MB(筆者の概算)
アカウント合計ストレージ 5 GB 1 TB 余裕
DB 数 10 50,000 1 個で足りる
1 Worker 呼び出しあたりのクエリ数 50 1,000 API は 1 リクエスト 1〜2 クエリに収める
1 クエリのバインド数 100 100 IN (...) の銘柄コードを大量に渡す設計は避ける
SQL 文の最大長 100,000 bytes 同左 1,550 行を 1 つの INSERT にすると超える見込み。emit_sql.py は 1 文を 100 KB 未満に分割する
1 日の rows read 500 万 月 250 億まで込み 閲覧は個人 1 人なので問題にならない
1 日の rows written 10 万 月 5,000 万まで込み 週 1 回の書き込みは数千行規模で、1 日上限の 1/10 未満(索引分の数え方は未確認)
Time Travel(復元) 7 日 30 日 反映ミスは 7 日以内なら戻せる

出典: https://developers.cloudflare.com/d1/platform/limits/ 、https://developers.cloudflare.com/d1/platform/pricing/

3-2. Cloudflare Workers(取得を寄せない根拠)

項目 Free Paid
Subrequests(外部 fetch)/ リクエスト 50 10,000
CPU 時間 / HTTP リクエスト 10 ms 既定 30 秒(最大 5 分)
Cron Triggers / アカウント 5 250

全 1,550 銘柄を 1 銘柄 1 リクエストで取ると、Free では 1 回の実行に収まらない。Workers 案は Queues での分割か Paid が前提になる。出典: https://developers.cloudflare.com/workers/platform/limits/

3-3. Cloudflare Access / Pages

項目 内容 出典
既定で保護される範囲 プレビューデプロイのみ(例: 373f31e2.user-example.pages.dev)。本番の *.pages.dev やカスタムドメインは保護されない https://developers.cloudflare.com/pages/configuration/preview-deployments/
本番 *.pages.dev を保護する手順 Workers & Pages → プロジェクト → Settings → Enable access policy → public hostname の subdomain からワイルドカード * を外す https://developers.cloudflare.com/pages/platform/known-issues/
カスタムドメインを使う場合 別途、Zero Trust → Access → Applications でそのドメイン用のポリシーを作る。作らないと認証画面は出るが動かない 同上
Free の対象人数 50 ユーザーまで(第三者サイトの記述。公式ページでは確認できなかった) 未確認
Pages Functions の D1 接続 ダッシュボードの Settings → Bindings、または wrangler の設定ファイル。コードは context.env.<BINDING>.prepare(...) https://developers.cloudflare.com/pages/functions/bindings/

3-4. wrangler(D1 への反映)

  • wrangler d1 execute は --command か --file のどちらかが必須で、--remote で本番 D1 に対して実行する。
  • wrangler d1 migrations apply は適用前に確認を求めるが CI では省略され、適用後にバックアップが取られ、失敗した移行はロールバックされる。
  • 出典: https://developers.cloudflare.com/d1/wrangler-commands/

3-5. GitHub

項目 内容 出典
Pages の非公開公開 GitHub Enterprise Cloud が必須(個人の無料・Pro では不可) https://docs.github.com/en/pages/getting-started-with-github-pages/changing-the-visibility-of-your-github-pages-site
schedule(cron) 既定は UTC。最短 5 分間隔。高負荷時(特に毎時の頭)は遅延しうる。デフォルトブランチ上のワークフローだけが動く https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows
無効化 パブリックリポジトリは 60 日間無活動で自動無効化される(本リポは Private なので対象外のはず) 同上
タイムゾーン指定 IANA タイムゾーン文字列でのタイムゾーン指定に対応(現行の weekly.yml は UTC 換算の 0 22 * * 0 のまま。切り替える価値あり) 同上

4. データモデル(D1)

CREATE TABLE stocks (code TEXT PRIMARY KEY, name TEXT NOT NULL, industry TEXT NOT NULL);

CREATE TABLE runs (
  as_of TEXT PRIMARY KEY,                 -- 取得週の月曜(JST)
  universe INTEGER NOT NULL, fetched INTEGER NOT NULL,
  status TEXT NOT NULL CHECK (status IN ('ok', 'rejected')),
  generated_at TEXT NOT NULL
);

CREATE TABLE snapshots (
  as_of TEXT NOT NULL REFERENCES runs(as_of),
  code  TEXT NOT NULL REFERENCES stocks(code),
  price REAL, dividend REAL, yield_pct REAL, payout_pct REAL, eps REAL,
  market_cap_oku INTEGER, flag TEXT NOT NULL,
  PRIMARY KEY (as_of, code)
);
CREATE INDEX idx_snapshots_code ON snapshots (code, as_of);
判断 理由
as_of は取得週の月曜(JST) UTC の日付だと日曜 22:00 UTC の実行で 1 日ずれる
再実行は INSERT OR REPLACE 手動再実行しても重複しない
値が取れない銘柄も行を残す 「その週に取れなかった」ことも履歴になる
eps も保存 判定ロジックを変えても過去週を再計算できる
runs を最後に ok で書く 途中で失敗した週を UI が採用しない(疑似トランザクション)

5. 論点: 「自分専用」をどう実現するか

案 可否 評価
GitHub Pages を非公開に Enterprise Cloud 限定のため不可(§3-5) ×
Pages を公開のまま、投資助言でない旨の注記だけ付ける 可能 個人の物色リストが誰でも見える。△
Pages + Cloudflare Access 公式手順あり(§3-3) ○ 採用
Private リポ + ローカル閲覧(git pull して静的サーバー) 可能 認証不要で最も単純。スマホで見づらい

実装時に検証すること(未実施): ① Enable access policy で本番 *.pages.dev が実際に認証画面に飛ぶこと。② One-time PIN で自分のメールだけ通ること。③ Functions の /api/* も同じ Access 配下に入っていること。

6. データ源の比較

データ源 配当性向 鮮度 規約・安定性 評価
yfinance(非公式) payoutRatio が取れる 当日 yfinance 自身が「Yahoo と無関係」「研究・教育目的」「個人利用のみ」と明記し、Yahoo の利用規約の確認を求めている。仕様変更で壊れうる 採用(個人・週 1 回・低頻度に限る)
J-Quants 無料プラン 財務は Summary のみ 株価は 12 週間遅延、履歴 2 年 公式 API。CSV ダウンロード不可 利回りの週次更新には不向き。取得層の差し替え候補
EDINET / TDnet 決算短信・有報から自前で抽出 開示直後 公式 整形コストが大きい
株探・Yahoo!ファイナンス(HTML スクレイピング) 画面から抽出 当日 規約で自動取得が禁止されていることが多い 不採用

出典: https://ranaroussi.github.io/yfinance/ 、https://jpx-jquants.com/en/spec/data-spec

未確認: Yahoo! JAPAN 側の利用規約(自動アクセスの禁止条項)の原文には、今回のセッションで当たれなかった(公式ページの取得に失敗)。二次情報では「禁止されている」とされるが、原文での確認は残課題。

JPX 銘柄一覧(ユニバース)

  • 取得元は JPX 公式の上場銘柄一覧(data_j.xlsx)。列は 日付 / コード / 銘柄名 / 市場・商品区分 / 33業種 / 17業種 / 規模。プライム(内国株式)は 1,556 銘柄(2026-08-31 時点)。
  • 以前の .xls の URL は 404 になっており、現行は .xlsx。ファイル名は変わるので、404 でジョブが落ちる設計(変更の検知)にしてある。

7. 配当性向の読み方(筆者の判断。一次資料に基づくものではない)

「配当性向が高い」は良さではなく、続くかどうかで見る。

指標 目安 理由
配当性向 30〜100% を目安内、100% 超は減配リスク 利益以上に払っていれば、いずれ続かない
利回り 6% 超は株価急落の結果であることが多い 高利回りは「安くなった」だけのことがある
営業 CF と配当総額 営業 CF が配当総額を大きく上回る 利益は会計上、配当は現金で払う
配当方針 累進配当・DOE の下限が明示されている 業績が悪くても減配しにくい設計
EPS ≤ 0 配当性向は無意味 赤字では分母が負になる

実装した判定列は「目安内(30-100%)/低配当性向/利益超過(減配リスク)/赤字/データ不足」の 5 分類。これは持続性の分類であって推奨ではない。利回りは yfinance の dividendYield を使わず、予想配当 ÷ 株価で自前計算する(dividendYield は版によって単位が揺れるため)。

8. リスクと未確認事項

リスク / 未確認 影響 対策・状態
GitHub Actions の共有 IP で yfinance が 429 全件取れない週が出る 取得率ゲートで弾く。続くなら J-Quants へ差し替え。全件実行は未実施(30 銘柄の試走のみ 30/30)
yfinance の仕様変更・項目欠損 値が NULL だらけ NULL 許容スキーマ、取得率ゲート、データ不足 判定
Yahoo! JAPAN 規約の原文未確認 遮断・不許可 個人利用・週 1 回・約 0.3 秒間隔に留める。原文確認は残課題
Access の本番 *.pages.dev 保護 公開されたままになる 公式手順はある。実機での確認は未実施
1 文 100 KB 制限 1 つの巨大 INSERT が失敗 emit_sql.py で分割(フェーズ 1 で実装・テスト)
履歴は今日から溜まるだけ 連続増配年数などの長期指標が出ない フェーズ 3 で yfinance の配当履歴を別途取り込む
Free の Access 50 ユーザー 自分 1 人なので問題なし 公式ページでの確認は未了

9. 実装状況と次のステップ

フェーズ 内容 状態
0 取得・計算・静的サイト・週次 Actions 済(pytest 10 件パス、30 銘柄で試走、main を push 済み)
1 D1 作成、DDL、emit_sql.py、Actions から反映 未着手(Cloudflare アカウントと API トークンが必要)
2 Pages Functions と UI の API 切り替え、Access 設定 未着手
3 銘柄詳細の推移、配当履歴(連続増配年数) 未着手
4 週次 cron を有効化して 2 週続けて確認 週次 cron は push 済みで有効だが、全件実行は未検証

10. まとめ — いつ何を選ぶか

状況 推奨
個人用に週次で全件を溜めたい Actions で取得 → D1 に追記 → Pages + Access で公開
まず動かして見たいだけ data.json を commit する現行方式(ローカルで http.server)
429 が続く 取得層を J-Quants API に差し替え(株価の遅延は別途補う)
取得も Cloudflare に寄せたい Workers Paid(Subrequests 10,000/リクエスト)か Queues での分割が前提
非公開にしたいが追加費用は避けたい GitHub Pages ではなく Cloudflare Access(無料枠)

参考リンク

  • 実装・設計書: https://github.com/yk0817/japan-dividend-screener (docs/design.md、Private)
  • Cloudflare D1 の制限: https://developers.cloudflare.com/d1/platform/limits/
  • Cloudflare D1 の料金: https://developers.cloudflare.com/d1/platform/pricing/
  • Cloudflare D1 の wrangler コマンド: https://developers.cloudflare.com/d1/wrangler-commands/
  • Cloudflare Workers の制限: https://developers.cloudflare.com/workers/platform/limits/
  • Cloudflare Pages × Access(プレビュー): https://developers.cloudflare.com/pages/configuration/preview-deployments/
  • Cloudflare Pages の既知の問題(*.pages.dev の保護手順): https://developers.cloudflare.com/pages/platform/known-issues/
  • Cloudflare Pages Functions の bindings: https://developers.cloudflare.com/pages/functions/bindings/
  • Cloudflare Pages Functions の wrangler 設定: https://developers.cloudflare.com/pages/functions/wrangler-configuration/
  • Cloudflare の GitHub Actions 連携: https://developers.cloudflare.com/workers/ci-cd/external-cicd/github-actions/
  • Cloudflare Access のポリシー: https://developers.cloudflare.com/cloudflare-one/policies/access/
  • GitHub Pages の公開範囲: https://docs.github.com/en/pages/getting-started-with-github-pages/changing-the-visibility-of-your-github-pages-site
  • GitHub Actions の schedule: https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows
  • J-Quants API のデータ仕様: https://jpx-jquants.com/en/spec/data-spec
  • yfinance ドキュメント(免責事項): https://ranaroussi.github.io/yfinance/
  • JPX 上場銘柄一覧: https://www.jpx.co.jp/markets/statistics-equities/misc/01.html
  • 参考(2023 年の解説記事・古い): https://dev.classmethod.jp/articles/cloudflare-pages-access/

作成: 2026-10-04 / 最終更新: 2026-10-04