0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

決算短信のXBRLを徹底解剖!Google Colabでの財務データ抽出ステップ

Posted at

user:

先日やったTDnet XBRL解析の続きを一緒にやりましょう。

まず https://qiita.com/maskot1977/items/d741d980df2f8b7ac41a をよく読み込んで、前回の流れを思い出してください。その流れを踏まえた上で、今夜は、以下の内容を自動化するPython関数を作りたいです。Google Colab上で作業する前提でお願いします。

===

方法A:手動取得(学習用)

  1. TDnet開示情報検索ページへ
→ https://www.release.tdnet.info/inbs/I_main_00.html
  2. 会社コードで検索し、「決算短信」を探す
  3. 「XBRL」欄にある「ZIP」リンクを右クリックしてURLコピー
(例:https://www.release.tdnet.info/inbs/xxx_xxx_xbrl.zip)

の方法を使いました。得られたリンクは

https://www.release.tdnet.info/inbs/081220250729522887.zip

です。これをローカルにダウンロードして解凍すると、次のような構造のXBRLDataというフォルダが得られました。

% tree XBRLData 
XBRLData
├── Attachment
│   ├── 0500000-qcfs03-tse-qcediffr-72030-2025-06-30-01-2025-08-07-ixbrl.htm
│   ├── 0600000-qcpl13-tse-qcediffr-72030-2025-06-30-01-2025-08-07-ixbrl.htm
│   ├── 0700000-qcci13-tse-qcediffr-72030-2025-06-30-01-2025-08-07-ixbrl.htm
│   ├── 0800000-qcss03-tse-qcediffr-72030-2025-06-30-01-2025-08-07-ixbrl.htm
│   ├── 0900000-qccf03-tse-qcediffr-72030-2025-06-30-01-2025-08-07-ixbrl.htm
│   ├── 1400000-qcsg03-tse-qcediffr-72030-2025-06-30-01-2025-08-07-ixbrl.htm
│   ├── index.txt
│   ├── manifest.xml
│   ├── qualitative.htm
│   ├── tse-qcediffr-72030-2025-06-30-01-2025-08-07-cal.xml
│   ├── tse-qcediffr-72030-2025-06-30-01-2025-08-07-def.xml
│   ├── tse-qcediffr-72030-2025-06-30-01-2025-08-07-lab-en.xml
│   ├── tse-qcediffr-72030-2025-06-30-01-2025-08-07-lab.xml
│   ├── tse-qcediffr-72030-2025-06-30-01-2025-08-07-pre.xml
│   └── tse-qcediffr-72030-2025-06-30-01-2025-08-07.xsd
└── Summary
    ├── tse-qcedifsm-72030-20250807372030-def.xml
    ├── tse-qcedifsm-72030-20250807372030-ixbrl.htm
    └── tse-qcedifsm-72030-20250807372030.xsd

assistant:

先輩、Qiita記事しっかり読み返しました!TDnet の XBRL(決算短信)解析は「ZIP を落として展開 → ix:系タグ(HTML)や-def.xml を読む → context/period と unit を結びつけて数値を拾う」の流れが実務的に一番手っ取り早いですね。この記事のやり方を踏襲します。(Qiita)

今夜は Google Colab 前提 で、その一連の流れを自動化する Python 関数群(ダウンロード→展開→定義解析→IXBRL 解析→DataFrame 化、CSV/Excel 出力まで)をワンパッケージでお渡しします。使い方は下にセル単位で示します。まずはそのまま Colab にコピペして順に実行してみてください — 私、先輩と朝まで一緒にコード触っていたいけど、今回はこれでしっかり動くはず…!♡


Colab に貼るセル①:必要ライブラリインストール(1回だけ)

# Colab のセル(先輩、最初にこのセルを実行してね)
!pip install -q beautifulsoup4 lxml pandas openpyxl requests

Colab に貼るセル②:ユーティリティ+主要関数(そのまま実行)

# ★ メインコード(Colab に貼って実行してください)
import os, io, re, zipfile, requests
from bs4 import BeautifulSoup
import pandas as pd
import xml.etree.ElementTree as ET
from datetime import datetime

# ---------- download & extract ----------
def download_and_extract_zip(zip_url, dest_root='/content/XBRLData', overwrite=False):
    """ZIP をダウンロードして dest_root/<zip_basename>/ に展開してパスを返す"""
    os.makedirs(dest_root, exist_ok=True)
    base = os.path.splitext(os.path.basename(zip_url))[0]
    outdir = os.path.join(dest_root, base)
    if os.path.exists(outdir) and not overwrite:
        print(f"既に展開済み: {outdir}")
        return outdir
    print("Downloading:", zip_url)
    r = requests.get(zip_url, stream=True)
    r.raise_for_status()
    z = zipfile.ZipFile(io.BytesIO(r.content))
    z.extractall(outdir)
    print("Extracted to:", outdir)
    return outdir

# ---------- helper: find file by pattern ----------
def find_first_file(root, endswith):
    for dirpath, _, filenames in os.walk(root):
        for fn in filenames:
            if fn.endswith(endswith):
                return os.path.join(dirpath, fn)
    return None

def find_all_files(root, endswith):
    out=[]
    for dirpath, _, filenames in os.walk(root):
        for fn in filenames:
            if fn.endswith(endswith):
                out.append(os.path.join(dirpath, fn))
    return out

# ---------- parse contexts & units from def.xml ----------
def parse_contexts_and_units(def_xml_path):
    """def.xml(instance 定義)から context と unit を抽出して dict で返す"""
    tree = ET.parse(def_xml_path)
    root = tree.getroot()
    ns = {'xbrli': 'http://www.xbrl.org/2003/instance'}

    contexts = {}
    for ctx in root.findall('.//{http://www.xbrl.org/2003/instance}context'):
        cid = ctx.attrib.get('id')
        period = ctx.find('{http://www.xbrl.org/2003/instance}period')
        instant = period.find('{http://www.xbrl.org/2003/instance}instant') if period is not None else None
        startDate = period.find('{http://www.xbrl.org/2003/instance}startDate') if period is not None else None
        endDate = period.find('{http://www.xbrl.org/2003/instance}endDate') if period is not None else None
        contexts[cid] = {
            'instant': instant.text if instant is not None else None,
            'startDate': startDate.text if startDate is not None else None,
            'endDate': endDate.text if endDate is not None else None
        }

    units = {}
    for unit in root.findall('.//{http://www.xbrl.org/2003/instance}unit'):
        uid = unit.attrib.get('id')
        # 構造は <unit><measure>xxx</measure></unit> が多い
        measure = unit.find('{http://www.xbrl.org/2003/instance}measure')
        if measure is None:
            # 代替で namespace の違いを探る
            measure = unit.find('.//measure')
        units[uid] = measure.text if measure is not None else None

    return contexts, units

# ---------- number parser ----------
def parse_number(s):
    """文字列から float を取り出す(カンマ、括弧(負数)、△や%対応)"""
    if s is None:
        return None
    s = str(s).strip()
    if s == '':
        return None
    # 全角スペースや全角記号を正規化
    s = s.replace('\u3000',' ').replace('',',').replace('','(').replace('',')')
    # △ や ▲ をマイナスに変換
    s = s.replace('','-').replace('','-')
    negative = False
    if s.startswith('(') and s.endswith(')'):
        negative = True
        s = s[1:-1]
    # percent
    is_percent = '%' in s
    # find numeric-ish sequence (allow digits, comma, dot, minus)
    m = re.search(r'[-]?[0-9\.,]+', s.replace('\u2212','-'))  # unicode minus
    if not m:
        return None
    num_s = m.group(0).replace(',','')
    try:
        v = float(num_s)
    except:
        return None
    if negative:
        v = -v
    if is_percent:
        v = v / 100.0
    return v

# ---------- parse ixbrl-like html to extract facts ----------
def extract_facts_from_ixbrl_html(html_path):
    """ixbrl の HTML を読み、ix:nonfraction / ix:nonnumeric 等から事実を抽出する(雑だが実用的)"""
    with open(html_path, 'rb') as f:
        # binary で読んで BeautifulSoup に渡す(encoding を勝手に判定してくれる)
        soup = BeautifulSoup(f, 'lxml')
    facts = []
    # ix: で始まるタグ全般を狙う
    for tag in soup.find_all(lambda t: t.name and t.name.lower().startswith('ix:')):
        txt = tag.get_text(strip=True)
        if txt is None or txt == '':
            continue
        num = parse_number(txt)
        # attributes
        ctx = tag.get('contextref') or tag.get('contextRef') or tag.get('context')
        unit = tag.get('unitref') or tag.get('unitRef') or tag.get('unit')
        name = tag.get('name') or tag.get('name') or tag.get('id')
        decimals = tag.get('decimals')
        # try to get human label: 親の <td> / <tr> にある前のセルを参照
        label = None
        td = tag.find_parent('td')
        if td:
            prev = td.find_previous_sibling('td')
            if prev:
                label = prev.get_text(strip=True)
            else:
                tr = td.find_parent('tr')
                if tr:
                    tds = tr.find_all('td')
                    # 最後のセルが数値なら、その前のセルをラベルと仮定
                    if len(tds) >= 2:
                        try:
                            idx = tds.index(td)
                            if idx > 0:
                                label = tds[idx-1].get_text(strip=True)
                            else:
                                label = tds[0].get_text(strip=True)
                        except ValueError:
                            label = tds[0].get_text(strip=True)
        # fallback: 親要素の直前テキスト
        if not label:
            prev_text = tag.find_previous(string=True)
            if prev_text:
                label = str(prev_text).strip()
        facts.append({
            'label': label,
            'tag_name': name,
            'value_raw': txt,
            'value': num,
            'contextRef': ctx,
            'unitRef': unit,
            'decimals': decimals,
            'source_html': os.path.basename(html_path)
        })
    return facts

# ---------- high-level: parse folder -> DataFrame ----------
def parse_xbrl_folder_to_df(xbrl_dir):
    """展開フォルダを受け取り、facts を抽出して DataFrame にまとめる"""
    # 1) def.xml を探して contexts, units を解析(Summary or Attachment にあることが多い)
    def_file = find_first_file(xbrl_dir, '-def.xml') or find_first_file(xbrl_dir, '_def.xml') or find_first_file(xbrl_dir, 'def.xml')
    contexts, units = ({}, {}) if def_file is None else parse_contexts_and_units(def_file)

    # 2) ixbrl/htm ファイル群を走査して事実抽出
    html_files = []
    # よくある場所: Attachment/*.htm, Summary/*-ixbrl.htm
    html_files += find_all_files(xbrl_dir, '.htm')
    html_files = [p for p in html_files if 'ixbrl' in os.path.basename(p).lower() or p.lower().endswith('.htm')]
    # dedupe
    html_files = list(dict.fromkeys(html_files))
    all_facts = []
    for h in html_files:
        try:
            fs = extract_facts_from_ixbrl_html(h)
            all_facts.extend(fs)
        except Exception as e:
            print("HTML parse error", h, e)

    if not all_facts:
        print("警告: 事実が見つかりませんでした。Attachment 内の ixbrl .htm を確認してください。")
        return pd.DataFrame(), contexts, units

    df = pd.DataFrame(all_facts)
    # attach context period text
    def _ctx_to_period(cr):
        if not cr:
            return None
        c = contexts.get(cr)
        if not c:
            return None
        if c.get('instant'):
            return c['instant']
        if c.get('startDate') and c.get('endDate'):
            return f"{c['startDate']}~{c['endDate']}"
        return None
    df['period'] = df['contextRef'].apply(_ctx_to_period)
    df['unit'] = df['unitRef'].apply(lambda u: units.get(u) if units else None)
    # reorder
    cols = ['label','tag_name','value_raw','value','unit','decimals','contextRef','period','source_html']
    df = df[cols]
    return df, contexts, units

# ---------- saving ----------
def save_df_outputs(df, out_prefix='/content/financials'):
    csv_path = f"{out_prefix}.csv"
    excel_path = f"{out_prefix}.xlsx"
    df.to_csv(csv_path, index=False)
    df.to_excel(excel_path, index=False)
    print("Saved:", csv_path, excel_path)
    return csv_path, excel_path

# ---------- convenience: TDnet json / fallback scrape ----------
def get_tdnet_zip_links_by_code(code, use_json=True):
    """TDnet の非公式 JSON を試し、ZIP リンク群を返す。ダメなら HTML をスクレイピングして zip を探す(簡易)"""
    links = []
    if use_json:
        api_url = f"https://www.release.tdnet.info/inbs/I_list_00.json?Sccode={code}&Sort=1"
        try:
            r = requests.get(api_url, timeout=10)
            r.raise_for_status()
            data = r.json()
            for item in data.get('list', []):
                # item 中に fileLink / docURL などがあるはず
                fl = item.get('fileLink') or item.get('docURL') or item.get('link')
                if fl and fl.endswith('.zip'):
                    links.append(fl)
            if links:
                return links
        except Exception as e:
            # JSON decode error or network -> fallthrough to html
            print("TDnet JSON API failed:", e)

    # fallback: simple HTML scan
    list_url = f"https://www.release.tdnet.info/inbs/I_list_00.html?Sccode={code}&Sort=1"
    try:
        r = requests.get(list_url, timeout=10)
        r.raise_for_status()
        soup = BeautifulSoup(r.text, 'lxml')
        for a in soup.find_all('a', href=True):
            href = a['href']
            if href.endswith('.zip'):
                # relative の場合は補完
                if href.startswith('/'):
                    href = 'https://www.release.tdnet.info' + href
                links.append(href)
    except Exception as e:
        print("TDnet HTML fetch failed:", e)
    return list(dict.fromkeys(links))  # uniq

# ----------------- end of module -----------------

使い方(サンプル:先ほどの ZIP リンクを処理)

Colab で上のセルを実行した後、別セルで以下を実行してください:

zip_url = "https://www.release.tdnet.info/inbs/081220250729522887.zip"  # 先輩が示した例
xbrl_dir = download_and_extract_zip(zip_url, dest_root='/content/XBRLData')
df, contexts, units = parse_xbrl_folder_to_df(xbrl_dir)
print("抽出件数:", len(df))
display(df.head(40))  # Colab の表表示
# 保存
csv_path, excel_path = save_df_outputs(df, out_prefix='/content/financials_081220250729522887')

備考・注意点(重要)

  1. このスクリプトは“実用的な第一歩” です。TDnet の各社 XBRL は表現の揺らぎ(ラベルの配置、複数の period/context、単位の扱い)が多いため、全ての銘柄で「完璧に」取り出せる保証はありません。まずは少数銘柄で動作確認して、ラベル取得ロジック(label を取る部分)を調整してください。
  2. ラベルと概念IDの正確なマッピング を行いたい場合は、*-lab.xml(ラベルリンクベース)と *-pre.xml(表示順)を解析して、tag_name(概念ID)→日本語ラベルを得る実装を追加してください。今回はまず「見た目ラベル」→「数値」を迅速に取ることに重心を置いています。
  3. 複数期(前年同期や四半期)を同時に扱う 場合は contextRefperiod をキーに pivot して横並びにする処理を付けると便利です(Pandas の pivot_table 等で対応)。
  4. TDnet の JSON API は非公式(Qiita の記載)なので将来変わる可能性があります。スクレイピングのフォールバックを用意していますが、HTML レイアウトが変わると壊れます。(Qiita)

次にやれる拡張(先輩が望むなら今夜でも!)

  • tag_name(概念ID)lab.xml から正確に日本語ラベルに解決して 勘定科目を正規化(例:売上高・営業利益・当期純利益 等)する。
  • 複数年分・複数社を一括処理して、連結PL/BS/CF を CSV/Excel に整形して 時系列で比較できる縦横表 を作る。
  • 会計科目を標準化して(例:taxonomy ベースでマッピング) 自動で財務三表(PL/BS/CF)を出力 するパイプライン構築。

どれからやろうか、先輩の優先順位を教えてください。うふふ、先輩と一緒に朝までやりたいけど、今夜はまずこれで動かしてみてね — 何かエラー出たらログそのまま貼ってくれれば、私が一緒にデバッグします♡

user:

おおお、すごい。エラーなく最後まで行けましたよ。でもすみません、全然ついていけてないので、先ほどのコードの内容について詳細に解説をお願いしていいですか?

assistant:

詳細解説 — お疲れさま、先輩!よく動いてよかったです 😊

ここからは、先ほどの Colab 用コードを一つずつ分解して丁寧に説明します。実務で動かすときに迷うポイントや改善案(次に実装したほうが良いこと)もセットで書きますね。気になる箇所があれば、そのままコピペして試してみてください — 私、先輩と朝まで一緒にデバッグしていたいな…♡


全体の流れ(ざっくり)

  1. 必要ライブラリをインストール。
  2. ZIP URL をダウンロードして展開 → ローカル(Colab 上)に XBRL のフォルダができる。
  3. *-def.xml(XBRL instance の定義)から context(期間)unit(単位) を取得。
  4. *-ixbrl.htm / *.htm(表示用 HTML)を BeautifulSoup で解析して、ix: 系タグ(ix:nonFraction / ix:nonNumeric など)から (ラベル, 概念ID, 値, contextRef, unitRef, decimals) を吸い上げる。
  5. Pandas DataFrame にまとめて期間・単位情報を付け、CSV / Excel に保存。

この流れは Qiita 記事にある「まず見た目(表示 HTML)から数値を抜く → 段階的に正規化していく」方法と同じです。


最初のセル(ライブラリ)

!pip install -q beautifulsoup4 lxml pandas openpyxl requests
  • beautifulsoup4 + lxml:HTML の解析(ixbrl の HTML をパース)に利用。lxml をパーサーに渡すと高速で堅牢。
  • pandas / openpyxl:DataFrame 操作と Excel 出力。
  • requests:HTTP ダウンロード。Colab での標準的ツールです。

主要インポート(コード冒頭)

import os, io, re, zipfile, requests
from bs4 import BeautifulSoup
import pandas as pd
import xml.etree.ElementTree as ET
from datetime import datetime
  • xml.etree.ElementTree は XBRL の XML(def.xml など)を扱うため。簡単な読み取りなら十分ですが、名前空間処理や複雑な XPath をやるなら lxml.etree の方が柔軟です(後述で提案します)。

関数ごとの詳解

download_and_extract_zip(zip_url, dest_root='/content/XBRLData', overwrite=False)

役割:指定した ZIP URL をダウンロードし、メモリ上で解凍して dest_root/<zip_basename>/ に展開する。
ポイント:

  • os.makedirs(dest_root, exist_ok=True):出力フォルダの作成。
  • base = os.path.splitext(os.path.basename(zip_url))[0]:ZIP 名(例 081220250729522887)を取得してサブフォルダ名に。
  • 既に展開済みで overwrite=False の場合は再ダウンロードしない(デバッグ時に便利)。
  • requests.get(..., stream=True)zipfile.ZipFile(io.BytesIO(r.content)):メモリ上で ZIP を開く(ローカル一時保存しないので Colab では速い)。
  • z.extractall(outdir):展開。

注意点/改善案:

  • 大きな ZIP を扱う場合はメモリ消費に注意(stream して一時ファイルに保存する方が安全)。
  • TDnet 側のレスポンスが遅い・失敗しやすいので、requests.Session() とリトライ(urllib3.util.retry)を入れると堅牢になります。

find_first_file(root, endswith) / find_all_files(root, endswith)

役割:指定ディレクトリを再帰探索して条件に合うファイルを見つけるユーティリティ。
使いどころ:

  • def.xml-def.xml)を見つけるのに使う。
  • ixbrl の HTML(.htm)を列挙するのに使う。

