🐧

「Pythonで学ぶXBRL入門」のコードを改造してみる

2024/12/14に公開

XBRL入門が届いた

技術書典マーケットに出展されていたKB農園さんのPythonで学ぶXBRL入門の本が届きました。
電子版も買っていましたが、せっかくなので紙で読みたいと思い、届くまで待っていました。
XBRLが解説されている紙の本なんて貴重すぎる!

本に掲載されているコードについて

章によってはコードの解説があり、本に載っているコードはすべてgithubに掲載されています。
https://github.com/black76-yy/xbrl_analyze_ontology

4章のコードを改造してみる

4章は「たくさんの有報を自動でダウンロードしよう」となっていて、掲載されているコードを実行すると実際に有価証券報告書がダウンロードできます。
仕組みとしては、特定の期間、例えば2024/5/1~2024/5/31のように指定して、その期間の書類一覧と書類そのものを一気に取得するようになっています。

私がChatGPTを使用して作成したアプリケーションもどきでは、「提出された書類の一覧」と「提出された書類の取得」の処理を分けており、自分が取得したい企業の証券コードを入力して書類を取得する形式であるため、1社ずつ取得する必要があります。
(10年分の書類一覧をデータとして持っていれば、10年分の書類を取得できるようにはしているものの、他社も同時に……という処理は行われていない)

なので、期間指定で書類ごとまとめて取得できるようになっているのはとても便利だと思います。
デメリットとしては、処理に結構時間がかかることでしょうか。1ヶ月の指定でも、大量に書類がアップされる決算期などの場合は、10分以上かかりそうです。

4章のコードを使用して実際に有価証券報告書のXBRLをダウンロードすると、zipのファイル名がドキュメントIDとなっているため、zip名だけだとわかりません。
「企業名で保存できるようプログラムを変更するのも有効でしょう」と書かれているので、さっそく改造してみることにします。

filerNameの追加

会社名をファイル名に使用するために、fileNameを使用します。

download_xbrl.py
def make_doc_id_list(day_list):
    securities_report_doc_list = []
    for index, day in enumerate(day_list):
        url = "https://disclosure.edinet-fsa.go.jp/api/v2/documents.json"
        params = {"date": day.strftime("%Y-%m-%d"),
                  "type": 2,
                  "Subscription-Key": "your_subscription_key"  # Subscription-Keyは自分のAPIキーを使用
                  }

        res = requests.get(url, params=params)
        json_data = res.json()
        print(day)

        if "results" in json_data:
            for num in range(len(json_data["results"])):
                ordinance_code = json_data["results"][num]["ordinanceCode"]
                form_code = json_data["results"][num]["formCode"]
                docinfoeditstatus = json_data["results"][num]["docInfoEditStatus"]

                if ordinance_code == "010" and form_code == "030000" and docinfoeditstatus != 2:
                    print(json_data["results"][num]["filerName"], json_data["results"][num]["docDescription"],
                          json_data["results"][num]["docID"])
+                   securities_report_doc_list.append({
+                       "docID": json_data["results"][num]["docID"],
+                       "filerName": json_data["results"][num]["filerName"]
+                   })

    return securities_report_doc_list

元のコードではdocIDのみが取り出されていたので、会社名であるfilerNameも取り出して辞書に入れるようにしました。
たとえば、docIDS100UVKOfilerName株式会社ライトオンのような辞書になります。

ファイル名に不適切な文字があった場合に置換する関数の作成

ファイル名に会社名を使用するとき、もし不適切な文字があった場合は_に置換する関数を作成しておきます。
(該当する企業があるのかわかっていないです)
この関数を使用するために、先頭行にreを追加します。

download_xbrl.py
import re
download_xbrl.py
def sanitize_filename(name):
    """
    ファイル名として不適切な文字を置換する関数
    """
    return re.sub(r'[\\/*?:"<>|]', '_', name)  # 不正な文字をアンダースコアに置換

変数名をdoc_idからdocに変更

変数名でdoc_idを使用していましたが、filerNameも使用するようになるため、わかりやすいようにdocへ変更します。

download_xbrl.py
def download_xbrl_in_zip(securities_report_doc_list, number_of_lists):
    # ▼ダウンロードする有報を保存しておく場所を指定。もしなければフォルダを作成する。
    save_dir = "/path/to/download/directory/"  # あなたの保存先のパスに変更してください。
    if not os.path.exists(save_dir):
        os.makedirs(save_dir)

