約3,800社、最大10年分、約39,000件。EDINET APIで有価証券報告書をここまで取り込んだ時点で、正直、上場企業の平均年収ランキングはできたも同然だと思っていました。ところが、いざ並べようとすると並ばないんです。同じ「平均給与」なのに、会計基準や業種、単体か連結かで、XBRLのタグが別物になるからでした。
それでもタグの違いを1つずつ潰して、平均給与、平均勤続年数、平均年齢、離職率を企業横断で見られるWebアプリ「kessan-tracker」として公開できました。載せている数字はすべて、金融庁のEDINETで公開されている有価証券報告書から取っています。
で、いま振り返ると、一番時間を食ったのはデータを集める処理ではありませんでした。EDINET API、XBRL、Next.js、PostgreSQL、AWS Lightsailと道具は並びますが、手がかかったのは、集めた数字の意味をそろえる作業のほうでした。
約39,000件を1つのランキングにするまで
EDINET APIで約3,800社・最大10年分の有価証券報告書を取得し、PostgreSQLへ保存。会計基準や業種、単体・連結で変わるXBRLタグの差異を吸収して、平均給与、勤続年数、年齢、離職率を企業ごとに比べられる形にした。
報告書を1社ずつ開かずに、上場企業の平均年収を比べる
kessan-trackerがやっているのは、1社ずつ書類を開く繰り返しを先にまとめて済ませておくことです。平均年収も平均勤続年数も、実は各社の有価証券報告書を開けば載っています。1社だけ調べるなら、それで十分でしょう。ただ、比べたくなった途端に話が変わります。会社ごとに書類を開いて、年度をそろえて、また次の会社、の繰り返しなので、対象が増えるほど手間もそのまま増えていきます。
企業一覧と決算履歴に加えて、ランキングで表示しているのは次の4つです。
- 平均給与
- 平均勤続年数
- 平均年齢
- 離職率
順位こそ付いていますが、投資先や転職先のおすすめではありません。公開書類に載っている数値を、企業と年度をまたいで確かめやすくするためのデータベース、というつもりで作っています。
同じ「平均給与」がタグごとに散らばっていた
XBRLは企業の開示情報を構造化して扱うための形式なのに、同じ「平均給与」が1つのタグに収まっていませんでした。構造化されているんだから、同じ項目は同じタグで取れるはず。取り込みを始める前は、そう信じていたんです。
実際に流し込んでみると、まずJ-GAAPとIFRSでタグが変わります。銀行や保険のような業種、単体決算か連結決算かでも別のタグになります。同じ意味の数値が別の名前で入っていたり、どのタグを優先するかが会社ごとに違ったりするわけです。
結局、指標ごとに候補タグを持たせて、会計基準や報告書の条件を見ながら値を選ぶ処理を入れました。XMLとして読めたら終わり、ではなかったんです。「このタグは経理上、どの数値を表しているのか」まで確かめないと、見た目が似ているだけの数字を同じランキングに混ぜてしまいます。
ここで効いたのが、本業の経理で身についた科目や開示資料の見方でした。候補タグを探すのも、値を選ぶ処理を書くのも、AIに手伝わせれば速く進みます。ただ、同じ名称に見える値を1つのランキングに並べてよいかどうかは、データの意味を見て私が決めました。
取得はPython、表示はNext.js、保存はPostgreSQL
難所はタグ処理に集中していました。それ以外の仕組みは、取得、表示、保存、公開で役割を分けた構成です。
| 役割 | 技術 | 処理内容 |
|---|---|---|
| 取得バッチ | Python | EDINET APIからXBRLを取得・解析する |
| Web表示 | Next.js/TypeScript | サーバー側でDBを読み、企業一覧とランキングを描画する |
| データベース | PostgreSQL | 約3,800社、約39,000件の報告書データを保存する |
| 公開環境 | AWS Lightsail | Web、DB、バッチを1台で動かす |
データベースは、全部入れても約25MBに収まりました。アクセスがまだ少ない個人開発なので、WebもDBも差分更新のバッチも、同じインスタンス1台に同居させています。答えの決まる処理をPythonへ固定する考え方は、経理の自動化でも同じでした。
「すでにあるインフラ」を勘違いしていた
「すでにある環境に合わせる」という方針は合っていたのに、肝心の「すでにある環境」を私が勘違いしていました。その1台をどこに置くか考えたとき、当初はVPSにセルフホストするつもりでいたんです。すでに使っているインフラに合わせれば、移行も運用も楽だろうという算段でした。
ところが、いざ自分の構成を見直してみたら、使っていたのはVPSではなくAWS Lightsailでした。
そこでLightsailに切り替えました。
月7ドルの1台に、Web・DB・バッチをまとめた
開発時に選んだ最小プラン1台に、Web、PostgreSQL、取得バッチを同居させています。中身はこうです。
- TLS証明書はCaddyとLet’s Encryptで自動取得する
- SSRのWebとバックエンドは同じプロセスで動かし、CloudFrontは使わない
- EDINETの決算データは毎日のスケジュール実行で差分更新する
月7ドルは契約した当時の料金なので、いまの価格はAWSの公式情報で確認してください。アクセスが少なく、DBも約25MBのいまなら、この1台で足りると判断しています。利用が増えてきたら、Web、DB、バッチを分けるかどうかをそのとき考えるつもりです。
「すでにある環境」は記憶ではなく構成図で確かめる
「すでにある環境に合わせる」という考え方自体は、移行も運用も楽にしてくれます。ただ、その環境が実際に何なのかを構成図や稼働状況で確かめないと、見当違いの前提の上に作り始めてしまいます。今回は、その確認を飛ばしたのが反省点でした。
「できたも同然」から、比べられる数字になるまで
EDINETの有価証券報告書は、誰でも取得できます。ただ、約39,000件を集め終えた時点のデータは、ランキングとしてはまだ使いものになりませんでした。横断で比べられるようになったのは、年度をそろえ、会計基準や業種、単体・連結をまたいでタグ候補を整理し、どの値を採用したかをあとから追える形にしてからです。
kessan-trackerは、いまもEDINETのデータを毎日差分更新しています。約3,800社のランキングと各社の決算履歴は、公開中のアプリでそのまま確認できます。
経理のデータを機械に処理させるときに、定型処理へ任せる範囲と人が確かめる範囲をどう分けるか——この線引きの考え方は、経理AIの内部統制の記事にまとめています。
経理AIの内部統制はどう設計する?作業と責任、証跡の分け方経理業務をAIに任せても、承認する人の責任まで移るわけではありません。新規取引先と経費精算の実例から、入…冒頭の「できたも同然」は、集める工程だけを見た勘違いでした。このアプリで難しかったのは、公開情報を集める処理ではなく、集めた数字の意味をそろえる判断のほうです。その判断を経た約39,000件が、いまのランキングの中身になっています。






