XBRLの包括タグ内のTableをBeautifulSoup & Pandasでデータフレームにする方法


はじめまして、XBRLJapanの開発委員会のメンバーKです。

こちらの記事で、PythonのBeautifulSoupを使ってEDINETのXBRLから財務諸表のデータベースを構築する方法を紹介しました。

実は、XBRLをBeautifulSoupで解析することで得られる大きなメリットがあります。今回はその紹介です。

0. こんなニーズありませんか?

XBRLからデータを取得し分析を行ううちに、「詳細タグ付けされていない情報も簡単に取得できればいいのに」と思われる方もいるのではないでしょうか。

インラインXBRLに含まれる企業の開示情報は、基本的に詳細タグまたは包括タグが付されています。

逆に言うと、詳細タグ付けされていない情報も、包括タグの単位では取得可能です。

もし仮に、包括タグ内でHTMLテーブルとして表現されている情報が欲しい情報ならば、それをデータフレームにできる可能性があります。

それは、BeautifulSoupで特定箇所を抜き出しPandasのread_html()で処理することで、有価証券報告書内の特定箇所に含まれるHTMLテーブルをデータフレームにできます。

1. はじめに - セットアップ

Pandasのread_html()を使用するために、html5libというライブラリが必要になりますので、これをインストールします。(その他必要なライブラリもあわせて記載しました)

$ pip install pandas beautifulsoup4 lxml html5lib

2. 包括タグの名称を調べる

有価証券報告書内の特定箇所と包括タグの名称の紐づけは、金融庁が公開しているタクソノミ要素リストを参照してください。

2021年版EDINETタクソノミの公表について
https://www.fsa.go.jp/search/20201110.html

特定箇所の包括タグ名称が分かれば、以下に記載しましたサンプルコードのtag_each_elems = dict_tag.get('jpcrp_cor:MajorFacilitiesTextBlock')のクオーテーション内を変更することで、特定箇所の情報が取得できます。

3. 処理の流れ

BeautifulSoupとPandasを使って、包括タグ内のHTMLテーブルをデータフレームにする手順は以下流れです。

  1. BeautifulSoupでインラインXBRLを読み込む
  2. すべての包括タグで囲われた要素を抜き出す
  3. 抜き出したすべての包括タグを、名称と要素を含む辞書型にする
  4. 辞書の中から、特定名称の要素を抜き出す
  5. 抜き出された要素をPandasのread_html()で処理する。

上記4.で抽出した結果をstr()で囲って、文字列にするのがポイント(上記4. の結果はbs4オブジェクトのため、エラーが出てしまう)

4. サンプルコード

主要な設備の状況のテーブルをデータフレームにするサンプルコードは以下です。

import glob

import pandas as pd
from bs4 import BeautifulSoup

# XBRLのzipファイルを解凍した階層をpath_baseとして指定してください。
path_base = '//****/**/'

def get_df_facilities(arg_docid):
    # インラインXBRLファイルの検索
    list_path_fs = glob.glob(path_base + arg_docid + '/XBRL/PublicDoc/**.htm')
    # インラインXBRLが見つかった場合の処理
    if list_path_fs:
        for path_fs in list_path_fs:
            # fsファイルの読み込み。bs4でパース
            with open(path_fs, encoding='utf-8') as f:
                soup = BeautifulSoup(f.read(), 'lxml')

            # nonNumericタグのみ抽出
            tags_nonnumeric = soup.find_all('ix:nonnumeric')

            # nonnumericの各要素を格納するカラの辞書を作成
            dict_tag = {}
            # nonnumericの内容を辞書型に
            for tag in tags_nonnumeric:
                dict_tag[tag.get('name')] = tag

            # ターゲットとなるタグの要素を取得
            tag_each_elems = dict_tag.get('jpcrp_cor:MajorFacilitiesTextBlock')
            # 辞書型の値をgetして、値がなければnoneが返る。noneはfalse扱いのため、これを条件に分岐。
            if tag_each_elems:
                try:
                    df_facilities = pd.read_html(str(tag_each_elems))
                    return df_facilities
                except ValueError:
                    print('No tables found')

例えば、以下の主要な設備の状況のテーブルは、

以下のようになります。

ですが、残念ながら

  1. カラム名がレコードとして認識されている
  2. HTML上でセル結合されている場合は、重複して値が入っている
  3. 1つの包括タグ内に複数テーブルが存在する可能性があるため、データフレームはリストに格納されて返ってくる
  4. 提出会社の主要な設備の状況と、国内子会社・在外子会社の設備の状況を区別するのが困難

と言った問題がありますが、「手打ちするしか術がない」状況から一歩前進させられるのでは、と思います。

5. 問合せ先

本記事に関する問い合わせは、以下のメールアドレスまでお願いします。
e-mail:[email protected]
(もちろん、qiita上でのコメントも歓迎します)

本メールアドレスは、qiitaの記事を執筆しているXBRLJapanの開発委員会の問合せ窓口になります。
そのため、組織に関する一般的な問合せなどは内容によって回答できかねますが、XBRLに関する技術的な質問、意見、要望、助言等はお気軽にご連絡ください。
なお、委員会メンバが有志で対応しているため、回答に時間がかかることもありますが、ご了承ください。