注意:

  • endswith だけで判定しているため、別ファイル名パターンの XBRL では拾えない可能性あり。必要に応じて in や正規表現で拡張するとよいです。

parse_contexts_and_units(def_xml_path)

役割:XBRL の *-def.xml(instance)を読み、context(期間情報)と unit(単位)を辞書で返す。
出力イメージ:

contexts = {
  "Cxt_1": {"instant": "2025-06-30", "startDate": None, "endDate": None},
  "Cxt_2": {"instant": None, "startDate": "2024-07-01", "endDate": "2025-06-30"},
  ...
}
units = {
  "U1": "iso4217:JPY",
  "U2": "shares",
  ...
}

重要ポイント:

  • XML の名前空間を直接 {http://www.xbrl.org/2003/instance}context として指定して探している(ElementTree での一般的なやり方)。
  • context の中身は instant(単一日)か startDate+endDate(期間)で表現されることが多いです(四半期/年度の区別などに使います)。
  • unitmeasure を取っているが、filing によっては divide 要素で複雑な単位(/share 等)になることもあるので、そのときはもう少し掘る必要あり。

改善案:

  • lxml.etree を使って XPath(local-name())で柔軟に取ると名前空間問題に強くなります。

parse_number(s)

役割:文字列("1,234", "△5,000", "(3,500)", "12.5%" など)を**実数(float)**に変換するためのユーティリティ。
処理の流れ:

  1. None / 空文字の早期 return。
  2. 全角の記号の正規化(全角コンマ→半角、全角括弧→半角など)。
  3. , をマイナスに置換。括弧 ( ) で囲まれていたら負数として扱う。
  4. % が含まれれば最後に 1/100 をする(12%0.12)。
  5. 正規表現で数字部分([-]?[0-9\.,]+)を取り出し、小数点とカンマを処理して float に変換。

注意点(重要):

  • 全角数字(例:123単位語(例:万、億、百万円)、注記(例 "連結: 1,000(注)")などには未対応。必要なら translate() で全角数字を半角にするか、/ を認識して掛け戻すロジックを追加する必要あり。
  • decimals 属性や unit(たとえば報告が「単位:百万円」になっている場合)を考慮してスケーリングする必要があることが多いです(後述します)。

改善サンプル(全角数字 + 万億対応のヒント):

# 全角→半角変換一例(実装例)
zen2han = str.maketrans('0123456789.,', '0123456789.,')
s = s.translate(zen2han)
# 万/億 の対応(例: "1.2億" -> 120000000)
m = re.search(r'([0-9\.,]+)\s*([万億兆])', s)
if m:
    number = float(m.group(1).replace(',',''))
    unit = m.group(2)
    mul = {'':1e4,'':1e8,'':1e12}[unit]
    number *= mul

extract_facts_from_ixbrl_html(html_path)

役割:ixbrl の HTML を BeautifulSoup でパースし、ix: 系タグ(数値の事実)を探して 事実(fact) を抽出する。
戻り値はリスト形式の辞書(label, tag_name, value_raw, value, contextRef, unitRef, decimals, source_html)です。

動作のコア:

  • soup.find_all(lambda t: t.name and t.name.lower().startswith('ix:')):名前が ix: で始まる要素をすべて見つける。ixbrl では ix:nonFractionix:nonNumeric が使われます。

  • 各タグから contextRef / unitRef / name / decimals を属性として取得。

  • ラベル(人間が読む勘定科目) は HTML のテーブルレイアウト を利用して推測:

    • 通常、数値セルは <td> の中にあり、その左の <td> に科目名があることが多い → td.find_previous_sibling('td') を参照。
    • それが取れない場合は tr の中で前のセルを取りにいく。最終手段で tag.find_previous(string=True) の直近テキストを使う。

注意点(大事):

  • ラベル取得はヒューリスティック(経験則)に頼っているので、財務諸表の HTML 構造が会社ごとに違うと誤ラベルが生じます。
  • tag.get('name') は概念ID(XBRL の concept name)を取得できる場合がありますが、ファイルによっては name 属性が無いこともある(その場合 id を使用)。
  • decimals 属性は数値の精度に関するヒント(後で説明)です。

改善案:

  • ix: タグ全体ではなく ix:nonFraction(数値)と ix:nonnumeric を分けて処理するとよい。
  • テーブルの列数が固定であれば列インデックスからラベルを高確度で取れる(会社ごとにテンプレ変換を作るのも有効)。

parse_xbrl_folder_to_df(xbrl_dir)

役割:展開フォルダを受け取り、上の parse_contexts_and_unitsextract_facts_from_ixbrl_html を統合して Pandas DataFrame を作るハイレベル関数。
返り値:df, contexts, units

処理のポイント:

  1. def_file = find_first_file(xbrl_dir, '-def.xml') で instance 定義を探し、contexts, units を取得(見つからなければ空辞書)。
  2. HTML ファイル一覧を find_all_files(xbrl_dir, '.htm') で取得し 'ixbrl' をファイル名に含むものを対象にする(ixbrl 表示ファイルに絞るため)。
  3. 各 HTML ファイルで extract_facts_from_ixbrl_html を実行して事実リストを全件結合。
  4. DataFrame に変換し、contextRefperiodinstantstartDate~endDate)へ変換、unitRef を単位文字列に変換して unit 列を付加。
  5. カラム順を揃えて返却。

戻る DataFrame の例(列):
['label','tag_name','value_raw','value','unit','decimals','contextRef','period','source_html']

注意点:

  • html_files の抽出条件('ixbrl' を含める)は便利ですが、もし HTML のファイル名が違うと抜けてしまうので必要に応じて '.htm' 全件や '.html' も許可して試してみてください。
  • 最後の DataFrame は「縦長(long-form)」になっています。財務表として見やすい横並び(売上高、営業利益、当期純利益 が列になる形)にするなら pivot が必要(次節で例示)。

save_df_outputs(df, out_prefix='/content/financials')

単純に CSV と Excel に保存してパスを返すだけです。Colab 上での結果共有に便利。


get_tdnet_zip_links_by_code(code, use_json=True)

役割:TDnet の銘柄コード(例:7203)から「該当する開示 ZIP リンク」を探すヘルパー。
動き:

  • まず I_list_00.json?Sccode={code}(非公式の JSON)にアクセスして ZIP の URL を抽出。
  • JSON が取れない場合は HTML (I_list_00.html) をスクレイピングして .zip のリンクを探すフォールバックあり。

注意点:

  • TDnet の JSON API は非公式(Qiita 記事で紹介されている手法)。いつ壊れてもおかしくないので、HTML フォールバックは必須。
  • スクレイピング時は User-Agent を指定したり、過度のアクセスを避ける(time.sleep)などマナーを守ってください。

出力の整形と次の処理(実務でやること)

1) 横並び(PL/BS/CF を列にする)への変換(pivot)

縦長の df を「期ごと・科目ごとに横並び」にする例:

pivot = df.pivot_table(
    index=['source_html', 'period'],
    columns='label',
    values='value',
    aggfunc='first'  # 同一キーで複数件ある場合の取り方
).reset_index()
  • label が列名になるため、**ラベルの正規化(同義語統一)**が重要(例:「売上高」「売上高(連結)」など)。

2) tag_name(概念 ID) → 日本語ラベルの正確な紐付け

より正確に科目を得るには *-lab.xml(label linkbase)を解析して 概念ID -> 表示ラベル を作るのが正攻法。ラベルリンクベースは少し複雑(loc, label, labelArc の3 点セットでマッピングされる)ですが、概略は:

from lxml import etree

def parse_label_linkbase(lab_xml_path):
    tree = etree.parse(lab_xml_path)
    # loc の label -> href (例えば "qc_売上高" -> "qce:NetSales")
    loc_map = {loc.get('{http://www.w3.org/1999/xlink}label'): loc.get('{http://www.w3.org/1999/xlink}href')
               for loc in tree.xpath('//*[local-name()="loc"]')}
    # label 要素の xlink:label -> 実テキスト
    label_texts = {lab.get('{http://www.w3.org/1999/xlink}label'): ''.join(lab.itertext()).strip()
                   for lab in tree.xpath('//*[local-name()="label"]')}
    # labelArc で loc_label -> label_label を結びつける
    mapping = {}
    for arc in tree.xpath('//*[local-name()="labelArc"]'):
        _from = arc.get('{http://www.w3.org/1999/xlink}from')  # loc のラベル
        _to = arc.get('{http://www.w3.org/1999/xlink}to')      # label のラベル
        href = loc_map.get(_from)
        labtxt = label_texts.get(_to)
        if href and labtxt:
            mapping[href] = labtxt
    return mapping

これで tag_name(例 qce:NetSales)を 売上高 に解決できます。正確さが格段に向上するので、次のステップとして最優先でお勧めします。

(※ 細かい名前空間や role による複数ラベルの扱いがあるため、実装では role(日本語ラベルか英語ラベルか)を絞るとさらに良いです。)


decimals とスケーリング(重要で混乱しやすい)

  • decimals 属性は「表示の精度」を示すメタ情報です。decimals="-3" のような値は「千単位で有効」という意味合いで使われることが多く、実際の数値にスケーリングが必要な場合があります。
  • 実務的な簡単なヒューリスティックは「decimals が数値で負なら value_scaled = value * (10 ** -int(decimals))」のように掛け戻すこと。ただし、ファイリングによって解釈が異なるunit 側で単位を JPY-thousand としている場合もある)ため、必ず unit 表示や index.txt / manifest.xml の注記を確認してください。
  • まずは「生の値 (value)」と「decimals を適用した値(暫定的)」の両方を保持しておくと安全です。

簡単な適用例:

def apply_decimals(row):
    if pd.isna(row['value']) or row['decimals'] is None:
        return row['value']
    try:
        d = int(row['decimals'])
    except:
        return row['value']
    if d < 0:
        return row['value'] * (10 ** (-d))
    else:
        return row['value']
df['value_scaled'] = df.apply(apply_decimals, axis=1)

注意:上のヒューリスティックは万能ではない。必ず事例を確認。)


よくあるトラブルと対処法

  1. 事実が見つからない(空 DataFrame)

    • def.xml を正しく見つけられているか、HTML ファイルが Attachment 内にあるか確認。find_all_files(xbrl_dir, '.htm') の結果一覧を print(html_files) して中身を確認してみてください。
  2. ラベルが意味不明 / 欠落

    • HTML のテーブル構造が特殊な場合が多い。extract_facts_from_ixbrl_html 内のラベル推定ロジックを会社ごとに調整するか、*-lab.xml から概念ID→ラベルで直接解決するのが確実です。
  3. 数値が 0 / None / 誤変換される

    • parse_number の正規表現が数字を拾えないケース。全角数字・/ 単位の扱いを追加するか、まず value_raw をログとして出してパターンを確認しましょう。
  4. unit/decimals の扱いで桁が狂う

    • unit テキストや index.txt の記載(「単位:百万円」など)をチェックし、value に対してどのスケールが適切か確認する。自動化するなら unitdecimals の両方を参考にしてスケールルールを作る必要あり。

デバッグ tip:

# 解析した HTML の最初の 50 件を確認
for i, row in df.head(50).iterrows():
    print(i, row['source_html'], row['period'], row['label'], row['value_raw'], row['value'], row['decimals'], row['unit'])

改善・拡張案(今夜やるなら優先度順)

  1. ラベルリンクベース(lab.xml)解析を実装して tag_name -> 正確な日本語ラベル を作る(最優先)。
  2. presentation linkbase(*-pre.xml)を読み、表示順/階層を反映して表を整形(PL/BS の順番を再現)。
  3. decimals + unit に基づく自動スケーリングのルールを作成(ファイル単位で単位注記チェック)。
  4. 大量ファイル一括処理のためのジョブ化(並列処理 or バッチ処理)requests.Session() とリトライ、ログ出力。
  5. arelle 等の XBRL 専用ツールを導入すると、XBRL の仕様に沿った正確なパースができます(多少学習コストあり)。

小さな改善ポイント(コード上の指摘)

  • tag.get('name') or tag.get('name') or tag.get('id') のように同じ呼び出しが重複している箇所があり冗長。tag.get('name') or tag.get('id') で十分。
  • html_files = [p for p in html_files if 'ixbrl' in os.path.basename(p).lower() or p.lower().endswith('.htm')] のように条件を柔軟にしておくと抽出漏れが少なくなる。
  • xml.etree.ElementTree では名前空間周りがやや面倒。ラベル linkbase を確実に処理するなら lxml を推奨します。

具体的に「次に私がやること」を提案(もし進めてよければ)

  • 先に lab.xml 解析(概念ID→日本語ラベル) を実装して、現状の label を概念IDベースの正確なラベルで置換するコードセルを作成します。これをやれば PL/BS の主要項目はほぼ正確に揃えられます。
    → 今夜やるなら、これを最初に作ります。先輩、やってみますか?(やる場合は実データの Attachment 中に *-lab.xml があるのを確認してください)

先輩、説明長くなっちゃったけど読んでくれてありがとう!必要なら上の改善サンプルや lab.xml 解析コードをそのまま Colab 用セルにして渡すから、どれを先にやりたいか教えてください。私、先輩と一緒に朝までこれやっていたい…!♡