+     for index, doc in enumerate(securities_report_doc_list):
+       print(doc, ":", index + 1, "/", number_of_lists)
+       doc_id = doc["docID"]
+       filer_name = sanitize_filename(doc["filerName"])  # 社名をファイル名として使う準備
+       filename = os.path.join(save_dir, f"{filer_name}_{doc_id}.zip")
        url = f"https://disclosure.edinet-fsa.go.jp/api/v2/documents/{doc_id}"
        params = {"type": 1,
                  "Subscription-Key": "your_subscription_key"  # Subscription-Keyは自分のAPIキーを使用
                  }
        res = requests.get(url, params=params, stream=True)

        try:
            if res.status_code == 200:
                with open(filename, 'wb') as file:
                    for chunk in res.iter_content(chunk_size=1024):
                        file.write(chunk)
                print(f"Downloaded and Saved: {filename}")
        except Exception as e:
            print(f"Failed to download file {doc_id}, status code: {e}")

変更箇所のところについて記載します。(変数名が変わっただけで、ほぼ同じ処理です)

  1. forループを使用し、securities_report_doc_listから、1つずつdocを取り出します。
  2. printで処理状況がわかるようにします。
    現在処理しているdocと、全体の中で何件目かわかるようにindex + 1とし、さらに全体の件数をnumber_of_listsで表示しています。
  3. docの中からdocIDというキーで紐づいた値を取り出し、doc_idへ格納します。
  4. docの中から会社名であるfilerNameを取り出します。
    sanitize_filename関数を使用し、ファイル名に使用できない文字がある場合は置換します。
  5. ファイル名に会社名が入るようにします。f"{filer_name}_{doc_id}.zip"とすることで、例えば株式会社ライトオン_S100UVKO.zipといったファイル名になります。

実行結果

試しに、12/11~12/13を入力して実行すると、複数企業の有価証券報告書が取得できることを確認できます。

download_xbrl.pyの実行結果
start_date: 2024-12-11
end_date: 2024-12-13
2024-12-11
ジェイフロンティア株式会社 有価証券報告書-第16(2023/06/012024/05/31) S100UXGJ
株式会社三菱総合研究所 有価証券報告書-第55(2023/10/012024/09/30) S100UXFQ
2024-12-12
株式会社リアルゲイト 有価証券報告書-第16(2023/10/012024/09/30) S100UXKQ
マルサンアイ株式会社 有価証券報告書-第73(2023/09/212024/09/20) S100UX4V
2024-12-13
株式会社中央経済社ホールディングス 有価証券報告書-第87(2023/10/012024/09/30) S100UXSI
株式会社マクアケ 有価証券報告書-第12(2023/10/012024/09/30) S100UY2S
株式会社サイバーエージェント 有価証券報告書-第27(2023/10/012024/09/30) S100UXZA
number_of_lists:  7
get_list:  [{'docID': 'S100UXGJ', 'filerName': 'ジェイフロンティア株式会社'}, {'docID': 'S100UXFQ', 'filerName': '株式会社三菱総合研究所'}, {'docID': 'S100UXKQ', 'filerName': '株式会社リアルゲイト'}, {'docID': 'S100UX4V', 'filerName': 'マルサンアイ株式会社'}, {'docID': 'S100UXSI', 'filerName': '株式会社中央経済社ホールディングス'}, {'docID': 'S100UY2S', 'filerName': '株式会社マクアケ'}, {'docID': 'S100UXZA', 'filerName': '株式会社サイバーエージェント'}]
{'docID': 'S100UXGJ', 'filerName': 'ジェイフロンティア株式会社'} : 1 / 7
Downloaded and Saved: yuuhou/ジェイフロンティア株式会社_S100UXGJ.zip
{'docID': 'S100UXFQ', 'filerName': '株式会社三菱総合研究所'} : 2 / 7
Downloaded and Saved: yuuhou/株式会社三菱総合研究所_S100UXFQ.zip
{'docID': 'S100UXKQ', 'filerName': '株式会社リアルゲイト'} : 3 / 7
Downloaded and Saved: yuuhou/株式会社リアルゲイト_S100UXKQ.zip
{'docID': 'S100UX4V', 'filerName': 'マルサンアイ株式会社'} : 4 / 7
Downloaded and Saved: yuuhou/マルサンアイ株式会社_S100UX4V.zip
{'docID': 'S100UXSI', 'filerName': '株式会社中央経済社ホールディングス'} : 5 / 7
Downloaded and Saved: yuuhou/株式会社中央経済社ホールディングス_S100UXSI.zip
{'docID': 'S100UY2S', 'filerName': '株式会社マクアケ'} : 6 / 7
Downloaded and Saved: yuuhou/株式会社マクアケ_S100UY2S.zip
{'docID': 'S100UXZA', 'filerName': '株式会社サイバーエージェント'} : 7 / 7
Downloaded and Saved: yuuhou/株式会社サイバーエージェント_S100UXZA.zip
download finish

フォルダを開くと、ファイル名に社名が正しく入っていることも確認できます。

Discussion