6
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【NotebookLM活用】ひょんなことからe-Gov法令APIでアレを一括取得してNotebookLMをアシスタント化計画!

Posted at

みんな、NotebookLMなどのAI活用している?
GMOコネクト 執行役員CTOの菅野 哲(かんの さとる)です。

2025年12月18日に全面施行される「スマートフォンにおいて利用される特定ソフトウェアに係る競争の促進に関する法律」(通称:スマホ法)のカウントダウンがされているのですが、ご存知でしょうか?
そんな時の流れを感じながら、公正取引委員会に関連する法令をもっと効率的に参照したいなぁ〜、e-Gov法令APIを使って法令データを一括取得し、NotebookLMに格納できたら素敵だな〜という電波を受信したため、ちょっと試してみたという記事でございます。

背景と動機

独禁法や下請法など、公正取引委員会が所管する法令を調べる機会が多いのですが、毎回e-Gov法令検索でブラウザを開いて検索するのは手間がかかります。また、複数の法令を横断的に参照したい場合、タブを何個も開くことになり効率的ではありません。

そんな中、e-Gov法令APIとNotebookLMを組み合わせる手法(下のサイト)を知り、「これは公取委関連法令でも使える!」と思い立ちました。著作権法の例を参考に、公正取引委員会が所管する法令に特化することも容易でした。

e-Gov法令APIについて

e-Gov法令APIは、日本の法令データをプログラムから取得できる無料のWeb APIです。現行法令の全文データをXML形式で提供しており、法令番号や法令名から検索できます。

主なエンドポイントは次のとおりです。

  • 法令一覧の取得: https://elaws.e-gov.go.jp/api/1/lawlists/1
  • 個別法令データの取得: https://elaws.e-gov.go.jp/api/1/lawdata/{法令ID}

実装の内容

取得対象の法令

実験として取り敢えず取得対象としたのは、公正取引委員会が所管する主要な4つの法令です。

TARGET_LAWS = [
    "私的独占の禁止及び公正取引の確保に関する法律",
    "下請代金支払遅延等防止法",
    "特定受託事業者に係る取引の適正化等に関する法律",
    "スマートフォンにおいて利用される特定ソフトウェアに係る競争の促進に関する法律",
    # テスト用(わざと間違った名前)
    "存在しない法律テスト"
]

法令データ取得関数

まず、法令名から法令データを取得する関数を実装してみます。

import requests
import xml.etree.ElementTree as ET
import time

def fetch_law_data(law_name):
    try:
        # e-Gov APIから全法令リストを取得(現行法令)
        list_url = "https://elaws.e-gov.go.jp/api/1/lawlists/1"
        response = requests.get(list_url)
        time.sleep(1) # APIサーバーへの負荷軽減
        
        root = ET.fromstring(response.content)
        law_id = None
        
        # 完全一致で検索
        for law in root.findall(".//LawNameListInfo"):
            if law.find("LawName").text == law_name:
                law_id = law.find("LawId").text
                break
        
        if not law_id:
            return None # IDが見つからない場合
        
        # 条文データを取得
        detail_url = f"https://elaws.e-gov.go.jp/api/1/lawdata/{law_id}"
        law_response = requests.get(detail_url)
        return law_response.content
        
    except Exception as e:
        print(f"エラー発生: {e}")
        return None

この関数では、次の処理を行っています。

  1. 法令一覧APIから全法令リストを取得
  2. 正式名称で完全一致検索を行い、法令IDを取得
  3. 取得した法令IDを使って条文データをXMLで取得
  4. APIサーバーへの負荷軽減のため、各リクエスト間に1秒の待機時間を設定

XML→Markdown変換関数

取得したXMLデータをMarkdown形式に変換する関数を準備します。

def parse_to_markdown(xml_content, law_name):
    # 簡易パース処理
    root = ET.fromstring(xml_content)
    md_text = f"# {law_name}\n\n"
    
    for article in root.findall(".//Article"):
        title = article.find("ArticleTitle")
        caption = article.find("ArticleCaption")
        t_text = title.text if title is not None else ""
        c_text = caption.text if caption is not None else ""
        
        md_text += f"## {t_text} {c_text}\n"
        for sentence in article.findall(".//Sentence"):
            md_text += f"{sentence.text}\n"
        md_text += "\n"
    
    return md_text