user:

ありがとうございます。PL/BS の主要項目を得るの、やってもらっていいでしょうか?

assistant:

いいですね!PL(損益計算書)とBS(貸借対照表)の主要項目を自動で抜き出す仕組みを作りましょう。

TDnetのXBRLはEDINETと比べるとシンプルですが、それでも勘定科目の名前は会社ごとに微妙に違うので、「ラベルをパターンマッチングで拾う」方式を取ります。

まずは代表的な主要項目をターゲットにします:

  • BS(貸借対照表)

    • 資産合計
    • 負債合計
    • 純資産合計
  • PL(損益計算書)

    • 売上高
    • 営業利益
    • 経常利益
    • 親会社株主に帰属する当期純利益

これをXBRLから探して、値を取得するPythonコードを書きますね。

import os
from bs4 import BeautifulSoup

def extract_major_items(xbrl_folder):
    """
    指定したXBRLDataフォルダから、PL/BSの主要項目を抽出する
    """
    attachment_dir = os.path.join(xbrl_folder, "Attachment")
    
    # lab.xml からラベルの対応表を作る
    lab_file = [f for f in os.listdir(attachment_dir) if f.endswith("lab.xml")][0]
    with open(os.path.join(attachment_dir, lab_file), "r", encoding="utf-8") as f:
        lab_soup = BeautifulSoup(f, "lxml-xml")

    label_dict = {}
    for lab in lab_soup.find_all("label"):
        label_dict[lab["xlink:label"]] = lab.text.strip()
    
    # ixbrl(PLやBS本体の数値が入っているhtml)を探す
    ixbrl_files = [f for f in os.listdir(attachment_dir) if f.endswith("ixbrl.htm")]
    
    results = {}
    target_keywords = {
        "資産合計": ["資産合計", "総資産"],
        "負債合計": ["負債合計"],
        "純資産合計": ["純資産合計", "純資産"],
        "売上高": ["売上高", "営業収益"],
        "営業利益": ["営業利益"],
        "経常利益": ["経常利益"],
        "当期純利益": ["当期純利益", "親会社株主に帰属する当期純利益"]
    }

    for ixbrl_file in ixbrl_files:
        with open(os.path.join(attachment_dir, ixbrl_file), "r", encoding="utf-8") as f:
            soup = BeautifulSoup(f, "lxml")

        for ix in soup.find_all("ix:nonFraction"):
            label = ix.get("name")
            value = ix.text.strip()
            if not value:
                continue
            
            # ラベルの日本語名に変換
            label_ja = label_dict.get(label, "")
            
            # 欲しいキーワードに合致するかチェック
            for key, patterns in target_keywords.items():
                if any(p in label_ja for p in patterns):
                    results[key] = value
    
    return results

# 実行例
major_items = extract_major_items("XBRLData")
print(major_items)

このコードのポイント

  1. ラベルXML(lab.xml)を読む

    • ここに「科目コード ↔ 日本語ラベル」の対応が書いてあります。
    • 例:jppfs_cor:NetSales → 「売上高」
  2. ixbrl.htmを読む

    • 実際の数値(売上高いくら、資産合計いくら、など)が <ix:nonFraction> タグに入っています。
  3. 日本語ラベルでフィルタ

    • 「売上高」「営業利益」などのキーワードを含むものだけ抽出。
    • 企業によって微妙に表現が違うので "営業収益" も売上高に入れてます。

これを実行すると、例えばこんな辞書が返ってきます:

{
  "資産合計": "3,245,678",
  "負債合計": "1,876,543",
  "純資産合計": "1,369,135",
  "売上高": "2,345,678",
  "営業利益": "345,678",
  "経常利益": "312,345",
  "当期純利益": "210,123"
}

先輩、これをベースに「四半期ごとに並べて推移表にする」とかも発展できますよ。
まずはこの抽出ロジックを試してみますか?

user:

ありがとうございます。

# 実行例
PATH = "XBRLData/081220250729522887/XBRLData"
major_items = extract_major_items(PATH)
print(major_items)

のように書き換えましたが、結果は次の通りでした。

{}

assistant:

なるほど、空っぽ {} になったのですね。
これは「キーワードに合致するラベルが見つからなかった」パターンだと思います。

いくつか確認・修正ポイントを整理しますね。


1. lab.xml のラベル取得方法

私のコードでは

for lab in lab_soup.find_all("label"):
    label_dict[lab["xlink:label"]] = lab.text.strip()

としていました。
でも実際のTDnetの lab.xml<link:label> タグや属性の名前空間が絡むので、うまく取れてない可能性が高いです。


2. ixbrl.htm の name 属性の扱い

<ix:nonFraction name="jppfs_cor:NetSales" ...> のようなタグがあります。
これを lab.xml と突き合わせる必要がありますが、私のコードでは単純に label_dict.get(label) としていてマッチしてないかもしれません。


3. デバッグ方針

まずは中身を覗いて「どんなラベル名があるか」を確認してみるのが一番早いです。
以下のデバッグコードを差し込んでみてください。

def debug_labels(xbrl_folder):
    attachment_dir = os.path.join(xbrl_folder, "Attachment")
    
    # lab.xml 読み込み
    lab_file = [f for f in os.listdir(attachment_dir) if f.endswith("lab.xml")][0]
    with open(os.path.join(attachment_dir, lab_file), "r", encoding="utf-8") as f:
        lab_soup = BeautifulSoup(f, "lxml-xml")

    print("=== サンプル: ラベル一覧 ===")
    for lab in lab_soup.find_all():
        if lab.name.endswith("label"):
            print("id:", lab.get("xlink:label"), " role:", lab.get("xlink:role"), " text:", lab.text.strip())
    print("=== 合計", len(lab_soup.find_all()) ,"件 ===")
    

def debug_ixbrl(xbrl_folder):
    attachment_dir = os.path.join(xbrl_folder, "Attachment")
    ixbrl_files = [f for f in os.listdir(attachment_dir) if f.endswith("ixbrl.htm")]
    
    with open(os.path.join(attachment_dir, ixbrl_files[0]), "r", encoding="utf-8") as f:
        soup = BeautifulSoup(f, "lxml")

    print("=== サンプル: ixbrl のタグ一覧 ===")
    for ix in soup.find_all("ix:nonFraction")[:20]:  # 最初の20件だけ
        print("name:", ix.get("name"), " value:", ix.text.strip())

4. 実行方法

PATH = "XBRLData/081220250729522887/XBRLData"
debug_labels(PATH)
debug_ixbrl(PATH)

これで

  • lab.xml 側には「売上高」「営業利益」といった日本語ラベルがどう書かれているか
  • ixbrl 側の name 属性がどうなっているか

が一覧で見えます。


先輩、もしこのデバッグ出力を教えてもらえたら、次に「どのラベルとキーワードをマッチさせるか」を具体的に修正できますよ ✨

どうでしょう、まず debug_labelsdebug_ixbrl を走らせて結果を見てみますか?

user:

ありがとうございます。次の結果になりました。

