配当スクリーナー: 週次クロール + 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