# gbizinfo — gBizINFO REST API v2 Python クライアント

[![PyPI version](https://img.shields.io/pypi/v/gbizinfo.svg)](https://pypi.org/project/gbizinfo/)
[![Python](https://img.shields.io/pypi/pyversions/gbizinfo.svg)](https://pypi.org/project/gbizinfo/)
[![Context7 Indexed](https://img.shields.io/badge/Context7-Indexed-ECFDF5)](https://context7.com/youseiushida/gbizinfo)
[![Context7 llms.txt](https://img.shields.io/badge/Context7-llms.txt-ECFDF5)](https://context7.com/youseiushida/gbizinfo/llms.txt)


**gbizinfo** は、[gBizINFO（法人活動情報）](https://info.gbiz.go.jp/) の REST API v2 に対応した Python クライアントライブラリです。法人検索・法人番号指定取得・差分更新の全エンドポイントをサポートし、同期・非同期クライアント、自動ページング、ローカルキャッシュ、リトライ、レート制限を提供します。内部の HTTP 通信には [httpx](https://github.com/encode/httpx) を使用しています。

[GitHub Repository](https://github.com/youseiushida/gbizinfo)

> **API 側の既知の問題（2025年2月時点）**
>
> 以下のエンドポイント / パラメータは OpenAPI 仕様に定義されていますが、API サーバー側で正常に動作しないことが確認されています。本ライブラリは仕様通りにリクエストを送信しますが、サーバー側の問題により 404 または 500 が返る場合があります。
>
> - **検索パラメータ `patent`・`certification`**: 検索リクエストに含めると 404 が返ります
> - **差分更新エンドポイント `/v2/hojin/updateInfo/patent`・`/v2/hojin/updateInfo/subsidy`**: 500 Internal Server Error が返ります

## インストール

```sh
pip install gbizinfo
```

## クイックスタート

API トークンは [gBizINFO](https://info.gbiz.go.jp/) から取得してください。

```python
from gbizinfo import GbizClient

with GbizClient(api_token="YOUR_TOKEN") as client:
    # 法人名で検索
    result = client.search(name="トヨタ", limit=5)
    for item in result.items:
        print(item.corporate_number, item.name)

    # 法人番号で詳細取得
    info = client.get("1180301018771")
    print(info.name, info.location, info.capital_stock)
```

環境変数 `GBIZINFO_API_TOKEN` を設定すれば、引数省略も可能です。

```sh
export GBIZINFO_API_TOKEN="YOUR_TOKEN"
```

```python
with GbizClient() as client:  # 環境変数から自動取得
    result = client.search(name="ソニー")
```

## 法人検索

`search()` は 30 以上の検索パラメータをサポートしています。Enum による型安全な指定が可能です。

```python
from gbizinfo import GbizClient
from gbizinfo.enums import Prefecture, CorporateType, Ministry, Source

with GbizClient(api_token="YOUR_TOKEN") as client:
    # 都道府県 + 法人種別
    result = client.search(
        prefecture=Prefecture.東京都,
        corporate_type=CorporateType.株式会社,
        limit=10,
    )

    # 複数の法人種別を指定
    result = client.search(
        corporate_type=[CorporateType.株式会社, CorporateType.合同会社],
        prefecture=Prefecture.大阪府,
    )

    # 資本金・従業員数の範囲指定
    result = client.search(
        capital_stock_from=100_000_000,
        capital_stock_to=500_000_000,
        employee_number_from=1000,
        prefecture=Prefecture.愛知県,
    )

    # 出典元・担当府省で絞り込み
    result = client.search(source=Source.調達, ministry=Ministry.国税庁)

    # 補助金・調達キーワード検索
    result = client.search(subsidy="環境", prefecture=Prefecture.東京都)
    result = client.search(procurement="情報", prefecture=Prefecture.東京都)
```

### 職場情報 Enum

職場情報パラメータも Enum で型安全に指定できます。

```python
from gbizinfo.enums import (
    AverageAge,
    AverageContinuousServiceYears,
    MonthAverageOvertimeHours,
    FemaleWorkersProportion,
)

result = client.search(
    average_age=AverageAge.歳30以下,
    prefecture=Prefecture.東京都,
)
result = client.search(
    female_workers_proportion=FemaleWorkersProportion.割合61以上,
    prefecture=Prefecture.東京都,
)
```

## 法人番号指定取得

法人番号（13桁）を指定して詳細情報を取得します。チェックデジットの自動バリデーション付きです。

```python
info = client.get("7000012050002")  # 国税庁
print(info.name)                     # "国税庁"
print(info.corporate_number)         # "7000012050002"
print(info.location)                 # 所在地
print(info.capital_stock)            # 資本金
print(info.employee_number)          # 従業員数
print(info.date_of_establishment)    # 設立年月日
```

### サブリソース

法人番号に紐づく各種情報を個別に取得できます。

```python
cert = client.get_certification("7000012050002")   # 届出・認定
comm = client.get_commendation("7000012050002")    # 表彰
corp = client.get_corporation("7000012050002")     # 届出認定
fin  = client.get_finance("7000012050002")         # 財務
pat  = client.get_patent("7000012050002")          # 特許
proc = client.get_procurement("7000012050002")     # 調達
sub  = client.get_subsidy("7000012050002")         # 補助金
work = client.get_workplace("7000012050002")       # 職場情報
```

## 差分更新

指定期間内に更新された法人情報を取得します。

```python
from datetime import date, timedelta

to_date = date.today()
from_date = to_date - timedelta(days=3)

result = client.get_update_info(from_date=from_date, to_date=to_date)
print(result.total_count)   # 総件数
print(result.total_page)    # 総ページ数
print(len(result.items))    # 取得件数

for item in result.items:
    print(item.corporate_number, item.name)
```

カテゴリ別の差分更新エンドポイントも対応しています。

```python
client.get_update_certification(from_date=..., to_date=...)
client.get_update_commendation(from_date=..., to_date=...)
client.get_update_corporation(from_date=..., to_date=...)
client.get_update_finance(from_date=..., to_date=...)
client.get_update_patent(from_date=..., to_date=...)
client.get_update_procurement(from_date=..., to_date=...)
client.get_update_subsidy(from_date=..., to_date=...)
client.get_update_workplace(from_date=..., to_date=...)
```

## to_flat_dict() によるフラット化

ネストされた法人情報をフラットな辞書に変換できます。pandas の DataFrame 化に便利です。

```python
info = client.get("1180301018771")

# リストの扱い方を 4 種類から選択
flat = info.to_flat_dict(lists="count")    # リスト → 件数のみ
flat = info.to_flat_dict(lists="first")    # リスト → 先頭要素のみ展開
flat = info.to_flat_dict(lists="json")     # リスト → JSON 文字列
flat = info.to_flat_dict(lists="explode")  # リスト → _0, _1, ... に展開

# SearchResult / UpdateResult からまとめてフラット化
result = client.search(name="トヨタ", limit=10)
dicts = result.to_flat_dicts(lists="count")

# pandas DataFrame への変換例
import pandas as pd
df = pd.DataFrame(dicts)
```

## 非同期クライアント

`AsyncGbizClient` をインポートし、`await` を付けるだけです。API は同期版と同一です。

```python
import asyncio
from gbizinfo import AsyncGbizClient

async def main():
    async with AsyncGbizClient(api_token="YOUR_TOKEN") as client:
        result = await client.search(name="ソニー", limit=3)
        for item in result.items:
            print(item.name)

        info = await client.get("1180301018771")
        print(info.name)

asyncio.run(main())
```

非同期クライアントは `max_concurrent` で並行リクエスト数を制御できます（デフォルト 10）。

## 自動ページング

gBizINFO API にはページネーションがあり、検索結果が `limit` 件を超える場合は複数回のリクエストが必要です。`paginate_search()` と `paginate_update_info()` はこれを透過的に処理し、全件をイテレータで返します。

```python
# 検索結果の自動ページング
for item in client.paginate_search(prefecture=Prefecture.東京都, limit=2000):
    print(item.corporate_number, item.name)

# 差分更新の自動ページング
from datetime import date

for item in client.paginate_update_info(
    from_date=date(2025, 2, 1),
    to_date=date(2025, 2, 5),
):
    print(item.corporate_number, item.update_date)
```

安全のため、10 ページ（最大 50,000 件）を超えると `PaginationLimitExceededError` が送出されます。検索条件を絞り込むか、期間を短くして分割取得してください。

### get_recent_updates()

過去 N 日分の更新を簡単に取得するヘルパーです。

```python
for item in client.get_recent_updates(days=7):
    print(item.corporate_number, item.name)
```

## Enum 一覧

マジックストリングを排除し、IDE の補完で安全にパラメータを指定できます。

| Enum | 用途 | 例 |
|:---|:---|:---|
| `Prefecture` | 都道府県（47） | `Prefecture.東京都` → `"13"` |
| `CorporateType` | 法人種別（10） | `CorporateType.株式会社` → `"301"` |
| `Region` | 地域（10） | `Region.関東.prefectures` → 都道府県タプル |
| `Ministry` | 担当府省（49） | `Ministry.国税庁` |
| `Source` | 出典元（6） | `Source.調達` |
| `AverageAge` | 平均年齢区分 | `AverageAge.歳30以下` |
| `AverageContinuousServiceYears` | 平均勤続年数区分 | `AverageContinuousServiceYears.年21以上` |
| `MonthAverageOvertimeHours` | 平均残業時間区分 | `MonthAverageOvertimeHours.時間20未満` |
| `FemaleWorkersProportion` | 女性労働者比率区分 | `FemaleWorkersProportion.割合61以上` |
| `BusinessItem` | 営業品目 | `BusinessItem.情報処理` |
| `QualificationType` | 全省庁統一資格種別 | `QualificationType.物品の製造` |
| `PatentClassification` | 特許分類（133） | `PatentClassification.食品_食料品` |
| `DesignClassification` | 意匠分類（57） | `DesignClassification.衣服` |
| `TrademarkClassification` | 商標分類（45） | `TrademarkClassification.化学品` |
| `PatentType` | 知財種別 | `PatentType.特許` |

すべての Enum は `StrEnum` を継承しているため、文字列としてそのまま使用できます。

```python
from gbizinfo.enums import Prefecture

Prefecture.東京都 == "13"  # True
```

## キャッシュ

`cache_dir` を指定するとローカルファイルキャッシュが有効になります。TTL（デフォルト 24 時間）内は API を呼ばずにキャッシュから返却します。

```python
from gbizinfo import GbizClient
from gbizinfo.config import CacheMode

client = GbizClient(
    api_token="YOUR_TOKEN",
    cache_dir="./cache",           # キャッシュディレクトリ
    cache_mode=CacheMode.READ_WRITE,
    cache_ttl=60 * 60 * 12,        # 12 時間
)

# キャッシュモード
# CacheMode.OFF            キャッシュ無効（デフォルト）
# CacheMode.READ           読み取りのみ（書き込みしない）
# CacheMode.READ_WRITE     読み書き両方
# CacheMode.FORCE_REFRESH  常にAPIから再取得し、キャッシュを更新
```

## エラーハンドリング

API エラーや通信エラーは、種別に応じた例外クラスで送出されます。すべての例外は `GbizError` を継承しています。

```python
import gbizinfo
from gbizinfo import GbizClient

with GbizClient(api_token="YOUR_TOKEN") as client:
    try:
        info = client.get("7000012050002")
    except gbizinfo.GbizBadRequestError as e:
        # 400: パラメータ誤り（errors 配列付き）
        print(e.context.status_code, e.errors)
    except gbizinfo.GbizUnauthorizedError as e:
        # 401: トークン無効
        print(e.context.status_code)
    except gbizinfo.GbizNotFoundError as e:
        # 404: 法人番号未登録
        print(e.context.status_code)
    except gbizinfo.GbizRateLimitError as e:
        # 429: レート制限超過
        print(e.context.retry_after)
    except gbizinfo.GbizServerError as e:
        # 5xx: サーバーエラー
        print(e.context.status_code)
    except gbizinfo.GbizTransportError as e:
        # ネットワーク接続エラー
        print(e.original)
    except gbizinfo.GbizValidationError as e:
        # 送信前バリデーションエラー
        print(e)
```

例外の一覧:

| ステータス | 例外クラス | 説明 |
|:---|:---|:---|
| 400 | `GbizBadRequestError` | パラメータ誤り（`errors` 配列付き） |
| 401 | `GbizUnauthorizedError` | トークン無効 |
| 403 | `GbizForbiddenError` | アクセス禁止 |
| 404 | `GbizNotFoundError` | リソース未発見 |
| 429 | `GbizRateLimitError` | レート制限超過 |
| 5xx | `GbizServerError` | サーバーエラー |
| --- | `GbizTransportError` | ネットワーク接続エラー |
| --- | `GbizTimeoutError` | タイムアウト |
| --- | `GbizValidationError` | 送信前バリデーション |
| --- | `GbizCorporateNumberError` | 法人番号バリデーション |
| --- | `PaginationLimitExceededError` | ページング上限超過 |

## リトライ

通信エラー（タイムアウト、接続エラー）および 429 / 5xx は、指数バックオフ + ジッターで自動リトライされます（デフォルト最大 5 回）。`Retry-After` ヘッダーにも対応しています。

```python
client = GbizClient(
    api_token="YOUR_TOKEN",
    retry_max_attempts=3,       # 最大試行回数（デフォルト: 5）
    retry_base_delay=1.0,       # バックオフ基準秒（デフォルト: 0.5）
    retry_cap_delay=16.0,       # バックオフ上限秒（デフォルト: 8.0）
)

# リトライ無効化
client = GbizClient(api_token="YOUR_TOKEN", retry_max_attempts=1)
```

## レート制限

高頻度アクセスによる接続遮断を防ぐため、デフォルトで 1 秒あたり 1 リクエストのレート制限が適用されます。

```python
client = GbizClient(
    api_token="YOUR_TOKEN",
    rate_limit_per_sec=1.5,  # 1.5 リクエスト/秒
)
```

## タイムアウト

デフォルトのタイムアウトは 30 秒です。

```python
client = GbizClient(api_token="YOUR_TOKEN", timeout=60.0)
```

## HTTP クライアントのカスタマイズ

内部の [httpx](https://www.python-httpx.org/) クライアントを直接指定できます。

```python
import httpx
from gbizinfo import GbizClient

# プロキシ経由
client = GbizClient(api_token="YOUR_TOKEN", proxy="http://my.proxy:8080")

# HTTP/2 有効化（pip install 'httpx[http2]' が必要）
client = GbizClient(api_token="YOUR_TOKEN", http2=True)

# 外部 httpx.Client を注入（ライフサイクルはユーザー管理）
http_client = httpx.Client(
    base_url="https://api.info.gbiz.go.jp/hojin",
    timeout=60.0,
)
client = GbizClient(api_token="YOUR_TOKEN", http_client=http_client)
```

## HTTP リソースの管理

デフォルトでは、`close()` を呼ぶか、コンテキストマネージャを使用して HTTP 接続を解放します。

```python
# コンテキストマネージャ（推奨）
with GbizClient(api_token="YOUR_TOKEN") as client:
    result = client.search(name="テスト")

# 手動クローズ
client = GbizClient(api_token="YOUR_TOKEN")
try:
    result = client.search(name="テスト")
finally:
    client.close()
```

非同期版:

```python
async with AsyncGbizClient(api_token="YOUR_TOKEN") as client:
    ...

# または
client = AsyncGbizClient(api_token="YOUR_TOKEN")
try:
    ...
finally:
    await client.aclose()
```

## 動作要件

Python 3.12 以上。