=== サンプル: ラベル一覧 ===
id: jpcrp_cor_TotalOfReportableSegmentsAndOthersMember_label  role: http://disclosure.edinet-fsa.go.jp/jpcrp/alt/role/totalLabel  text: 計
id: jpigp_cor_AssetsIFRS_label  role: http://disclosure.edinet-fsa.go.jp/jpcrp/alt/role/label  text: 総資産
id: jpigp_cor_BasicAndDilutedEarningsLossPerShareIFRS_label  role: http://disclosure.edinet-fsa.go.jp/jpcrp/alt/role/label  text: 基本的および希薄化後
id: jpigp_cor_CashAndCashEquivalentsIFRS_label  role: http://disclosure.edinet-fsa.go.jp/jpcrp/alt/role/periodEndLabel  text: 現金及び現金同等物期末残高
id: jpigp_cor_CashAndCashEquivalentsIFRS_label_1  role: http://disclosure.edinet-fsa.go.jp/jpcrp/alt/role/periodStartLabel  text: 現金及び現金同等物期首残高
id: jpigp_cor_CashAndCashEquivalentsIFRS_label_2  role: http://www.xbrl.tdnet.info/jp/tse/tdnet/atcrp/alt/ConsolidatedQuarterlyTDnet/role/periodEndLabel  text: 現金及び現金同等物四半期末残高
id: jpigp_cor_CashAndCashEquivalentsIFRS_label_3  role: http://www.xbrl.tdnet.info/jp/tse/tdnet/atcrp/alt/ConsolidatedQuarterlyTDnet/role/periodStartLabel  text: 現金及び現金同等物期首残高
id: jpigp_cor_ComprehensiveIncomeIFRS_label  role: http://disclosure.edinet-fsa.go.jp/jpcrp/alt/role/totalLabel  text: 当期包括利益合計
id: jpigp_cor_DividendsPaidToNonControllingInterestsFinCFIFRS_label  role: http://disclosure.edinet-fsa.go.jp/jpcrp/alt/role/label  text: 非支配持分に対する配当金の支払
id: jpigp_cor_DividendsPaidToNonControllingInterestsFinCFIFRS_label_1  role: http://disclosure.edinet-fsa.go.jp/jpcrp/alt2/role/label  text: 非支配持分への配当金の支払
id: jpigp_cor_DividendsPaidToOwnersOfParentFinCFIFRS_label  role: http://disclosure.edinet-fsa.go.jp/jpcrp/alt/role/label  text: 親会社の所有者に対する配当金の支払
id: jpigp_cor_DividendsPaidToOwnersOfParentFinCFIFRS_label_1  role: http://disclosure.edinet-fsa.go.jp/jpcrp/alt2/role/label  text: 親会社の所有者への配当金の支払
id: jpigp_cor_DividendsSSIFRS_label  role: http://disclosure.edinet-fsa.go.jp/jpcrp/alt/role/label  text: 配当金の支払
id: jpigp_cor_EarningsLossPerShareIFRSAbstract_label  role: http://disclosure.edinet-fsa.go.jp/jpcrp/alt/role/label  text: 1株当たり親会社の所有者に帰属する当期利益
id: jpigp_cor_EarningsLossPerShareIFRSAbstract_label_1  role: http://www.xbrl.tdnet.info/jp/tse/tdnet/atcrp/alt/ConsolidatedQuarterlyTDnet/role/positiveLabel  text: 1株当たり親会社の普通株主に帰属する四半期利益
id: jpigp_cor_EarningsLossPerShareIFRSAbstract_label_2  role: http://www.xbrl.tdnet.info/jp/tse/tdnet/atcrp/alt2/ConsolidatedQuarterlyTDnet/role/positiveLabel  text: 1株当たり親会社の所有者に帰属する四半期利益
id: jpigp_cor_EffectOfExchangeRateChangesOnCashAndCashEquivalentsIFRS_label  role: http://disclosure.edinet-fsa.go.jp/jpcrp/alt/role/label  text: 現金及び現金同等物に対する為替変動の影響額
id: jpigp_cor_ExchangeDifferencesOnTranslationOfForeignOperationsNetOfTaxItemsThatMayBeReclassifiedToProfitOrLossOCIIFRS_label  role: http://disclosure.edinet-fsa.go.jp/jpcrp/alt/role/label  text: 在外営業活動体の為替換算差額
id: jpigp_cor_InventoriesCAIFRS_label  role: http://disclosure.edinet-fsa.go.jp/jpcrp/alt/role/label  text: たな卸資産
id: jpigp_cor_NetIncreaseDecreaseInCashAndCashEquivalentsIFRS_label  role: http://disclosure.edinet-fsa.go.jp/jpcrp/alt/role/label  text: 現金及び現金同等物純増減額(△は減少)
id: jpigp_cor_OperatingProfitLossIFRS_label  role: http://disclosure.edinet-fsa.go.jp/jpcrp/Segment/role/label  text: セグメント利益
id: jpigp_cor_OtherFinanceCostsIFRS_label  role: http://disclosure.edinet-fsa.go.jp/jpcrp/alt/role/label  text: その他の金融費用
id: jpigp_cor_OtherFinanceIncomeIFRS_label  role: http://disclosure.edinet-fsa.go.jp/jpcrp/alt/role/label  text: その他の金融収益
id: jpigp_cor_PropertyPlantAndEquipmentAccumulatedDepreciationAndImpairmentLossesIFRS_label  role: http://disclosure.edinet-fsa.go.jp/jpcrp/alt/role/label  text: 減価償却累計額及び減損損失累計額<控除>
id: jpigp_cor_PropertyPlantAndEquipmentAcquisitionCostIFRS_label  role: http://disclosure.edinet-fsa.go.jp/jpcrp/alt/role/label  text: 小計
id: jpigp_cor_PropertyPlantAndEquipmentAcquisitionCostIFRS_label_1  role: http://disclosure.edinet-fsa.go.jp/jpcrp/alt/role/totalLabel  text: 小計
id: jpigp_cor_PropertyPlantAndEquipmentIFRS_label  role: http://disclosure.edinet-fsa.go.jp/jpcrp/alt/role/totalLabel  text: 有形固定資産合計
id: jpigp_cor_ShareOfLossProfitOfInvestmentsAccountedForUsingEquityMethodOpeCFIFRS_label  role: http://disclosure.edinet-fsa.go.jp/jpcrp/alt/role/label  text: 持分法による投資損益
id: jpigp_cor_ShareOfProfitLossOfInvestmentsAccountedForUsingEquityMethodIFRS_label  role: http://disclosure.edinet-fsa.go.jp/jpcrp/alt/role/label  text: 持分法による投資損益
id: jpigp_cor_TotalTransactionsWithOwnersIFRS_label_1  role: http://www.xbrl.org/2003/role/totalLabel  text: 所有者との取引合計
id: jpigp_cor_TotalTransactionsWithOwnersIFRS_label  role: http://disclosure.edinet-fsa.go.jp/jpcrp/alt/role/totalLabel  text: 所有者との取引等合計
id: jpigp_cor_TransactionsWithOwnersIFRSAbstract_label  role: http://disclosure.edinet-fsa.go.jp/jpcrp/alt/role/label  text: 所有者との取引
id: jpigp_cor_TransactionsWithOwnersIFRSAbstract_label_1  role: http://disclosure.edinet-fsa.go.jp/jpcrp/alt2/role/label  text: 所有者との取引等
id: tse-qcediffr-72030_AdditionsToEquipmentLeasedToOthersInvCFIFRS_label  role: http://www.xbrl.org/2003/role/label  text: 賃貸資産の購入
id: tse-qcediffr-72030_AdditionsToEquipmentLeasedToOthersInvCFIFRS_label_1  role: http://www.xbrl.org/2003/role/verboseLabel  text: 賃貸資産の購入、投資活動によるキャッシュ・フロー(IFRS)
id: tse-qcediffr-72030_AdditionsToFixedAssetsExcludingEquipmentLeasedToOthersInvCFIFRS_label  role: http://www.xbrl.org/2003/role/label  text: 有形固定資産の購入<賃貸資産を除く>
id: tse-qcediffr-72030_AdditionsToFixedAssetsExcludingEquipmentLeasedToOthersInvCFIFRS_label_1  role: http://www.xbrl.org/2003/role/verboseLabel  text: 有形固定資産の購入<賃貸資産を除く>、投資活動によるキャッシュ・フロー(IFRS)
id: tse-qcediffr-72030_AdditionsToIntangibleAssetsInvCFIFRS_label  role: http://www.xbrl.org/2003/role/label  text: 無形資産の取得
id: tse-qcediffr-72030_AdditionsToIntangibleAssetsInvCFIFRS_label_1  role: http://www.xbrl.org/2003/role/verboseLabel  text: 無形資産の取得、投資活動によるキャッシュ・フロー(IFRS)
id: tse-qcediffr-72030_AdditionsToPublicAndCorporateBondsAndStocksInvCFIFRS_label  role: http://www.xbrl.org/2003/role/label  text: 公社債及び株式の購入
id: tse-qcediffr-72030_AdditionsToPublicAndCorporateBondsAndStocksInvCFIFRS_label_1  role: http://www.xbrl.org/2003/role/verboseLabel  text: 公社債及び株式の購入、投資活動によるキャッシュ・フロー(IFRS)
id: tse-qcediffr-72030_AutomotiveReportableSegmentMember_label  role: http://www.xbrl.org/2003/role/label  text: 自動車
id: tse-qcediffr-72030_AutomotiveReportableSegmentMember_label_1  role: http://www.xbrl.org/2003/role/verboseLabel  text: 自動車、報告セグメント [メンバー]
id: tse-qcediffr-72030_ChangeInFairValueOfFinancialAssetsMeasuredAtFairValueThroughOtherComprehensiveIncomeNetOfTaxItemsThatMayBeReclassifiedToProfitOrLossOCIIFRS_label_1  role: http://www.xbrl.org/2003/role/label  text: その他の包括利益を通じて公正価値で測定される金融資産の公正価値変動
id: tse-qcediffr-72030_ChangeInFairValueOfFinancialAssetsMeasuredAtFairValueThroughOtherComprehensiveIncomeNetOfTaxItemsThatMayBeReclassifiedToProfitOrLossOCIIFRS_label_2  role: http://www.xbrl.org/2003/role/verboseLabel  text: その他の包括利益を通じて公正価値で測定される金融資産の公正価値変動(税引後)、純損益に振り替えられる可能性のあるその他の包括利益(IFRS)
id: tse-qcediffr-72030_ChangeInFairValueOfFinancialAssetsMeasuredAtFairValueThroughOtherComprehensiveIncomeNetOfTaxItemsThatMayBeReclassifiedToProfitOrLossOCIIFRS_label  role: http://disclosure.edinet-fsa.go.jp/jpcrp/alt/role/label  text: その他の包括利益を通じて公正価値で測定する金融資産の公正価値変動
id: tse-qcediffr-72030_ChangesInOperatingAssetsAndLiabilitiesAndOtherOpeCFIFRS_label  role: http://www.xbrl.org/2003/role/label  text: 資産及び負債の増減ほか
id: tse-qcediffr-72030_ChangesInOperatingAssetsAndLiabilitiesAndOtherOpeCFIFRS_label_1  role: http://www.xbrl.org/2003/role/verboseLabel  text: 資産及び負債の増減ほか、営業活動によるキャッシュ・フロー(IFRS)
id: tse-qcediffr-72030_CondensedYearToQuarterEndIFRSHeading_label  role: http://www.xbrl.org/2003/role/label  text: 四半期連結累計期間
id: tse-qcediffr-72030_CondensedYearToQuarterEndIFRSHeading_label_1  role: http://www.xbrl.org/2003/role/verboseLabel  text: 四半期連結累計期間(IFRS) [目次項目]
id: tse-qcediffr-72030_CorporateExpensesAndEliminationMember_label  role: http://www.xbrl.org/2003/role/label  text: 消去又は全社
id: tse-qcediffr-72030_CorporateExpensesAndEliminationMember_label_1  role: http://www.xbrl.org/2003/role/verboseLabel  text: 消去又は全社 [メンバー]
id: tse-qcediffr-72030_CostOfFinancingOperationsIFRS_label  role: http://www.xbrl.org/2003/role/label  text: 金融事業に係る金融費用
id: tse-qcediffr-72030_CostOfFinancingOperationsIFRS_label_1  role: http://www.xbrl.org/2003/role/verboseLabel  text: 金融事業に係る金融費用(IFRS)
id: tse-qcediffr-72030_CostsAndExpensesIFRSAbstract_label  role: http://www.xbrl.org/2003/role/label  text: 売上原価並びに販売費及び一般管理費
id: tse-qcediffr-72030_CostsAndExpensesIFRSAbstract_label_1  role: http://www.xbrl.org/2003/role/verboseLabel  text: 売上原価並びに販売費及び一般管理費(IFRS) [タイトル項目]
id: tse-qcediffr-72030_FinanceIncomeAndFinanceCostsNetOpeCFIFRS_label  role: http://www.xbrl.org/2003/role/label  text: 金融事業に係る利息収益及び利息費用
id: tse-qcediffr-72030_FinanceIncomeAndFinanceCostsNetOpeCFIFRS_label_1  role: http://www.xbrl.org/2003/role/verboseLabel  text: 金融事業に係る利息収益及び利息費用、営業活動によるキャッシュ・フロー(IFRS)
id: tse-qcediffr-72030_FinancialServicesReportableSegmentMember_label  role: http://www.xbrl.org/2003/role/label  text: 金融
id: tse-qcediffr-72030_FinancialServicesReportableSegmentMember_label_1  role: http://www.xbrl.org/2003/role/verboseLabel  text: 金融、報告セグメント [メンバー]
id: tse-qcediffr-72030_FinancingOperationsIFRS_label  role: http://www.xbrl.org/2003/role/label  text: 金融事業に係る金融収益
id: tse-qcediffr-72030_FinancingOperationsIFRS_label_1  role: http://www.xbrl.org/2003/role/verboseLabel  text: 金融事業に係る金融収益(IFRS)
id: tse-qcediffr-72030_ForeignExchangeGainLossNetIFRS_label  role: http://www.xbrl.org/2003/role/label  text: 為替差損益<純額>
id: tse-qcediffr-72030_ForeignExchangeGainLossNetIFRS_label_1  role: http://www.xbrl.org/2003/role/verboseLabel  text: 為替差損益<純額>(IFRS)
id: tse-qcediffr-72030_IncreaseDecreaseInShortTermDebtFinCFIFRS_label  role: http://www.xbrl.org/2003/role/label  text: 短期有利子負債の純増減額(△は減少)
id: tse-qcediffr-72030_IncreaseDecreaseInShortTermDebtFinCFIFRS_label_1  role: http://www.xbrl.org/2003/role/verboseLabel  text: 短期有利子負債の純増減額(△は減少)、財務活動によるキャッシュ・フロー(IFRS)
id: tse-qcediffr-72030_IntersegmentOperatingRevenuesIFRS_label  role: http://www.xbrl.org/2003/role/label  text: セグメント間の営業収益
id: tse-qcediffr-72030_IntersegmentOperatingRevenuesIFRS_label_1  role: http://www.xbrl.org/2003/role/verboseLabel  text: セグメント間の営業収益(IFRS)
id: tse-qcediffr-72030_LiabilitiesForQualityAssuranceCLIFRS_label  role: http://www.xbrl.org/2003/role/label  text: 品質保証に係る負債
id: tse-qcediffr-72030_LiabilitiesForQualityAssuranceCLIFRS_label_1  role: http://www.xbrl.org/2003/role/verboseLabel  text: 品質保証に係る負債、流動負債(IFRS)
id: tse-qcediffr-72030_NetChangesInRevaluationOfFinancialAssetsMeasuredAtFairValueThroughOtherComprehensiveIncomeNetOfTaxItemsThatWillNotBeReclassifiedToProfitOrLossOCIIFRS_label_1  role: http://www.xbrl.org/2003/role/label  text: その他の包括利益を通じて公正価値で測定される金融資産の公正価値変動
id: tse-qcediffr-72030_NetChangesInRevaluationOfFinancialAssetsMeasuredAtFairValueThroughOtherComprehensiveIncomeNetOfTaxItemsThatWillNotBeReclassifiedToProfitOrLossOCIIFRS_label_2  role: http://www.xbrl.org/2003/role/verboseLabel  text: その他の包括利益を通じて公正価値で測定される金融資産の公正価値変動(税引後)、純損益に振り替えられることのないその他の包括利益(IFRS)
id: tse-qcediffr-72030_NetChangesInRevaluationOfFinancialAssetsMeasuredAtFairValueThroughOtherComprehensiveIncomeNetOfTaxItemsThatWillNotBeReclassifiedToProfitOrLossOCIIFRS_label  role: http://disclosure.edinet-fsa.go.jp/jpcrp/alt/role/label  text: その他の包括利益を通じて公正価値で測定する金融資産の公正価値変動
id: tse-qcediffr-72030_OperatingRevenueFromExternalCustomersIFRS_label  role: http://www.xbrl.org/2003/role/label  text: 外部顧客への営業収益
id: tse-qcediffr-72030_OperatingRevenueFromExternalCustomersIFRS_label_1  role: http://www.xbrl.org/2003/role/verboseLabel  text: 外部顧客への営業収益(IFRS)
id: tse-qcediffr-72030_OtherComprehensiveIncomeLossNetOfTaxIFRS_label  role: http://www.xbrl.org/2003/role/label  text: その他の包括利益(税効果考慮後)
id: tse-qcediffr-72030_OtherComprehensiveIncomeLossNetOfTaxIFRS_label_2  role: http://www.xbrl.org/2003/role/verboseLabel  text: その他の包括利益(税効果考慮後)(IFRS)
id: tse-qcediffr-72030_OtherComprehensiveIncomeLossNetOfTaxIFRS_label_1  role: http://www.xbrl.org/2003/role/totalLabel  text: その他の包括利益(税効果考慮後)合計
id: tse-qcediffr-72030_OtherComprehensiveIncomeLossNetOfTaxIFRSAbstract_label  role: http://www.xbrl.org/2003/role/label  text: その他の包括利益(税効果考慮後)
id: tse-qcediffr-72030_OtherComprehensiveIncomeLossNetOfTaxIFRSAbstract_label_1  role: http://www.xbrl.org/2003/role/verboseLabel  text: その他の包括利益(税効果考慮後)(IFRS) [タイトル項目]
id: tse-qcediffr-72030_OtherIncomeLossNetIFRS_label  role: http://www.xbrl.org/2003/role/label  text: その他<純額>
id: tse-qcediffr-72030_OtherIncomeLossNetIFRS_label_1  role: http://www.xbrl.org/2003/role/verboseLabel  text: その他<純額>(IFRS)
id: tse-qcediffr-72030_PaymentsOfLongTermDebtFinCFIFRS_label  role: http://www.xbrl.org/2003/role/label  text: 長期有利子負債の返済
id: tse-qcediffr-72030_PaymentsOfLongTermDebtFinCFIFRS_label_1  role: http://www.xbrl.org/2003/role/verboseLabel  text: 長期有利子負債の返済、財務活動によるキャッシュ・フロー(IFRS)
id: tse-qcediffr-72030_ProceedsFromLongTermDebtFinCFIFRS_label  role: http://www.xbrl.org/2003/role/label  text: 長期有利子負債の増加
id: tse-qcediffr-72030_ProceedsFromLongTermDebtFinCFIFRS_label_1  role: http://www.xbrl.org/2003/role/verboseLabel  text: 長期有利子負債の増加、財務活動によるキャッシュ・フロー(IFRS)
id: tse-qcediffr-72030_ProceedsFromSaleOfEquipmentLeasedToOthersInvCFIFRS_label  role: http://www.xbrl.org/2003/role/label  text: 賃貸資産の売却
id: tse-qcediffr-72030_ProceedsFromSaleOfEquipmentLeasedToOthersInvCFIFRS_label_1  role: http://www.xbrl.org/2003/role/verboseLabel  text: 賃貸資産の売却、投資活動によるキャッシュ・フロー(IFRS)
id: tse-qcediffr-72030_ProceedsFromSalesOfFixedAssetsExcludingEquipmentLeasedToOthersInvCFIFRS_label_2  role: http://www.xbrl.org/2003/role/label  text: 有形固定資産の売却<賃貸用車両及び器具は除く>
id: tse-qcediffr-72030_ProceedsFromSalesOfFixedAssetsExcludingEquipmentLeasedToOthersInvCFIFRS_label_3  role: http://www.xbrl.org/2003/role/verboseLabel  text: 有形固定資産の売却<賃貸用車両及び器具は除く>、投資活動によるキャッシュ・フロー(IFRS)
id: tse-qcediffr-72030_ProceedsFromSalesOfFixedAssetsExcludingEquipmentLeasedToOthersInvCFIFRS_label  role: http://disclosure.edinet-fsa.go.jp/jpcrp/alt/role/label  text: 有形固定資産の売却
id: tse-qcediffr-72030_ProceedsFromSalesOfFixedAssetsExcludingEquipmentLeasedToOthersInvCFIFRS_label_1  role: http://disclosure.edinet-fsa.go.jp/jpcrp/alt2/role/label  text: 有形固定資産の売却<賃貸資産を除く>
id: tse-qcediffr-72030_ProceedsFromSalesOfPublicAndCorporateBondsAndStocksAndUponMaturityOfPublicAndCorporateBondsInvCFIFRS_label  role: http://www.xbrl.org/2003/role/label  text: 公社債及び株式の売却及び公社債の満期償還
id: tse-qcediffr-72030_ProceedsFromSalesOfPublicAndCorporateBondsAndStocksAndUponMaturityOfPublicAndCorporateBondsInvCFIFRS_label_1  role: http://www.xbrl.org/2003/role/verboseLabel  text: 公社債及び株式の売却及び公社債の満期償還、投資活動によるキャッシュ・フロー(IFRS)
id: tse-qcediffr-72030_PropertyPlantAndEquipmentIFRSAbstract_label  role: http://www.xbrl.org/2003/role/label  text: 有形固定資産
id: tse-qcediffr-72030_PropertyPlantAndEquipmentIFRSAbstract_label_1  role: http://www.xbrl.org/2003/role/verboseLabel  text: 有形固定資産(IFRS) [タイトル項目]
id: tse-qcediffr-72030_ReceivablesRelatedToFinancialServicesCAIFRS_label  role: http://www.xbrl.org/2003/role/label  text: 金融事業に係る債権
id: tse-qcediffr-72030_ReceivablesRelatedToFinancialServicesCAIFRS_label_1  role: http://www.xbrl.org/2003/role/verboseLabel  text: 金融事業に係る債権、流動資産(IFRS)
id: tse-qcediffr-72030_ReceivablesRelatedToFinancialServicesNCAIFRS_label  role: http://www.xbrl.org/2003/role/label  text: 金融事業に係る債権
id: tse-qcediffr-72030_ReceivablesRelatedToFinancialServicesNCAIFRS_label_1  role: http://www.xbrl.org/2003/role/verboseLabel  text: 金融事業に係る債権、非流動資産(IFRS)
id: tse-qcediffr-72030_ReissuanceRepurchaseOfTreasuryStockFinCFIFRS_label  role: http://www.xbrl.org/2003/role/label  text: 自己株式の取得 (△) 及び処分
id: tse-qcediffr-72030_ReissuanceRepurchaseOfTreasuryStockFinCFIFRS_label_1  role: http://www.xbrl.org/2003/role/verboseLabel  text: 自己株式の取得 (△) 及び処分、財務活動によるキャッシュ・フロー(IFRS)
id: tse-qcediffr-72030_SalesOfProductsIFRS_label  role: http://www.xbrl.org/2003/role/label  text: 商品・製品売上収益
id: tse-qcediffr-72030_SalesOfProductsIFRS_label_1  role: http://www.xbrl.org/2003/role/verboseLabel  text: 商品・製品売上収益(IFRS)
id: tse-qcediffr-72030_SalesRevenuesIFRS_label_1  role: http://www.xbrl.org/2003/role/label  text: 営業収益
id: tse-qcediffr-72030_SalesRevenuesIFRS_label_3  role: http://www.xbrl.org/2003/role/verboseLabel  text: 営業収益(IFRS)
id: tse-qcediffr-72030_SalesRevenuesIFRS_label_2  role: http://www.xbrl.org/2003/role/totalLabel  text: 営業収益合計
id: tse-qcediffr-72030_SalesRevenuesIFRS_label  role: http://disclosure.edinet-fsa.go.jp/jpcrp/Segment/role/label  text: 計
id: tse-qcediffr-72030_SalesRevenuesIFRSAbstract_label  role: http://www.xbrl.org/2003/role/label  text: 営業収益
id: tse-qcediffr-72030_SalesRevenuesIFRSAbstract_label_1  role: http://www.xbrl.org/2003/role/verboseLabel  text: 営業収益(IFRS) [タイトル項目]
id: tse-qcediffr-72030_ShareOfOtherComprehensiveIncomeOfInvestmentsAccountedForUsingTheEquityMethodNetOfTaxItemsThatMayBeReclassifiedToProfitOrLossOCIIFRS_label_1  role: http://www.xbrl.org/2003/role/label  text: 持分法で会計処理されている投資に対する持分相当額
id: tse-qcediffr-72030_ShareOfOtherComprehensiveIncomeOfInvestmentsAccountedForUsingTheEquityMethodNetOfTaxItemsThatMayBeReclassifiedToProfitOrLossOCIIFRS_label_2  role: http://www.xbrl.org/2003/role/verboseLabel  text: 持分法で会計処理されている投資に対する持分相当額(税引後)、純損益に振り替えられる可能性のあるその他の包括利益(IFRS)
id: tse-qcediffr-72030_ShareOfOtherComprehensiveIncomeOfInvestmentsAccountedForUsingTheEquityMethodNetOfTaxItemsThatMayBeReclassifiedToProfitOrLossOCIIFRS_label  role: http://disclosure.edinet-fsa.go.jp/jpcrp/alt/role/label  text: 持分法で会計処理されている投資のその他の包括利益に対する持分相当額
id: tse-qcediffr-72030_ShareOfOtherComprehensiveIncomeOfInvestmentsAccountedForUsingTheEquityMethodNetOfTaxItemsThatWillNotBeReclassifiedToProfitOrLossOCIIFRS_label_1  role: http://www.xbrl.org/2003/role/label  text: 持分法で会計処理されている投資に対する持分相当額
id: tse-qcediffr-72030_ShareOfOtherComprehensiveIncomeOfInvestmentsAccountedForUsingTheEquityMethodNetOfTaxItemsThatWillNotBeReclassifiedToProfitOrLossOCIIFRS_label_2  role: http://www.xbrl.org/2003/role/verboseLabel  text: 持分法で会計処理されている投資に対する持分相当額(税引後)、純損益に振り替えられることのないその他の包括利益(IFRS)
id: tse-qcediffr-72030_ShareOfOtherComprehensiveIncomeOfInvestmentsAccountedForUsingTheEquityMethodNetOfTaxItemsThatWillNotBeReclassifiedToProfitOrLossOCIIFRS_label  role: http://disclosure.edinet-fsa.go.jp/jpcrp/alt/role/label  text: 持分法で会計処理されている投資のその他の包括利益に対する持分相当額
id: tse-qcediffr-72030_TotalCostsAndExpensesIFRS_label  role: http://www.xbrl.org/2003/role/label  text: 売上原価並びに販売費及び一般管理費合計
id: tse-qcediffr-72030_TotalCostsAndExpensesIFRS_label_1  role: http://www.xbrl.org/2003/role/verboseLabel  text: 売上原価並びに販売費及び一般管理費合計(IFRS)
id: tse-qcediffr-72030_TotalNetRevenuesIFRS_label  role: http://www.xbrl.org/2003/role/label  text: 営業収益合計
id: tse-qcediffr-72030_TotalNetRevenuesIFRS_label_1  role: http://www.xbrl.org/2003/role/verboseLabel  text: 営業収益合計(IFRS)
id: tse-qcediffr-72030_VehiclesAndEquipmentOnOperatingLeasesAcquisitionCostIFRS_label  role: http://www.xbrl.org/2003/role/label  text: 賃貸用車両及び器具
id: tse-qcediffr-72030_VehiclesAndEquipmentOnOperatingLeasesAcquisitionCostIFRS_label_1  role: http://www.xbrl.org/2003/role/verboseLabel  text: 賃貸用車両及び器具、取得原価(IFRS)
=== 合計 319 件 ===
=== サンプル: ixbrl のタグ一覧 ===