この関数では、条文の構造を保ちながら読みやすいMarkdown形式に整形しています。各条文は見出し(##)として扱い、本文は段落として配置することで、NotebookLMで扱いやすい形式にしました。

注意: 今回の実装は簡易版です。元記事では項(Paragraph)、号(Item)、副項(Subitem)まで詳細に解析していますが、今回は条文と本文のみを抽出する簡素な実装にしています。より詳細な構造が必要な場合は、元記事のコードを参考に拡張できますよ!

メイン処理

最後に、すべてを統合したメイン処理なのです。

import os  # ファイル操作のために追加

# --- メイン処理 ---
not_found_laws = [] # APIで見つからなかった法令
existing_laws = []  # すでにファイルが存在していた法令
downloaded_laws = [] # 新規にダウンロードした法令

print("処理を開始します...\n")

for law in TARGET_LAWS:
    filename = f"{law}.md"
    
    # 【追加機能】ファイルが既に存在するかチェック
    if os.path.exists(filename):
        print(f"[{law}]")
        print("  -> 既存ファイルがあるためスキップします (SKIP)")
        existing_laws.append(law)
        continue # 次のループへ(APIを叩かない)
    
    # 存在しない場合のみAPIアクセスへ進む
    print(f"[{law}]")
    print("  -> データを取得中...", end=" ")
    
    xml_data = fetch_law_data(law)
    
    if xml_data:
        # 成功時
        md_output = parse_to_markdown(xml_data, law)
        with open(filename, "w", encoding="utf-8") as f:
            f.write(md_output)
        print("-> OK (新規保存)")
        downloaded_laws.append(law)
    else:
        # 失敗時
        print("-> × 見つかりません (ERROR)")
        not_found_laws.append(law)

# --- 結果レポート ---
print("\n" + "="*50)
print("【実行結果サマリー】")
print(f"・新規取得完了 : {len(downloaded_laws)}")
print(f"・既存スキップ : {len(existing_laws)}")
print(f"・取得失敗   : {len(not_found_laws)}")

if not_found_laws:
    print("\n[!] 以下の法令は見つかりませんでした(名称を確認してください):")
    for s in not_found_laws:
        print(f"  - {s}")
print("="*50)

地球に優しい実装のポイント・:*+.(( °ω° ))/.:+

既存ファイルのチェック機能

同じ法令を何度もダウンロードしないよう、ファイルが存在する場合はAPIアクセスをスキップする仕組みを実装しました。これによりAPIサーバーへの負荷を軽減し、実行時間も短縮できます。

エラーハンドリング

法令が見つからない場合の処理を実装し、実行結果のサマリーを表示するようにしました。どの法令が正常に取得できて、どれが失敗したのかが一目で分かります。

API負荷への配慮

time.sleep(1) を使って、各リクエスト間に1秒の待機時間を設けています。連続してリクエストを送ると、サーバーに負荷をかけてしまう可能性があるためです。

完全なコード

完全なコードを表示
import requests
import xml.etree.ElementTree as ET
import time
import os

# 1. 取得したい法令の正式名称リスト
TARGET_LAWS = [
    "私的独占の禁止及び公正取引の確保に関する法律",
    "下請代金支払遅延等防止法",
    "特定受託事業者に係る取引の適正化等に関する法律",
    "スマートフォンにおいて利用される特定ソフトウェアに係る競争の促進に関する法律",
    # テスト用(わざと間違った名前)
    "存在しない法律テスト"
]

def fetch_law_data(law_name):
    try:
        # e-Gov APIから全法令リストを取得(現行法令)
        list_url = "https://elaws.e-gov.go.jp/api/1/lawlists/1"
        response = requests.get(list_url)
        time.sleep(1) # APIサーバーへの負荷軽減
        
        root = ET.fromstring(response.content)
        law_id = None
        
        # 完全一致で検索
        for law in root.findall(".//LawNameListInfo"):
            if law.find("LawName").text == law_name:
                law_id = law.find("LawId").text
                break
        
        if not law_id:
            return None # IDが見つからない場合
        
        # 条文データを取得
        detail_url = f"https://elaws.e-gov.go.jp/api/1/lawdata/{law_id}"
        law_response = requests.get(detail_url)
        return law_response.content
        
    except Exception as e:
        print(f"エラー発生: {e}")
        return None

def parse_to_markdown(xml_content, law_name):
    # 簡易パース処理
    root = ET.fromstring(xml_content)
    md_text = f"# {law_name}\n\n"
    
    for article in root.findall(".//Article"):
        title = article.find("ArticleTitle")
        caption = article.find("ArticleCaption")
        t_text = title.text if title is not None else ""
        c_text = caption.text if caption is not None else ""
        
        md_text += f"## {t_text} {c_text}\n"
        for sentence in article.findall(".//Sentence"):
            md_text += f"{sentence.text}\n"
        md_text += "\n"
    
    return md_text

# --- メイン処理 ---
not_found_laws = [] # APIで見つからなかった法令
existing_laws = []  # すでにファイルが存在していた法令
downloaded_laws = [] # 新規にダウンロードした法令

print("処理を開始します...\n")

for law in TARGET_LAWS:
    filename = f"{law}.md"
    
    # 【追加機能】ファイルが既に存在するかチェック
    if os.path.exists(filename):
        print(f"[{law}]")
        print("  -> 既存ファイルがあるためスキップします (SKIP)")
        existing_laws.append(law)
        continue # 次のループへ(APIを叩かない)
    
    # 存在しない場合のみAPIアクセスへ進む
    print(f"[{law}]")
    print("  -> データを取得中...", end=" ")
    
    xml_data = fetch_law_data(law)
    
    if xml_data:
        # 成功時
        md_output = parse_to_markdown(xml_data, law)
        with open(filename, "w", encoding="utf-8") as f:
            f.write(md_output)
        print("-> OK (新規保存)")
        downloaded_laws.append(law)
    else:
        # 失敗時
        print("-> × 見つかりません (ERROR)")
        not_found_laws.append(law)

# --- 結果レポート ---
print("\n" + "="*50)
print("【実行結果サマリー】")
print(f"・新規取得完了 : {len(downloaded_laws)}")
print(f"・既存スキップ : {len(existing_laws)}")
print(f"・取得失敗   : {len(not_found_laws)}")

if not_found_laws:
    print("\n[!] 以下の法令は見つかりませんでした(名称を確認してください):")
    for s in not_found_laws:
        print(f"  - {s}")
print("="*50)

実行結果

実行すると、次のような出力が得られます。

処理を開始します...

[私的独占の禁止及び公正取引の確保に関する法律]
  -> データを取得中... -> × 見つかりません (ERROR)
[下請代金支払遅延等防止法]
  -> データを取得中... -> OK (新規保存)
[特定受託事業者に係る取引の適正化等に関する法律]
  -> データを取得中... -> OK (新規保存)
[スマートフォンにおいて利用される特定ソフトウェアに係る競争の促進に関する法律]
  -> データを取得中... -> OK (新規保存)
[存在しない法律テスト]
  -> データを取得中... -> × 見つかりません (ERROR)

==================================================
【実行結果サマリー】
・新規取得完了 : 3 件
・既存スキップ : 0 件
・取得失敗   : 2 件

[!] 以下の法令は見つかりませんでした(名称を確認してください):
  - 私的独占の禁止及び公正取引の確保に関する法律
  - 存在しない法律テスト
==================================================

コマンド実行したディレクトリに、各法令のMarkdownファイルが生成されます。

NotebookLMでの活用

取得したMarkdownファイルをNotebookLMにアップロードすることで、いくつかの便利な機能が使えるようになります。

複数法令の横断検索

複数の法令を横断した検索が可能になり、たとえば「優越的地位の濫用」という概念が独禁法とフリーランス新法でどう扱われているかを一度に検索できます。

AIによる要約

AIによる要約機能を使えば、長大な法令の要点を素早く把握することができます。

法令間の比較分析

法令間の関連性や相違点についてAIに質問することで、複雑な法律関係の理解が深まります。たとえば、下請法と独禁法の適用関係について質問したり、各法令の規制対象の違いを比較したりできます。

NotebookLMの強み

元記事でも強調されていますが、NotebookLMの最大の利点はソース(条文)に基づいて回答するため、一般的なChatGPTなどと比べてハルシネーション(誤った情報の生成)が大幅に減少することです。法令の条数を間違えたり、存在しない規定を創作したりするリスクが低くなります。

実装時の注意点

法令名は正式名称で

法令名は正式名称での完全一致が必要です。略称や通称では検索できないため、事前に正式名称を確認しておく必要があります。今回のコードでは「存在しない法律テスト」という架空の法令名を意図的に含めて、エラー処理が正しく動作することを確認しました。

API負荷への配慮

各リクエスト間に1秒の待機時間を入れています。連続してリクエストを送ると、サーバーに負荷をかけてしまう可能性があるためです。大量の法令を取得する場合は、さらに待機時間を延ばすことを検討してください。

XMLパース処理の限界

今回は簡易的な実装にしていますが、実際の法令XMLは複雑な構造を持っているため、より詳細な情報が必要な場合は丁寧なパース処理が必要になります。たとえば、項、号、別表などの構造を正確に再現したい場合は、元記事のコードを参考に、より高度なXML処理を実装することをお勧めします。

結果と考察

この取り組みにより、公取委関連の法令データを効率的に取得し、NotebookLMという新しい形で活用できるようになりました。
従来はブラウザで個別に検索していた法令が、一括で手元に揃い、AI機能を使った検索や分析が可能になったことは大きなメリットです。特に、複数の法令にまたがる概念を調べる際や、法令間の関係性を理解する際に威力を発揮します。

一方で、法令の改正には対応できないという課題があります。取得したMarkdownファイルはある時点でのスナップショットなので、法改正があった場合は再取得が必要です。実務で使う場合は、定期的な更新の仕組みを検討する必要があるでしょう。

また、NotebookLMのAI機能の精度には注意が必要です。法令の解釈は専門的な知識が必要な領域であり、AIの回答は参考程度に留め、重要な判断をする際は必ず原文を確認することが重要です。

まとめ

e-Gov法令APIを活用することで、プログラムから簡単に法令データを取得できることを確認できました。取得したデータをNotebookLMに格納することで、AI機能を使った新しい法令の活用方法が可能になります。

今回のコードは基本的な実装ですが、必要に応じて取得対象の法令を増やしたり、XML解析をより詳細にしたりと、拡張の余地は十分にあります。公開APIを活用した法令データの利活用は、法務業務の効率化や法令理解の深化に役立つ可能性があります。

ただし、法令の正確な解釈が必要な場面では、必ず原文を確認し、専門家の助言を得ることを忘れないようにしましょう。

参考リンク

6
2
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
6
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?