EDINET APIを使って、神戸物産(3038)の売上と純利益の過去5年分をpythonで取得してみた。
EDINET APIキー取得は、SMS認証が必要!
ブラウザのポップアップ設定で、https://api.edinet-fsa.go.jp を許可しないと、氏名と電話番号が入力できず、APIキーが取得できない!
EDINET APIの検索条件が日付だけしかなくて、証券コードで検索できないのって、おかしくない!?
こんな感じで、日付だけしか検索条件に入れられない…。
https://api.edinet-fsa.go.jp/api/v2/documents.json?date=2023-06-28&type=2&Subscription-Key=XXXXXXX
しょうがないので、過去5年分の日付(365*5=1825回)で、その日の提出リストを取得してきて、pythonで、SQLiteに保存してみた。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 |
import sqlite3 import requests import time from datetime import datetime, timedelta # データベースにデータを保存する関数 def save_to_database(uploadDate, data, db_name="edinet.db"): conn = sqlite3.connect(db_name) c = conn.cursor() # テーブルの作成 c.execute('''CREATE TABLE IF NOT EXISTS lists ( id INT AUTO_INCREMENT PRIMARY KEY, uploadDate DATE NULL, docID VARCHAR(255) NULL, edinetCode VARCHAR(255) NULL, secCode VARCHAR(255) NULL, JCN VARCHAR(255) NULL, filerName VARCHAR(255) NULL, fundCode VARCHAR(255) NULL, ordinanceCode VARCHAR(255) NULL, formCode VARCHAR(255) NULL, docTypeCode VARCHAR(255) NULL, periodStart DATE NULL, periodEnd DATE NULL, submitDateTime DATETIME NULL, docDescription TEXT NULL, issuerEdinetCode VARCHAR(255) NULL, subjectEdinetCode VARCHAR(255) NULL, subsidiaryEdinetCode VARCHAR(255) NULL, currentReportReason VARCHAR(255) NULL, parentDocID VARCHAR(255) NULL, opeDateTime DATETIME NULL, withdrawalStatus TINYINT NULL, docInfoEditStatus TINYINT NULL, disclosureStatus TINYINT NULL, xbrlFlag TINYINT NULL, pdfFlag TINYINT NULL, attachDocFlag TINYINT NULL, englishDocFlag TINYINT NULL, csvFlag TINYINT NULL, legalStatus TINYINT NULL );''') # データの挿入 for item in data['results']: item['uploadDate'] = uploadDate # item辞書に'uploadDate'キーと値を追加 c.execute('''INSERT INTO lists (uploadDate, docID, edinetCode, secCode, JCN, filerName, fundCode, ordinanceCode, formCode, docTypeCode, periodStart, periodEnd, submitDateTime, docDescription, issuerEdinetCode, subjectEdinetCode, subsidiaryEdinetCode, currentReportReason, parentDocID, opeDateTime, withdrawalStatus, docInfoEditStatus, disclosureStatus, xbrlFlag, pdfFlag, attachDocFlag, englishDocFlag, csvFlag, legalStatus) VALUES (:uploadDate, :docID, :edinetCode, :secCode, :JCN, :filerName, :fundCode, :ordinanceCode, :formCode, :docTypeCode, :periodStart, :periodEnd, :submitDateTime, :docDescription, :issuerEdinetCode, :subjectEdinetCode, :subsidiaryEdinetCode, :currentReportReason, :parentDocID, :opeDateTime, :withdrawalStatus, :docInfoEditStatus, :disclosureStatus, :xbrlFlag, :pdfFlag, :attachDocFlag, :englishDocFlag, :csvFlag, :legalStatus)''', item) conn.commit() conn.close() # APIからデータを取得する関数 def fetch_data_for_date(date, subscription_key): url = f'https://api.edinet-fsa.go.jp/api/v2/documents.json?date={date}&type=2&Subscription-Key={subscription_key}' response = requests.get(url) if response.status_code == 200: return response.json() else: print(f"Error fetching data for {date}: {response.status_code}") return None # 2019年1月1日から本日までの日付を生成する関数 def generate_dates_from_2019_to_today(): start_date = datetime(2019, 1, 1) end_date = datetime.now() current_date = start_date while current_date <= end_date: yield current_date.strftime('%Y-%m-%d') current_date += timedelta(days=1) # 主関数 def main(subscription_key): for date in generate_dates_from_2019_to_today(): data = fetch_data_for_date(date, subscription_key) if data and 'results' in data: save_to_database(date, data) print(f"{date}") else: print(f"No data for {date}") time.sleep(1) # APIリクエストの間に1秒待機 # APIキーを置き換えて実行 subscription_key = 'xxxxx' main(subscription_key) |
ここまでやって、ようやくダウンロードリストのIDが取得できた!
secCodeが銘柄コード+最後に0を追加らしい
docType
120 有価証券報告書/130 訂正有価証券報告書
140 四半期報告書 /150 訂正四半期報告書
180 臨時報告書 /190 訂正臨時報告書
1 2 3 4 5 6 7 8 |
-- 年一の決算書(有価証券報告書)のドキュメントIDを取得 select docID from lists where secCode = '30380' and docTypeCode='120'; |
以下のdocIDが取得できた。S100SP8Xが一番新しいので、それを使おう
S100F0RM
S100HUKA
S100KL8G
S100NAJY
S100Q1QH
S100SP8X
docIDを指定して、type=5がCSVファイル指定。ダウンロードすると
https://api.edinet-fsa.go.jp/api/v2/documents/S100SP8X?type=5&Subscription-Key=XXXXXX
S100SP8X_5.zipがダウンロードされて、5ファイル入ってた。
jpaud-aai-cc-001_E02999-000_2023-10-31_01_2024-01-31.csv
jpaud-aar-cn-001_E02999-000_2023-10-31_01_2024-01-31.csv
jpaud-qrr-cc-001_E02999-000_2024-01-31_01_2024-03-15.csv
jpcrp030000-asr-001_E02999-000_2023-10-31_01_2024-01-31.csv
jpcrp040300-q1r-001_E02999-000_2024-01-31_01_2024-03-15.csv
このCSVファイルから、売上を取得するには
要素ID=jpcrp_cor:NetSalesSummaryOfBusinessResults
コンテキストID=CurrentYearDuration_NonConsolidatedMember
の値を取得するpythonを組まないと…。
参考URL
https://zenn.dev/tomodo_ysys/articles/edinet-chatgpt-financial-report
https://qiita.com/shunyaorad/items/7b53653caad514457f59
EDINET APIを使って、神戸物産(3038)の売上と純利益の過去5年分をpythonで取得してみた。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
import os import io import csv import requests import zipfile def download_edinet_documents(): api_key = '' docID = 'S100SP8X' # ダウンロード先フォルダ filer_name_dir = 'docs' os.makedirs(filer_name_dir, exist_ok=True) # EDINETからpdfを取得 url = f"https://api.edinet-fsa.go.jp/api/v2/documents/{docID}?type=2&Subscription-Key={api_key}" response = requests.request("GET", url) with open(os.path.join(filer_name_dir, f"{docID}.pdf"), "wb") as f: f.write(response.content) # EDINETからzipを取得 url = f"https://api.edinet-fsa.go.jp/api/v2/documents/{docID}?type=5&Subscription-Key={api_key}" response = requests.request("GET", url) # ZIPファイルを解凍する with zipfile.ZipFile(io.BytesIO(response.content)) as z: z.extractall(filer_name_dir) return # ダウンロードした文書から必要な情報をCSVファイルから抽出する def extract_content_from_csv(): content_data = {} filer_name_dir = 'docs' # 解凍したzipのXBRL_TO_CSVフォルダ内のjpcrpから始まるcsvファイルを解析する for file in os.listdir(os.path.join(filer_name_dir, "XBRL_TO_CSV")): if file.startswith("jpcrp") and file.endswith(".csv"): csv_path = os.path.join(filer_name_dir, "XBRL_TO_CSV", file) with open(csv_path, "r", encoding="utf-16") as csv_file: reader = csv.reader(csv_file, delimiter="\t") for row in reader: # 要素ID&コンテキストIDで検索 # 売上 if row[0] == 'jpcrp_cor:NetSalesSummaryOfBusinessResults' and row[2] == 'CurrentYearDuration_NonConsolidatedMember': content_data["netsale"] = row[8] # 1~4期前の売上 if row[0] == 'jpcrp_cor:NetSalesSummaryOfBusinessResults' and row[2] == 'Prior1YearDuration_NonConsolidatedMember': content_data["netsale1"] = row[8] if row[0] == 'jpcrp_cor:NetSalesSummaryOfBusinessResults' and row[2] == 'Prior2YearDuration_NonConsolidatedMember': content_data["netsale2"] = row[8] if row[0] == 'jpcrp_cor:NetSalesSummaryOfBusinessResults' and row[2] == 'Prior3YearDuration_NonConsolidatedMember': content_data["netsale3"] = row[8] if row[0] == 'jpcrp_cor:NetSalesSummaryOfBusinessResults' and row[2] == 'Prior4YearDuration_NonConsolidatedMember': content_data["netsale4"] = row[8] # 純利益 elif row[0] == 'jpcrp_cor:ProfitLossAttributableToOwnersOfParentSummaryOfBusinessResults' and row[2] == 'CurrentYearDuration': content_data["profit"] = row[8] # 1~4期前の純利益 elif row[0] == 'jpcrp_cor:ProfitLossAttributableToOwnersOfParentSummaryOfBusinessResults' and row[2] == 'Prior1YearDuration': content_data["profit1"] = row[8] elif row[0] == 'jpcrp_cor:ProfitLossAttributableToOwnersOfParentSummaryOfBusinessResults' and row[2] == 'Prior2YearDuration': content_data["profit2"] = row[8] elif row[0] == 'jpcrp_cor:ProfitLossAttributableToOwnersOfParentSummaryOfBusinessResults' and row[2] == 'Prior3YearDuration': content_data["profit3"] = row[8] elif row[0] == 'jpcrp_cor:ProfitLossAttributableToOwnersOfParentSummaryOfBusinessResults' and row[2] == 'Prior4YearDuration': content_data["profit4"] = row[8] return content_data # pdfとcsvをダウンロード download_edinet_documents() # csvから必要なデータを取得 content_data = extract_content_from_csv() print(f"売上:{content_data['netsale']} {int(content_data['netsale']) / int(content_data['netsale1'])}") print(f"売上:{content_data['netsale1']} {int(content_data['netsale1']) / int(content_data['netsale2'])}") print(f"売上:{content_data['netsale2']} {int(content_data['netsale2']) / int(content_data['netsale3'])}") print(f"売上:{content_data['netsale3']} {int(content_data['netsale3']) / int(content_data['netsale4'])}") print(f"売上:{content_data['netsale4']}") print(f"純利益:{content_data['profit']} {int(content_data['profit']) / int(content_data['profit1'])}") print(f"純利益:{content_data['profit1']} {int(content_data['profit1']) / int(content_data['profit2'])}") print(f"純利益:{content_data['profit2']} {int(content_data['profit2']) / int(content_data['profit3'])}") print(f"純利益:{content_data['profit3']} {int(content_data['profit3']) / int(content_data['profit4'])}") print(f"純利益:{content_data['profit4']}") |
売上:446858000000 1.1310226478896055
売上:395092000000 1.0655928710905893
売上:370772000000 1.1101157505823458
売上:333994000000 1.2063860143396363
売上:276855000000
純利益:20560000000 0.9869431643625192
純利益:20832000000 1.0632911392405062
純利益:19592000000 1.302053565494783
純利益:15047000000 1.2480922362309224
純利益:12056000000
EDINETのAPI、データ形式が分かりづらすぎて辛い…。