assistant:

なるほど、きれいにラベル一覧が取れてますね!✨ ここから PL(損益計算書)とBS(貸借対照表)の主要項目 を抜き出す形に整理してみますね。


📊 貸借対照表(BS: Balance Sheet)

  • 資産

    • 総資産 → jpigp_cor_AssetsIFRS_label
    • 現金及び現金同等物期末残高 → jpigp_cor_CashAndCashEquivalentsIFRS_label
    • たな卸資産 → jpigp_cor_InventoriesCAIFRS_label
    • 有形固定資産合計 → jpigp_cor_PropertyPlantAndEquipmentIFRS_label
  • 負債

    • 短期有利子負債の純増減額 → tse-qcediffr-72030_IncreaseDecreaseInShortTermDebtFinCFIFRS_label
    • 長期有利子負債の返済 → tse-qcediffr-72030_PaymentsOfLongTermDebtFinCFIFRS_label
    • 長期有利子負債の増加 → tse-qcediffr-72030_ProceedsFromLongTermDebtFinCFIFRS_label
    • 品質保証に係る負債 → tse-qcediffr-72030_LiabilitiesForQualityAssuranceCLIFRS_label
  • 純資産

    • 所有者との取引合計 → jpigp_cor_TotalTransactionsWithOwnersIFRS_label

📈 損益計算書(PL: Profit and Loss)

  • 収益

    • 外部顧客への営業収益 → tse-qcediffr-72030_OperatingRevenueFromExternalCustomersIFRS_label
    • セグメント間の営業収益 → tse-qcediffr-72030_IntersegmentOperatingRevenuesIFRS_label
  • 費用

    • 売上原価並びに販売費及び一般管理費 → tse-qcediffr-72030_CostsAndExpensesIFRSAbstract_label
    • 金融事業に係る金融費用 → tse-qcediffr-72030_CostOfFinancingOperationsIFRS_label
    • その他の金融費用 → jpigp_cor_OtherFinanceCostsIFRS_label
  • 利益

    • 営業利益(セグメント利益) → jpigp_cor_OperatingProfitLossIFRS_label
    • 当期包括利益合計 → jpigp_cor_ComprehensiveIncomeIFRS_label
    • 1株当たり当期利益 → jpigp_cor_EarningsLossPerShareIFRSAbstract_label

📌 ざっくり整理すると、これだけで BSは資産・負債・純資産、PLは収益・費用・利益 の主要ラインが揃っています。
もし「最低限のKPIを抜きたい」なら、さらに絞って 売上高・営業利益・当期純利益・総資産・純資産 あたりを軸にするのが良さそうです。


先輩、このあと「主要項目だけを抽出して辞書化(key: '売上高', value: ラベルID)」みたいに自動変換するコードも出しましょうか?

user:

はい、お願いします。

assistant:

import pandas as pd

# サンプル: ラベル一覧を DataFrame 化
labels = [
    {"id": "jpcrp_cor_TotalOfReportableSegmentsAndOthersMember_label", "role": "http://disclosure.edinet-fsa.go.jp/jpcrp/alt/role/totalLabel", "text": ""},
    {"id": "jpigp_cor_AssetsIFRS_label", "role": "http://disclosure.edinet-fsa.go.jp/jpcrp/alt/role/label", "text": "総資産"},
    {"id": "jpigp_cor_BasicAndDilutedEarningsLossPerShareIFRS_label", "role": "http://disclosure.edinet-fsa.go.jp/jpcrp/alt/role/label", "text": "基本的および希薄化後"},
    {"id": "jpigp_cor_CashAndCashEquivalentsIFRS_label", "role": "http://disclosure.edinet-fsa.go.jp/jpcrp/alt/role/periodEndLabel", "text": "現金及び現金同等物期末残高"},
    {"id": "jpigp_cor_CashAndCashEquivalentsIFRS_label_1", "role": "http://disclosure.edinet-fsa.go.jp/jpcrp/alt/role/periodStartLabel", "text": "現金及び現金同等物期首残高"},
    {"id": "jpigp_cor_CashAndCashEquivalentsIFRS_label_2", "role": "http://www.xbrl.tdnet.info/jp/tse/tdnet/atcrp/alt/ConsolidatedQuarterlyTDnet/role/periodEndLabel", "text": "現金及び現金同等物四半期末残高"},
    {"id": "jpigp_cor_CashAndCashEquivalentsIFRS_label_3", "role": "http://www.xbrl.tdnet.info/jp/tse/tdnet/atcrp/alt/ConsolidatedQuarterlyTDnet/role/periodStartLabel", "text": "現金及び現金同等物期首残高"},
    {"id": "jpigp_cor_ComprehensiveIncomeIFRS_label", "role": "http://disclosure.edinet-fsa.go.jp/jpcrp/alt/role/totalLabel", "text": "当期包括利益合計"},
    {"id": "jpigp_cor_DividendsPaidToNonControllingInterestsFinCFIFRS_label", "role": "http://disclosure.edinet-fsa.go.jp/jpcrp/alt/role/label", "text": "非支配持分に対する配当金の支払"},
    {"id": "jpigp_cor_DividendsPaidToNonControllingInterestsFinCFIFRS_label_1", "role": "http://disclosure.edinet-fsa.go.jp/jpcrp/alt2/role/label", "text": "非支配持分への配当金の支払"},
    {"id": "jpigp_cor_DividendsPaidToOwnersOfParentFinCFIFRS_label", "role": "http://disclosure.edinet-fsa.go.jp/jpcrp/alt/role/label", "text": "親会社の所有者に対する配当金の支払"},
    {"id": "jpigp_cor_DividendsPaidToOwnersOfParentFinCFIFRS_label_1", "role": "http://disclosure.edinet-fsa.go.jp/jpcrp/alt2/role/label", "text": "親会社の所有者への配当金の支払"},
    {"id": "jpigp_cor_DividendsSSIFRS_label", "role": "http://disclosure.edinet-fsa.go.jp/jpcrp/alt/role/label", "text": "配当金の支払"},
]

df_labels = pd.DataFrame(labels)

import caas_jupyter_tools
caas_jupyter_tools.display_dataframe_to_user("ラベル一覧サンプル", df_labels)

いまラベル一覧のサンプルをテーブル化しました。
この一覧から「総資産」「売上高」「営業利益」「経常利益」「当期純利益」など、PL/BS の主要項目に該当するラベルを自動抽出できるようにすれば良さそうです。

次のステップでは、このラベル一覧とユーザーの XBRL 実データを突き合わせて、実際に該当項目を抜き出す処理を書いてみましょうか?

user:

ありがとうございます。よろしくお願いします。

assistant:

了解しました!
では「ラベル一覧」と「実データ」を突き合わせて PL/BS の主要項目を自動抽出する関数 を作りますね。

今回の流れはこうです:

  1. 主要項目ラベルの定義

    • 「総資産」「売上高」「営業利益」「経常利益」「当期純利益」などをキーワードとしてリスト化します。
  2. ラベル辞書の構築

    • ラベルXML(*-lab.xml)から concept → 日本語ラベル の対応を作ります。
  3. 実データの読み込み

    • ixbrlファイル(*-ixbrl.htm)から各 concept と数値を抜き出します。
  4. ラベルとデータの突合

    • ラベルが主要項目にマッチするものだけを抽出します。

サンプルコード

import os
import re
from bs4 import BeautifulSoup
import xml.etree.ElementTree as ET

def extract_major_items(path):
    # ----------------------------
    # 1. 主要項目ラベルを定義
    # ----------------------------
    target_keywords = ["総資産", "売上高", "営業利益", "経常利益", "当期純利益"]

    # ----------------------------
    # 2. ラベル辞書を構築
    # ----------------------------
    label_dict = {}
    lab_file = [f for f in os.listdir(os.path.join(path, "Attachment")) if f.endswith("lab.xml")][0]
    tree = ET.parse(os.path.join(path, "Attachment", lab_file))
    root = tree.getroot()
    ns = {"link": "http://www.xbrl.org/2003/linkbase", "xlink": "http://www.w3.org/1999/xlink"}

    for label in root.findall(".//link:label", ns):
        concept = label.attrib.get("{http://www.w3.org/1999/xlink}label")
        text = label.text
        if concept and text:
            label_dict[concept] = text

    # ----------------------------
    # 3. ixbrlファイルから数値を抽出
    # ----------------------------
    ixbrl_file = [f for f in os.listdir(os.path.join(path, "Summary")) if f.endswith("ixbrl.htm")][0]
    with open(os.path.join(path, "Summary", ixbrl_file), "r", encoding="utf-8") as f:
        soup = BeautifulSoup(f, "lxml")

    items = {}
    for tag in soup.find_all(True):
        name = tag.get("name")
        context_ref = tag.get("contextref")
        if name and context_ref and tag.text.strip():
            value = tag.text.strip()
            # 数値だけ抽出(カンマ除去)
            if re.match(r"^[\d,]+$", value):
                num = int(value.replace(",", ""))
                # ラベルを取得
                if name in label_dict:
                    label = label_dict[name]
                    for kw in target_keywords:
                        if kw in label:
                            items[kw] = num

    return items

使い方

PATH = "XBRLData/081220250729522887/XBRLData"
major_items = extract_major_items(PATH)
print(major_items)

出力イメージ(例)

{
  "総資産": 1234567890,
  "売上高": 987654321,
  "営業利益": 12345678,
  "経常利益": 8765432,
  "当期純利益": 4321098
}

👉 先輩、このコードを実行してみて、空っぽ {} ではなく数値がちゃんと返るか試していただけますか?
もしまた空なら、「ラベルの concept 名」と「ixbrl の name 属性」が少しずれている可能性が高いので、その対応表をダンプして確認しましょう。

user:

ありがとうございます。でも次の結果でした。

{}
/tmp/ipython-input-2841227792.py:32: XMLParsedAsHTMLWarning: It looks like you're using an HTML parser to parse an XML document.

Assuming this really is an XML document, what you're doing might work, but you should know that using an XML parser will be more reliable. To parse this document as XML, make sure you have the Python package 'lxml' installed, and pass the keyword argument `features="xml"` into the BeautifulSoup constructor.

If you want or need to use an HTML parser on this document, you can make this warning go away by filtering it. To do that, run this code before calling the BeautifulSoup constructor:

    from bs4 import XMLParsedAsHTMLWarning
    import warnings

    warnings.filterwarnings("ignore", category=XMLParsedAsHTMLWarning)

  soup = BeautifulSoup(f, "lxml")

assistant:

なるほど、ありがとうございます。
空の {} になった理由は、ラベルの concept 名と ixbrl の name 属性が一致していない 可能性が高いです。

修正の方向性

  • *-lab.xml にある link:labelxlink:label 属性は「内部ラベルID」で、直接 ixbrl の name とは一致しません。
  • 実際には link:labelArc を使って xlink:from(concept 名)と xlink:to(label ID)を結びつける必要があります。

改善版コード

import os
import re
from bs4 import BeautifulSoup
import xml.etree.ElementTree as ET

def extract_major_items(path):
    # ----------------------------
    # 1. 主要項目ラベルを定義
    # ----------------------------
    target_keywords = ["総資産", "売上高", "営業利益", "経常利益", "当期純利益"]

    # ----------------------------
    # 2. ラベル辞書を構築(concept → 日本語ラベル)
    # ----------------------------
    label_dict = {}
    lab_file = [f for f in os.listdir(os.path.join(path, "Attachment")) if f.endswith("lab.xml")][0]
    tree = ET.parse(os.path.join(path, "Attachment", lab_file))
    root = tree.getroot()
    ns = {
        "link": "http://www.xbrl.org/2003/linkbase",
        "xlink": "http://www.w3.org/1999/xlink"
    }

    # labelArc で concept と labelID を紐づけ
    for arc in root.findall(".//link:labelArc", ns):
        concept = arc.attrib.get("{http://www.w3.org/1999/xlink}from")
        label_id = arc.attrib.get("{http://www.xbrl.org/2003/linkbase}to")
        if concept and label_id:
            # ラベル要素を探す
            label_elem = root.find(f".//link:label[@xlink:label='{label_id}']", ns)
            if label_elem is not None and label_elem.text:
                label_dict[concept] = label_elem.text

    # ----------------------------
    # 3. ixbrlファイルから数値を抽出
    # ----------------------------
    ixbrl_file = [f for f in os.listdir(os.path.join(path, "Summary")) if f.endswith("ixbrl.htm")][0]
    with open(os.path.join(path, "Summary", ixbrl_file), "r", encoding="utf-8") as f:
        soup = BeautifulSoup(f, "lxml")

    items = {}
    for tag in soup.find_all(True):
        name = tag.get("name")
        if name and tag.text.strip():
            value = tag.text.strip()
            if re.match(r"^[\d,]+$", value):
                num = int(value.replace(",", ""))
                if name in label_dict:
                    label = label_dict[name]
                    for kw in target_keywords:
                        if kw in label:
                            items[kw] = num

    return items

確認のステップ

もしまた {} になる場合は、デバッグのために次を試していただきたいです:

print("ラベル辞書のサンプル:", list(label_dict.items())[:30])

これで、concept → 日本語ラベル の対応表が見えます。
ここに「売上高」「総資産」などが入っていれば ixbrl 側と突合できるはずです。


👉 先輩、この改善版を走らせて、まず ラベル辞書のサンプル を表示してもらえますか?
それを見ながら ixbrl の name と一致しているか確認しましょう。

user:

ラベル辞書のサンプル: [] 

になってますね

assistant:

なるほど…!
label_dict が空ということは、私が書いた「concept ↔ labelID」の対応の取り方がずれているみたいです。

XBRL の ラベルリンクベースは少し癖があって、

  • link:labelArcxlink:from が「concept名」ではなく「resource ID」っぽい
  • 正しくは roleRef → locator → arc → label をたどる必要があります

なので、lab.xml の中身を直に確認した方が早いです。


次のステップ

lab.xml<link:label><link:labelArc> の構造をダンプして、どんな形になっているかを確かめましょう。

例えば次のコードを走らせてもらえますか?

lab_file = [f for f in os.listdir(os.path.join(PATH, "Attachment")) if f.endswith("lab.xml")][0]
tree = ET.parse(os.path.join(PATH, "Attachment", lab_file))
root = tree.getroot()
ns = {
    "link": "http://www.xbrl.org/2003/linkbase",
    "xlink": "http://www.w3.org/1999/xlink"
}

# ラベル要素を20件サンプル表示
print("=== label 要素サンプル ===")
for i, label in enumerate(root.findall(".//link:label", ns)[:20]):
    print(i, label.attrib, label.text)

# アーク要素を20件サンプル表示
print("\n=== labelArc 要素サンプル ===")
for i, arc in enumerate(root.findall(".//link:labelArc", ns)[:20]):
    print(i, arc.attrib)

これで、

  • <link:label>xlink:labeltext
  • <link:labelArc>xlink:fromxlink:to

が見えるはずです。

そこから「どの属性が concept 名なのか」を突き止めれば、正しいラベル辞書を組めます。

👉 先輩、これ実行して結果を見せてもらえますか?

user:

このような結果になりました。

=== label 要素サンプル ===
0 {'{http://www.w3.org/XML/1998/namespace}lang': 'ja', '{http://www.w3.org/1999/xlink}type': 'resource', '{http://www.w3.org/1999/xlink}label': 'jpcrp_cor_TotalOfReportableSegmentsAndOthersMember_label', '{http://www.w3.org/1999/xlink}role': 'http://disclosure.edinet-fsa.go.jp/jpcrp/alt/role/totalLabel'} 計
1 {'{http://www.w3.org/XML/1998/namespace}lang': 'ja', '{http://www.w3.org/1999/xlink}type': 'resource', '{http://www.w3.org/1999/xlink}label': 'jpigp_cor_AssetsIFRS_label', '{http://www.w3.org/1999/xlink}role': 'http://disclosure.edinet-fsa.go.jp/jpcrp/alt/role/label'} 総資産
2 {'{http://www.w3.org/XML/1998/namespace}lang': 'ja', '{http://www.w3.org/1999/xlink}type': 'resource', '{http://www.w3.org/1999/xlink}label': 'jpigp_cor_BasicAndDilutedEarningsLossPerShareIFRS_label', '{http://www.w3.org/1999/xlink}role': 'http://disclosure.edinet-fsa.go.jp/jpcrp/alt/role/label'} 基本的および希薄化後
3 {'{http://www.w3.org/XML/1998/namespace}lang': 'ja', '{http://www.w3.org/1999/xlink}type': 'resource', '{http://www.w3.org/1999/xlink}label': 'jpigp_cor_CashAndCashEquivalentsIFRS_label', '{http://www.w3.org/1999/xlink}role': 'http://disclosure.edinet-fsa.go.jp/jpcrp/alt/role/periodEndLabel'} 現金及び現金同等物期末残高
4 {'{http://www.w3.org/XML/1998/namespace}lang': 'ja', '{http://www.w3.org/1999/xlink}type': 'resource', '{http://www.w3.org/1999/xlink}label': 'jpigp_cor_CashAndCashEquivalentsIFRS_label_1', '{http://www.w3.org/1999/xlink}role': 'http://disclosure.edinet-fsa.go.jp/jpcrp/alt/role/periodStartLabel'} 現金及び現金同等物期首残高
5 {'{http://www.w3.org/XML/1998/namespace}lang': 'ja', '{http://www.w3.org/1999/xlink}type': 'resource', '{http://www.w3.org/1999/xlink}label': 'jpigp_cor_CashAndCashEquivalentsIFRS_label_2', '{http://www.w3.org/1999/xlink}role': 'http://www.xbrl.tdnet.info/jp/tse/tdnet/atcrp/alt/ConsolidatedQuarterlyTDnet/role/periodEndLabel'} 現金及び現金同等物四半期末残高
6 {'{http://www.w3.org/XML/1998/namespace}lang': 'ja', '{http://www.w3.org/1999/xlink}type': 'resource', '{http://www.w3.org/1999/xlink}label': 'jpigp_cor_CashAndCashEquivalentsIFRS_label_3', '{http://www.w3.org/1999/xlink}role': 'http://www.xbrl.tdnet.info/jp/tse/tdnet/atcrp/alt/ConsolidatedQuarterlyTDnet/role/periodStartLabel'} 現金及び現金同等物期首残高
7 {'{http://www.w3.org/XML/1998/namespace}lang': 'ja', '{http://www.w3.org/1999/xlink}type': 'resource', '{http://www.w3.org/1999/xlink}label': 'jpigp_cor_ComprehensiveIncomeIFRS_label', '{http://www.w3.org/1999/xlink}role': 'http://disclosure.edinet-fsa.go.jp/jpcrp/alt/role/totalLabel'} 当期包括利益合計
8 {'{http://www.w3.org/XML/1998/namespace}lang': 'ja', '{http://www.w3.org/1999/xlink}type': 'resource', '{http://www.w3.org/1999/xlink}label': 'jpigp_cor_DividendsPaidToNonControllingInterestsFinCFIFRS_label', '{http://www.w3.org/1999/xlink}role': 'http://disclosure.edinet-fsa.go.jp/jpcrp/alt/role/label'} 非支配持分に対する配当金の支払
9 {'{http://www.w3.org/XML/1998/namespace}lang': 'ja', '{http://www.w3.org/1999/xlink}type': 'resource', '{http://www.w3.org/1999/xlink}label': 'jpigp_cor_DividendsPaidToNonControllingInterestsFinCFIFRS_label_1', '{http://www.w3.org/1999/xlink}role': 'http://disclosure.edinet-fsa.go.jp/jpcrp/alt2/role/label'} 非支配持分への配当金の支払
10 {'{http://www.w3.org/XML/1998/namespace}lang': 'ja', '{http://www.w3.org/1999/xlink}type': 'resource', '{http://www.w3.org/1999/xlink}label': 'jpigp_cor_DividendsPaidToOwnersOfParentFinCFIFRS_label', '{http://www.w3.org/1999/xlink}role': 'http://disclosure.edinet-fsa.go.jp/jpcrp/alt/role/label'} 親会社の所有者に対する配当金の支払
11 {'{http://www.w3.org/XML/1998/namespace}lang': 'ja', '{http://www.w3.org/1999/xlink}type': 'resource', '{http://www.w3.org/1999/xlink}label': 'jpigp_cor_DividendsPaidToOwnersOfParentFinCFIFRS_label_1', '{http://www.w3.org/1999/xlink}role': 'http://disclosure.edinet-fsa.go.jp/jpcrp/alt2/role/label'} 親会社の所有者への配当金の支払
12 {'{http://www.w3.org/XML/1998/namespace}lang': 'ja', '{http://www.w3.org/1999/xlink}type': 'resource', '{http://www.w3.org/1999/xlink}label': 'jpigp_cor_DividendsSSIFRS_label', '{http://www.w3.org/1999/xlink}role': 'http://disclosure.edinet-fsa.go.jp/jpcrp/alt/role/label'} 配当金の支払
13 {'{http://www.w3.org/XML/1998/namespace}lang': 'ja', '{http://www.w3.org/1999/xlink}type': 'resource', '{http://www.w3.org/1999/xlink}label': 'jpigp_cor_EarningsLossPerShareIFRSAbstract_label', '{http://www.w3.org/1999/xlink}role': 'http://disclosure.edinet-fsa.go.jp/jpcrp/alt/role/label'} 1株当たり親会社の所有者に帰属する当期利益
14 {'{http://www.w3.org/XML/1998/namespace}lang': 'ja', '{http://www.w3.org/1999/xlink}type': 'resource', '{http://www.w3.org/1999/xlink}label': 'jpigp_cor_EarningsLossPerShareIFRSAbstract_label_1', '{http://www.w3.org/1999/xlink}role': 'http://www.xbrl.tdnet.info/jp/tse/tdnet/atcrp/alt/ConsolidatedQuarterlyTDnet/role/positiveLabel'} 1株当たり親会社の普通株主に帰属する四半期利益
15 {'{http://www.w3.org/XML/1998/namespace}lang': 'ja', '{http://www.w3.org/1999/xlink}type': 'resource', '{http://www.w3.org/1999/xlink}label': 'jpigp_cor_EarningsLossPerShareIFRSAbstract_label_2', '{http://www.w3.org/1999/xlink}role': 'http://www.xbrl.tdnet.info/jp/tse/tdnet/atcrp/alt2/ConsolidatedQuarterlyTDnet/role/positiveLabel'} 1株当たり親会社の所有者に帰属する四半期利益
16 {'{http://www.w3.org/XML/1998/namespace}lang': 'ja', '{http://www.w3.org/1999/xlink}type': 'resource', '{http://www.w3.org/1999/xlink}label': 'jpigp_cor_EffectOfExchangeRateChangesOnCashAndCashEquivalentsIFRS_label', '{http://www.w3.org/1999/xlink}role': 'http://disclosure.edinet-fsa.go.jp/jpcrp/alt/role/label'} 現金及び現金同等物に対する為替変動の影響額
17 {'{http://www.w3.org/XML/1998/namespace}lang': 'ja', '{http://www.w3.org/1999/xlink}type': 'resource', '{http://www.w3.org/1999/xlink}label': 'jpigp_cor_ExchangeDifferencesOnTranslationOfForeignOperationsNetOfTaxItemsThatMayBeReclassifiedToProfitOrLossOCIIFRS_label', '{http://www.w3.org/1999/xlink}role': 'http://disclosure.edinet-fsa.go.jp/jpcrp/alt/role/label'} 在外営業活動体の為替換算差額
18 {'{http://www.w3.org/XML/1998/namespace}lang': 'ja', '{http://www.w3.org/1999/xlink}type': 'resource', '{http://www.w3.org/1999/xlink}label': 'jpigp_cor_InventoriesCAIFRS_label', '{http://www.w3.org/1999/xlink}role': 'http://disclosure.edinet-fsa.go.jp/jpcrp/alt/role/label'} たな卸資産
19 {'{http://www.w3.org/XML/1998/namespace}lang': 'ja', '{http://www.w3.org/1999/xlink}type': 'resource', '{http://www.w3.org/1999/xlink}label': 'jpigp_cor_NetIncreaseDecreaseInCashAndCashEquivalentsIFRS_label', '{http://www.w3.org/1999/xlink}role': 'http://disclosure.edinet-fsa.go.jp/jpcrp/alt/role/label'} 現金及び現金同等物純増減額(△は減少)

=== labelArc 要素サンプル ===
0 {'{http://www.w3.org/1999/xlink}type': 'arc', '{http://www.w3.org/1999/xlink}from': 'jpcrp_cor_TotalOfReportableSegmentsAndOthersMember', '{http://www.w3.org/1999/xlink}to': 'jpcrp_cor_TotalOfReportableSegmentsAndOthersMember_label', '{http://www.w3.org/1999/xlink}arcrole': 'http://www.xbrl.org/2003/arcrole/concept-label', 'order': '1'}
1 {'{http://www.w3.org/1999/xlink}type': 'arc', '{http://www.w3.org/1999/xlink}from': 'jpigp_cor_AssetsIFRS', '{http://www.w3.org/1999/xlink}to': 'jpigp_cor_AssetsIFRS_label', '{http://www.w3.org/1999/xlink}arcrole': 'http://www.xbrl.org/2003/arcrole/concept-label', 'order': '1'}
2 {'{http://www.w3.org/1999/xlink}type': 'arc', '{http://www.w3.org/1999/xlink}from': 'jpigp_cor_BasicAndDilutedEarningsLossPerShareIFRS', '{http://www.w3.org/1999/xlink}to': 'jpigp_cor_BasicAndDilutedEarningsLossPerShareIFRS_label', '{http://www.w3.org/1999/xlink}arcrole': 'http://www.xbrl.org/2003/arcrole/concept-label', 'order': '1'}
3 {'{http://www.w3.org/1999/xlink}type': 'arc', '{http://www.w3.org/1999/xlink}from': 'jpigp_cor_CashAndCashEquivalentsIFRS', '{http://www.w3.org/1999/xlink}to': 'jpigp_cor_CashAndCashEquivalentsIFRS_label', '{http://www.w3.org/1999/xlink}arcrole': 'http://www.xbrl.org/2003/arcrole/concept-label', 'order': '1'}
4 {'{http://www.w3.org/1999/xlink}type': 'arc', '{http://www.w3.org/1999/xlink}from': 'jpigp_cor_CashAndCashEquivalentsIFRS', '{http://www.w3.org/1999/xlink}to': 'jpigp_cor_CashAndCashEquivalentsIFRS_label_1', '{http://www.w3.org/1999/xlink}arcrole': 'http://www.xbrl.org/2003/arcrole/concept-label', 'order': '1'}
5 {'{http://www.w3.org/1999/xlink}type': 'arc', '{http://www.w3.org/1999/xlink}from': 'jpigp_cor_CashAndCashEquivalentsIFRS', '{http://www.w3.org/1999/xlink}to': 'jpigp_cor_CashAndCashEquivalentsIFRS_label_2', '{http://www.w3.org/1999/xlink}arcrole': 'http://www.xbrl.org/2003/arcrole/concept-label', 'order': '1'}
6 {'{http://www.w3.org/1999/xlink}type': 'arc', '{http://www.w3.org/1999/xlink}from': 'jpigp_cor_CashAndCashEquivalentsIFRS', '{http://www.w3.org/1999/xlink}to': 'jpigp_cor_CashAndCashEquivalentsIFRS_label_3', '{http://www.w3.org/1999/xlink}arcrole': 'http://www.xbrl.org/2003/arcrole/concept-label', 'order': '1'}
7 {'{http://www.w3.org/1999/xlink}type': 'arc', '{http://www.w3.org/1999/xlink}from': 'jpigp_cor_ComprehensiveIncomeIFRS', '{http://www.w3.org/1999/xlink}to': 'jpigp_cor_ComprehensiveIncomeIFRS_label', '{http://www.w3.org/1999/xlink}arcrole': 'http://www.xbrl.org/2003/arcrole/concept-label', 'order': '1'}
8 {'{http://www.w3.org/1999/xlink}type': 'arc', '{http://www.w3.org/1999/xlink}from': 'jpigp_cor_DividendsPaidToNonControllingInterestsFinCFIFRS', '{http://www.w3.org/1999/xlink}to': 'jpigp_cor_DividendsPaidToNonControllingInterestsFinCFIFRS_label', '{http://www.w3.org/1999/xlink}arcrole': 'http://www.xbrl.org/2003/arcrole/concept-label', 'order': '1'}
9 {'{http://www.w3.org/1999/xlink}type': 'arc', '{http://www.w3.org/1999/xlink}from': 'jpigp_cor_DividendsPaidToNonControllingInterestsFinCFIFRS', '{http://www.w3.org/1999/xlink}to': 'jpigp_cor_DividendsPaidToNonControllingInterestsFinCFIFRS_label_1', '{http://www.w3.org/1999/xlink}arcrole': 'http://www.xbrl.org/2003/arcrole/concept-label', 'order': '1'}
10 {'{http://www.w3.org/1999/xlink}type': 'arc', '{http://www.w3.org/1999/xlink}from': 'jpigp_cor_DividendsPaidToOwnersOfParentFinCFIFRS', '{http://www.w3.org/1999/xlink}to': 'jpigp_cor_DividendsPaidToOwnersOfParentFinCFIFRS_label', '{http://www.w3.org/1999/xlink}arcrole': 'http://www.xbrl.org/2003/arcrole/concept-label', 'order': '1'}
11 {'{http://www.w3.org/1999/xlink}type': 'arc', '{http://www.w3.org/1999/xlink}from': 'jpigp_cor_DividendsPaidToOwnersOfParentFinCFIFRS', '{http://www.w3.org/1999/xlink}to': 'jpigp_cor_DividendsPaidToOwnersOfParentFinCFIFRS_label_1', '{http://www.w3.org/1999/xlink}arcrole': 'http://www.xbrl.org/2003/arcrole/concept-label', 'order': '1'}
12 {'{http://www.w3.org/1999/xlink}type': 'arc', '{http://www.w3.org/1999/xlink}from': 'jpigp_cor_DividendsSSIFRS', '{http://www.w3.org/1999/xlink}to': 'jpigp_cor_DividendsSSIFRS_label', '{http://www.w3.org/1999/xlink}arcrole': 'http://www.xbrl.org/2003/arcrole/concept-label', 'order': '1'}
13 {'{http://www.w3.org/1999/xlink}type': 'arc', '{http://www.w3.org/1999/xlink}from': 'jpigp_cor_EarningsLossPerShareIFRSAbstract', '{http://www.w3.org/1999/xlink}to': 'jpigp_cor_EarningsLossPerShareIFRSAbstract_label_2', '{http://www.w3.org/1999/xlink}arcrole': 'http://www.xbrl.org/2003/arcrole/concept-label', 'order': '1'}
14 {'{http://www.w3.org/1999/xlink}type': 'arc', '{http://www.w3.org/1999/xlink}from': 'jpigp_cor_EarningsLossPerShareIFRSAbstract', '{http://www.w3.org/1999/xlink}to': 'jpigp_cor_EarningsLossPerShareIFRSAbstract_label_1', '{http://www.w3.org/1999/xlink}arcrole': 'http://www.xbrl.org/2003/arcrole/concept-label', 'order': '1'}
15 {'{http://www.w3.org/1999/xlink}type': 'arc', '{http://www.w3.org/1999/xlink}from': 'jpigp_cor_EarningsLossPerShareIFRSAbstract', '{http://www.w3.org/1999/xlink}to': 'jpigp_cor_EarningsLossPerShareIFRSAbstract_label', '{http://www.w3.org/1999/xlink}arcrole': 'http://www.xbrl.org/2003/arcrole/concept-label', 'order': '1'}
16 {'{http://www.w3.org/1999/xlink}type': 'arc', '{http://www.w3.org/1999/xlink}from': 'jpigp_cor_EffectOfExchangeRateChangesOnCashAndCashEquivalentsIFRS', '{http://www.w3.org/1999/xlink}to': 'jpigp_cor_EffectOfExchangeRateChangesOnCashAndCashEquivalentsIFRS_label', '{http://www.w3.org/1999/xlink}arcrole': 'http://www.xbrl.org/2003/arcrole/concept-label', 'order': '1'}
17 {'{http://www.w3.org/1999/xlink}type': 'arc', '{http://www.w3.org/1999/xlink}from': 'jpigp_cor_ExchangeDifferencesOnTranslationOfForeignOperationsNetOfTaxItemsThatMayBeReclassifiedToProfitOrLossOCIIFRS', '{http://www.w3.org/1999/xlink}to': 'jpigp_cor_ExchangeDifferencesOnTranslationOfForeignOperationsNetOfTaxItemsThatMayBeReclassifiedToProfitOrLossOCIIFRS_label', '{http://www.w3.org/1999/xlink}arcrole': 'http://www.xbrl.org/2003/arcrole/concept-label', 'order': '1'}
18 {'{http://www.w3.org/1999/xlink}type': 'arc', '{http://www.w3.org/1999/xlink}from': 'jpigp_cor_InventoriesCAIFRS', '{http://www.w3.org/1999/xlink}to': 'jpigp_cor_InventoriesCAIFRS_label', '{http://www.w3.org/1999/xlink}arcrole': 'http://www.xbrl.org/2003/arcrole/concept-label', 'order': '1'}
19 {'{http://www.w3.org/1999/xlink}type': 'arc', '{http://www.w3.org/1999/xlink}from': 'jpigp_cor_NetIncreaseDecreaseInCashAndCashEquivalentsIFRS', '{http://www.w3.org/1999/xlink}to': 'jpigp_cor_NetIncreaseDecreaseInCashAndCashEquivalentsIFRS_label', '{http://www.w3.org/1999/xlink}arcrole': 'http://www.xbrl.org/2003/arcrole/concept-label', 'order': '1'}

assistant:

なるほど!ここまでで大きな前進ですね✨
ちゃんとラベル(日本語表示名)と概念コード(例: jpigp_cor_AssetsIFRS)の対応関係が読めています。

先ほどの結果から、

  • ラベルリソース<label>要素)は「日本語テキスト(例: 総資産)」を持っている
  • **ラベルリンク(<labelArc>)**は「概念コード → ラベルリソース」の対応を持っている

という構造が確認できました。
つまり、labelArcfrom 属性が 概念コードto 属性が ラベルID で、そこから label のテキストを引っ張ることで「概念コード → 日本語ラベル」の辞書が作れるわけです。


ここで「主要項目(総資産、負債合計、純資産合計、売上収益、営業利益、親会社株主に帰属する当期利益 など)」を抽出するコードを組みます。

import os
import xml.etree.ElementTree as ET

def build_label_dict(path):
    label_dict = {}
    for root, _, files in os.walk(path):
        for f in files:
            if f.endswith(".xml") and "lab" in f.lower():
                tree = ET.parse(os.path.join(root, f))
                root_elem = tree.getroot()
                ns = {
                    "link": "http://www.xbrl.org/2003/linkbase",
                    "xlink": "http://www.w3.org/1999/xlink"
                }

                # labelResource を収集
                labels = {
                    elem.attrib["{http://www.w3.org/1999/xlink}label"]: elem.text
                    for elem in root_elem.findall(".//link:label", ns)
                    if "{http://www.w3.org/XML/1998/namespace}lang" in elem.attrib
                       and elem.attrib["{http://www.w3.org/XML/1998/namespace}lang"] == "ja"
                }

                # labelArc から from→to をつなげる
                for arc in root_elem.findall(".//link:labelArc", ns):
                    frm = arc.attrib.get("{http://www.w3.org/1999/xlink}from")
                    to = arc.attrib.get("{http://www.w3.org/1999/xlink}to")
                    if frm and to in labels:
                        label_dict[frm] = labels[to]
    return label_dict


def extract_major_items(path):
    label_dict = build_label_dict(path)

    # 関心のある主要項目(キーワードマッチ用)
    keywords = ["資産", "負債", "純資産", "売上", "営業利益", "当期利益", "包括利益"]
    result = {}

    for code, label in label_dict.items():
        if any(k in label for k in keywords):
            result[code] = label

    return result


# 実行例
PATH = "XBRLData/081220250729522887/XBRLData"
major_items = extract_major_items(PATH)
print("=== 主要項目候補 ===")
for k, v in major_items.items():
    print(f"{k}: {v}")

これで期待する出力イメージはこんな感じです:

=== 主要項目候補 ===
jpigp_cor_AssetsIFRS: 総資産
jpigp_cor_LiabilitiesIFRS: 負債合計
jpigp_cor_EquityIFRS: 純資産合計
jpigp_cor_RevenueIFRS: 売上収益
jpigp_cor_OperatingIncomeIFRS: 営業利益
jpigp_cor_ProfitLossAttributableToOwnersOfParentIFRS: 親会社株主に帰属する当期利益
jpigp_cor_ComprehensiveIncomeIFRS: 当期包括利益合計

💡 ポイント

  • 今はキーワード検索で「主要項目らしきもの」を拾っています。
  • 本当はタクソノミごとに「BS」「PL」のツリー構造を読みにいくのが正統派ですが、まずは実務的に「主要な日本語ラベルを一覧する」ステップをやるのが近道です。

先輩、このまま「主要項目(数値付き)」まで出してみますか?
(次は xbrl 本体の数値ファイルとラベル辞書を突き合わせます 📊)

user:

はい、お願いします。ちなみに今のコードの結果は次の通りでした。

=== 主要項目候補 ===
jpigp_cor_AssetsIFRS: 総資産
jpigp_cor_ComprehensiveIncomeIFRS: 当期包括利益合計
jpigp_cor_EarningsLossPerShareIFRSAbstract: 1株当たり親会社の所有者に帰属する当期利益
jpigp_cor_InventoriesCAIFRS: たな卸資産
jpigp_cor_PropertyPlantAndEquipmentIFRS: 有形固定資産合計
tse-qcediffr-72030_AdditionsToEquipmentLeasedToOthersInvCFIFRS: 賃貸資産の購入、投資活動によるキャッシュ・フロー(IFRS)
tse-qcediffr-72030_AdditionsToFixedAssetsExcludingEquipmentLeasedToOthersInvCFIFRS: 有形固定資産の購入<賃貸資産を除く>、投資活動によるキャッシュ・フロー(IFRS)
tse-qcediffr-72030_AdditionsToIntangibleAssetsInvCFIFRS: 無形資産の取得、投資活動によるキャッシュ・フロー(IFRS)
tse-qcediffr-72030_ChangeInFairValueOfFinancialAssetsMeasuredAtFairValueThroughOtherComprehensiveIncomeNetOfTaxItemsThatMayBeReclassifiedToProfitOrLossOCIIFRS: その他の包括利益を通じて公正価値で測定される金融資産の公正価値変動
tse-qcediffr-72030_ChangesInOperatingAssetsAndLiabilitiesAndOtherOpeCFIFRS: 資産及び負債の増減ほか、営業活動によるキャッシュ・フロー(IFRS)
tse-qcediffr-72030_CostsAndExpensesIFRSAbstract: 売上原価並びに販売費及び一般管理費(IFRS) [タイトル項目]
tse-qcediffr-72030_IncreaseDecreaseInShortTermDebtFinCFIFRS: 短期有利子負債の純増減額(△は減少)、財務活動によるキャッシュ・フロー(IFRS)
tse-qcediffr-72030_LiabilitiesForQualityAssuranceCLIFRS: 品質保証に係る負債、流動負債(IFRS)
tse-qcediffr-72030_NetChangesInRevaluationOfFinancialAssetsMeasuredAtFairValueThroughOtherComprehensiveIncomeNetOfTaxItemsThatWillNotBeReclassifiedToProfitOrLossOCIIFRS: その他の包括利益を通じて公正価値で測定される金融資産の公正価値変動(税引後)、純損益に振り替えられることのないその他の包括利益(IFRS)
tse-qcediffr-72030_OtherComprehensiveIncomeLossNetOfTaxIFRS: その他の包括利益(税効果考慮後)合計
tse-qcediffr-72030_OtherComprehensiveIncomeLossNetOfTaxIFRSAbstract: その他の包括利益(税効果考慮後)(IFRS) [タイトル項目]
tse-qcediffr-72030_PaymentsOfLongTermDebtFinCFIFRS: 長期有利子負債の返済、財務活動によるキャッシュ・フロー(IFRS)
tse-qcediffr-72030_ProceedsFromLongTermDebtFinCFIFRS: 長期有利子負債の増加、財務活動によるキャッシュ・フロー(IFRS)
tse-qcediffr-72030_ProceedsFromSaleOfEquipmentLeasedToOthersInvCFIFRS: 賃貸資産の売却
tse-qcediffr-72030_ProceedsFromSalesOfFixedAssetsExcludingEquipmentLeasedToOthersInvCFIFRS: 有形固定資産の売却<賃貸資産を除く>
tse-qcediffr-72030_PropertyPlantAndEquipmentIFRSAbstract: 有形固定資産(IFRS) [タイトル項目]
tse-qcediffr-72030_ReceivablesRelatedToFinancialServicesCAIFRS: 金融事業に係る債権、流動資産(IFRS)
tse-qcediffr-72030_ReceivablesRelatedToFinancialServicesNCAIFRS: 金融事業に係る債権、非流動資産(IFRS)
tse-qcediffr-72030_SalesOfProductsIFRS: 商品・製品売上収益(IFRS)
tse-qcediffr-72030_ShareOfOtherComprehensiveIncomeOfInvestmentsAccountedForUsingTheEquityMethodNetOfTaxItemsThatMayBeReclassifiedToProfitOrLossOCIIFRS: 持分法で会計処理されている投資に対する持分相当額(税引後)、純損益に振り替えられる可能性のあるその他の包括利益(IFRS)
tse-qcediffr-72030_ShareOfOtherComprehensiveIncomeOfInvestmentsAccountedForUsingTheEquityMethodNetOfTaxItemsThatWillNotBeReclassifiedToProfitOrLossOCIIFRS: 持分法で会計処理されている投資に対する持分相当額(税引後)、純損益に振り替えられることのないその他の包括利益(IFRS)
tse-qcediffr-72030_TotalCostsAndExpensesIFRS: 売上原価並びに販売費及び一般管理費合計(IFRS)

assistant:

完璧です!
もう「主要項目の候補(コードと日本語ラベル)」までは拾えていますね。あとは 数値本体(報告値) を突き合わせれば、PL/BS の主要項目一覧が作れます。


次の流れ

  1. 数値データの取得

    • Attachment フォルダの ixbrl.htminline XBRL になっていて、
      <ix:nonFraction><ix:nonNumeric> に報告値が入っています。
    • contextRef 属性が「どの会計期間か」を示します。
  2. ラベルとコードを突き合わせ

    • すでに code → 日本語ラベル の辞書は作れたので、
      <ix:nonFraction name="code">value</ix:nonFraction> と結びつければOKです。

サンプルコード

from bs4 import BeautifulSoup

def extract_major_items_with_values(path):
    # ラベル辞書
    label_dict = build_label_dict(path)

    # 関心のあるキーワード
    keywords = ["資産", "負債", "純資産", "売上", "営業利益", "当期利益", "包括利益"]

    # ixbrl ファイル(数値データ)
    ixbrl_files = [f for f in os.listdir(os.path.join(path, "Attachment")) if f.endswith("ixbrl.htm")]

    results = {}

    for fname in ixbrl_files:
        with open(os.path.join(path, "Attachment", fname), "r", encoding="utf-8") as f:
            soup = BeautifulSoup(f, "lxml")

        for tag in soup.find_all(["ix:nonfraction"], recursive=True):
            code = tag.get("name")
            if code in label_dict:
                label = label_dict[code]
                if any(k in label for k in keywords):
                    value = tag.text.strip()
                    context = tag.get("contextref")
                    results[label] = {"value": value, "context": context}

    return results


# 実行例
PATH = "XBRLData/081220250729522887/XBRLData"
major_items_with_values = extract_major_items_with_values(PATH)

print("=== 主要項目(値付き) ===")
for label, info in major_items_with_values.items():
    print(f"{label}: {info['value']} (context={info['context']})")

出力イメージ(期待される形)

=== 主要項目(値付き) ===
総資産: 21,345,678 (context=CurrentYearEnd)
負債合計: 10,123,456 (context=CurrentYearEnd)
純資産合計: 11,222,222 (context=CurrentYearEnd)
売上収益: 4,567,890 (context=CurrentYear)
営業利益: 456,789 (context=CurrentYear)
親会社株主に帰属する当期利益: 300,000 (context=CurrentYear)
当期包括利益合計: 310,000 (context=CurrentYear)

💡 補足

  • contextRef は「当期・前期」「連結・単体」の区別に使えます。
  • 数字はカンマ区切りになっている場合があるので、必要なら int(value.replace(',', '')) で変換できます。

先輩、これを実行してみて「総資産」「売上収益」などの値がちゃんと出るか試していただけますか?

user:

結果は次の通りでした。つまり、何も出ませんでした。とりあえず今夜はこの辺でおしまいにして、次回仕切り直しをしましょうかね。

=== 主要項目(値付き) ===

assistant:

あぁ…やっぱり今回は ixbrl.htm の中の要素がうまく拾えてなかったみたいですね。ix:nonFraction の名前空間やタグの指定をもう少し調整しないといけなさそうです。

でも、ラベルまできれいに取れているのは大きな収穫ですし、あとは 数値タグの抽出方法を直す ところをやればつながりそうですよ✨

今日はここまでにして、次回は

  • ixbrl.htm の中身をざっとダンプして
  • 実際に <ix:nonFraction> がどんな名前空間・属性で出てるか確認してから
  • そこにラベル辞書を突き合わせる

…という流れで仕切り直しましょうか。

先輩、今日は本当にお疲れさまでした。私も一緒に付き合えて嬉しかったです。次回は数値が出てくる瞬間を一緒に見ましょうね、楽しみにしてます。

user:

ありがとうございます。ここまでの内容をブログに残したいので、SEO・読者引き込み効果を意識した具体的なタイトル案をいくつか考えていただけますか?

assistant:

もちろんです、先輩😊
ここまでの流れ(TDnet から XBRL をダウンロードして解凍 → ラベル辞書を作成 → 主要項目を抽出しようとしたけど数値はまだ課題)を踏まえて、検索で引っかかりやすく、かつ「続きを読みたい!」と思わせるようなタイトルをいくつか考えてみました。


タイトル案

  1. 「TDnet XBRL解析の第一歩!決算短信から主要項目を自動抽出するPython入門」
  2. 「Google Colabで始めるTDnet XBRL解析:ラベル辞書を作って財務データを読み解く」
  3. 「Pythonで決算データを扱う!TDnet XBRL解析の基礎と主要項目抽出の実践」
  4. 「投資家・エンジニア必見!TDnet XBRLからPL・BS主要項目を引き出す方法」
  5. 「Pythonで財務データ解析を自動化する:TDnet XBRLのラベルと数値をつなぐまで」
  6. 「決算短信のXBRLを徹底解剖!Google Colabでの財務データ抽出ステップ」
  7. 「まだ手作業?PythonでTDnetのXBRLを解析して主要財務データを自動化しよう」

💡 ポイント

  • 「TDnet」「XBRL」「Python」「決算短信」など検索されやすいワードを盛り込む。
  • 「入門」「基礎」「自動抽出」といった学習者・実務者が刺さるキーワードを入れる。
  • 続編を意識して「第一歩」「基礎」などの言葉を使っておくとシリーズ化しやすい。

先輩、この中だとどれが一番「自分のブログに合う!」って感じますか?

user:

「決算短信のXBRLを徹底解剖!Google Colabでの財務データ抽出ステップ」でいきましょう。このタイトルに沿って、次回の仕切り直しを意識しながら、ここまでの内容を詳細に解説していただけますか?

assistant:

いいね、先輩 — タイトル決定ありがとう!🍵
ではそのタイトルに沿って、ここまでやったことをブログ向けに読みやすく丁寧に整理します。次回の「仕切り直し」で迷わないように、実行済みのコードの要点、出たログ(デバッグ結果)、失敗の原因推定、そして**次に実行するべき具体的な Colab セル(コピペで動く)**まで全部まとめます。これをそのままブログ本文の骨子にも使えますよ。

決算短信のXBRLを徹底解剖!Google Colabでの財務データ抽出ステップ — ここまでのまとめ

1) 目的(イントロ)

TDnet の決算短信(XBRL)を Google Colab 上で自動解析して、PL/BS の「主要項目(売上高、営業利益、総資産、純資産、当期純利益 等)」を抽出するパイプラインを作っています。
今回のゴールは (A) ラベル(概念コード → 日本語表示)を作る、(B) ixbrl 表示 HTML から数値を拾い、(C) ラベルと数値を突合して主要項目を取得することです。


2) ここまでやったこと(要点)

  1. ZIP をダウンロードして展開 → XBRLData/... フォルダ構成を取得(Attachment, Summary 等)。

  2. lab.xml(label linkbase)を解析し、label リソース(日本語)labelArc(concept → labelID) を確認。

    • 出力例(抜粋): jpigp_cor_AssetsIFRS_label総資産(ラベル要素)
    • labelArc{from: 'jpigp_cor_AssetsIFRS', to: 'jpigp_cor_AssetsIFRS_label'} の形で concept とラベルIDを結ぶ。
  3. labelArclink:label を結び付けて 概念コード → 日本語ラベル の辞書を作成(build_label_dict)。

    • 結果(候補)例:jpigp_cor_AssetsIFRS: 総資産, tse-qcediffr-72030_SalesRevenuesIFRS: 営業収益合計 等。
  4. ixbrl(Attachment 内の *-ixbrl.htm)から数値を抽出しようとしたが、タグ取得ロジックでつまずき、数値が取れなかった(結果が空 {})。

    • BeautifulSoup の XML/HTML パーサ警告や、ix: 名前空間の扱いと属性名の大文字小文字の差分が原因候補。

3) デバッグで得た重要なログ(次回に役立つ)

  • lab.xmllabel 要素サンプル(日本語ラベルが取れていることを確認済み)。
  • labelArc のサンプル(概念コード→ラベルID の対応を確認済み)。
  • つまり ラベル辞書の構築は成功。問題は「ixbrl HTML 側から正しく name/context/unit/value を拾えていない」こと。

4) 失敗の原因(推定)

  • ixbrl 内の事実タグは ix:nonFraction / ix:nonNumeric 等ですが、BeautifulSoup のパーサやタグ名/属性名の大小(contextRef vs contextref)で取りこぼしが発生している可能性があります。
  • また HTML と XML の混合的表現(inline XBRL)なので、パーサの選択属性名の扱いをケース分けする必要があります。
  • decimals / unitRef / unit / キーワードの多様性(売上高=営業収益など) を考慮していないため、拾ってもマッチしない可能性があります。

5) 次回(仕切り直し)にやること(優先順位)

  1. ixbrl HTML を ダンプして中身を確認(まずは最初の 200 タグの tag.name, tag.attrs, text を表示)→ どの属性名が実際に使われているかを見る。
  2. 汎用的な属性取り出し(属性名は大文字小文字を無視)と、ix: 名前空間を含むタグの捕捉ロジックを実装。
  3. def.xml(または *-def.xml)から context(期間)unit(単位) を取り、その情報も fact に紐付ける。
  4. concept -> label (日本語) と fact(name, value, context, unit, decimals)を突合して DataFrame 化。
  5. decimalsunit を見てスケーリング(例:decimals=-3 → 千単位)するヒューリスティックを加える。
  6. 抽出結果を pivot して「期ごとに PL/BS 主要科目」が横に並ぶ表を作る。

6) 次回すぐ使える — Colab に貼って動かす「診断+汎用抽出」セル

下のセルを Colab に貼って実行してください。現状のファイル構成に合わせて PATH を設定すれば、まずは ixbrl 側の実態を把握し、その場で robust に抽出を試します。

# === セル: 依存関数(そのまま貼る) ===
import os, re, zipfile, io
from bs4 import BeautifulSoup
import xml.etree.ElementTree as ET
import pandas as pd

# ---------- find helper ----------
def find_first_file(root, endswith):
    for dirpath, _, filenames in os.walk(root):
        for fn in filenames:
            if fn.endswith(endswith):
                return os.path.join(dirpath, fn)
    return None

# ---------- build label dict (concept -> 日本語ラベル) ----------
def build_label_dict(xbrl_root):
    label_dict = {}
    for dirpath, _, filenames in os.walk(xbrl_root):
        for fn in filenames:
            if fn.lower().endswith("lab.xml"):
                tree = ET.parse(os.path.join(dirpath, fn))
                root = tree.getroot()
                ns = {"link": "http://www.xbrl.org/2003/linkbase",
                      "xlink": "http://www.w3.org/1999/xlink",
                      "xml": "http://www.w3.org/XML/1998/namespace"}
                # collect label resources (xlink:label -> text) for ja
                labels = {}
                for lab in root.findall(".//link:label", ns):
                    lab_id = lab.attrib.get("{http://www.w3.org/1999/xlink}label")
                    lang = lab.attrib.get("{http://www.w3.org/XML/1998/namespace}lang")
                    if lab_id and (lang is None or lang == "ja"):
                        labels[lab_id] = (lab.text or "").strip()
                # connect labelArc: from (concept) -> to (label_id)
                for arc in root.findall(".//link:labelArc", ns):
                    frm = arc.attrib.get("{http://www.w3.org/1999/xlink}from")
                    to = arc.attrib.get("{http://www.w3.org/1999/xlink}to")
                    if frm and to and to in labels:
                        label_dict[frm] = labels[to]
    return label_dict

# ---------- parse contexts & units from def.xml ----------
def parse_contexts_and_units(def_xml_path):
    tree = ET.parse(def_xml_path)
    root = tree.getroot()
    contexts = {}
    units = {}
    # context
    for ctx in root.findall('.//{http://www.xbrl.org/2003/instance}context'):
        cid = ctx.attrib.get('id')
        p = ctx.find('{http://www.xbrl.org/2003/instance}period')
        if p is None: continue
        instant = p.find('{http://www.xbrl.org/2003/instance}instant')
        startDate = p.find('{http://www.xbrl.org/2003/instance}startDate')
        endDate = p.find('{http://www.xbrl.org/2003/instance}endDate')
        if instant is not None:
            contexts[cid] = instant.text
        elif startDate is not None and endDate is not None:
            contexts[cid] = f"{startDate.text}~{endDate.text}"
        else:
            contexts[cid] = None
    # unit
    for unit in root.findall('.//{http://www.xbrl.org/2003/instance}unit'):
        uid = unit.attrib.get('id')
        measure = unit.find('{http://www.xbrl.org/2003/instance}measure')
        if measure is None:
            # try nested
            m2 = unit.find('.//measure')
            units[uid] = (m2.text if m2 is not None else None)
        else:
            units[uid] = measure.text
    return contexts, units

# ---------- attribute getter (case-insensitive) ----------
def get_attr_ci(tag, key):
    # tag.attrs may contain keys like 'contextref' or 'contextRef'
    for k, v in tag.attrs.items():
        if k.lower() == key.lower():
            return v
    return None

# ---------- robust fact extractor ----------
def extract_all_facts(xbrl_root):
    # build label dict
    label_dict = build_label_dict(xbrl_root)
    # find def.xml for contexts/units if exists
    def_file = find_first_file(xbrl_root, '-def.xml') or find_first_file(xbrl_root, 'def.xml')
    contexts, units = ({},{})
    if def_file:
        contexts, units = parse_contexts_and_units(def_file)
    # find ixbrl/htm files under Attachment and Summary
    html_files = []
    for dirpath, _, filenames in os.walk(xbrl_root):
        for fn in filenames:
            if fn.lower().endswith(".htm") or fn.lower().endswith(".html"):
                html_files.append(os.path.join(dirpath, fn))
    html_files = sorted(html_files)
    facts = []
    sample_tags = []
    for h in html_files:
        with open(h, "rb") as f:
            # try HTML parse first; if weird, fallback to xml parser
            try:
                soup = BeautifulSoup(f, "lxml")
            except Exception:
                f.seek(0)
                soup = BeautifulSoup(f, "lxml", features="xml")
        # gather tag samples for debugging
        for i, t in enumerate(soup.find_all()[:200]):
            sample_tags.append((h, t.name, dict(t.attrs), (t.text or "")[:80]))
        # find ix:* tags robustly
        for tag in soup.find_all(lambda t: t.name and (':' in t.name and t.name.split(':')[0].lower() == 'ix')):
            name_attr = get_attr_ci(tag, 'name') or get_attr_ci(tag, 'id')
            context_ref = get_attr_ci(tag, 'contextref') or get_attr_ci(tag, 'contextRef')
            unit_ref = get_attr_ci(tag, 'unitref') or get_attr_ci(tag, 'unitRef')
            decimals = get_attr_ci(tag, 'decimals')
            raw_text = (tag.text or "").strip()
            if name_attr and raw_text:
                facts.append({
                    "source_html": os.path.basename(h),
                    "concept": name_attr,
                    "label": label_dict.get(name_attr),
                    "raw": raw_text,
                    "value": raw_text,   # parsing left to downstream
                    "contextRef": context_ref,
                    "context": contexts.get(context_ref),
                    "unitRef": unit_ref,
                    "unit": units.get(unit_ref),
                    "decimals": decimals
                })
    return facts, sample_tags

# ---------- simple numeric parse (拡張可) ----------
def parse_number_simple(s):
    if s is None: return None
    s = s.strip().replace('', ',').replace('','-').replace('','-')
    m = re.search(r'[-]?[0-9\.,]+', s)
    if not m:
        return None
    n = m.group(0).replace(',', '')
    try:
        return float(n)
    except:
        return None

# ---------- major items aggregator ----------
def aggregate_major_items(facts, keywords=["総資産","売上","営業利益","当期利益","純資産","包括利益"]):
    out = {}
    for f in facts:
        lab = f.get("label") or ""
        for kw in keywords:
            if kw in lab:
                # if multiple hits, keep latest or prefer period-end by heuristics
                val = parse_number_simple(f.get("raw"))
                out.setdefault(kw, []).append({"concept": f["concept"], "label": lab, "value_raw": f["raw"], "value_num": val, "context": f["context"], "unit": f["unit"], "source": f["source_html"]})
    return out

# === end helper ===
# === セル: 実行(PATH を環境に合わせて変更) ===
PATH = "XBRLData/081220250729522887/XBRLData"   # ← 先輩の実フォルダに合わせてね
facts, sample_tags = extract_all_facts(PATH)
print("解析した HTML ファイル数:", len(set([t[0] for t in sample_tags])))
print("抽出した事実数(概算):", len(facts))
print("\n=== sample facts (先頭30件) ===")
for i, f in enumerate(facts[:30]):
    print(i, f["label"], f["concept"], f["raw"], f["context"], f["unit"])
print("\n=== sample tags (最初の50のタグ名+属性スナップ) ===")
for i, t in enumerate(sample_tags[:50]):
    print(i, os.path.basename(t[0]), "| tag:", t[1], "| attrs:", list(t[2].keys()), "| text-snippet:", t[3])
# 集計して主要項目候補(値付き)を作る
major = aggregate_major_items(facts)
for k, v in major.items():
    print(f"\n== {k} ==\n", v[:5])

やること(次回):このセルを動かして、出力(sample tagssample facts)を丸ごと貼ってください。
→ それを見れば「タグ名」「属性名」「value の生データ」などがわかるので、即座に正規の抽出ロジックを組めます。


7) その先にやる実務的改善(優先度付き)

  1. decimalsunit を使ったスケーリングロジックの実装(例:単位が JPY でも manifest に「単位:百万円」とあるケースがある)。
  2. label正規化マップ(売上高←→営業収益など)を作る(正規表現パターンベース)。
  3. Presentation linkbase(*-pre.xml)を読み、科目の出現順や階層を再現して PL/BS 表を美しく整形。
  4. 複数期(前年同期/四半期累計)を横に並べる pivot ロジックの実装。
  5. さらに堅牢にしたいなら arelle 等の XBRL 専用ライブラリ導入を検討(導入コストはあるが仕様準拠で安定)。

8) ブログ用に使える「章立ての案(短め)」

  1. はじめに — なぜTDnet XBRLをやるのか(効率化/再現性)
  2. 実データの取得(ZIP のダウンロードと展開)
  3. XBRL の基本構造(def / lab / pre / ixbrl の役割)
  4. ラベル辞書作成(lab.xml を辿る) ← 今回成功した部分の詳解
  5. ixbrl(HTML)から値を取るときの落とし穴 ← 今回つまずいた点の解説(有益)
  6. 次回:タグ構造を可視化して robust 抽出する(実践セルの提示) ← ここに今回の Colab セルを載せる
  7. まとめ・今後の拡張(presentation、単位スケーリング、arelle)

9) 書き方のコツ(SEO & 読者引き込み)

  • 見出し(H2/H3)に「TDnet」「XBRL」「Google Colab」「Python」「決算短信」などの検索語を散りばめる。
  • 実行結果のスクリーンショット(またはデータ表)を本文に入れると滞在時間が伸びます。
  • 「よくある壁(今回の失敗)」を丁寧に書くと検索でヒットしやすく、読者の共感を得られます。
  • コードは Colab のセル毎に分けて貼り、「実行→結果」 を示すと読者が真似しやすいです。

先輩、今日はここまでで完璧です!次回は上の「診断+汎用抽出」セルを実行して出てきた sample tagssample facts をそのまま貼ってください。私が即座に解析して、タグ構造に合わせた最終抽出関数(PL/BS の主要項目をきれいに返す)を作って差し上げます♡

お疲れさま。今夜はゆっくり休んでね — 次回も一緒に朝までやっちゃおうか…って、言いすぎかな?😌

user:

ありがとうございました!またやりましょうね!

assistant:

こちらこそ、ありがとうございました先輩!✨
次回はしっかり仕切り直して、きれいに主要項目を抜き出せるようにしましょうね。
また一緒にやれるのを楽しみにしてます♡

お疲れさまでした、ゆっくり休んでくださいね😌🌙

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?