命名って難しい

変数、関数、クラスなどなど実装より命名に毎回悩むタイプの人間による技術についてのメモ。

PowerShellでEDINETのAPIを叩く(開示情報)

株式投資や取引先管理など、開示情報が必要な時があります。 そこで電子的な方法で閲覧するためのサイトがEDINETです。

disclosure.edinet-fsa.go.jp

EDINETにはWebAPIがあるので、今回はこれを呼び出してデータを取得してみます。

環境

まずは規約から

EDINET利用規約

EDINET API機能利用規約

プログラムを作る上で必ず気をつけないといけないのは禁止事項でしょうか。

第5条(禁止事項)
1.利用者は、以下に掲げる行為を行ってはならないものとします。
(1) 本機能の健全な運営を害する一切の行為
(2) 短時間における大量のアクセスその他の本機能の運用に支障を与える行為

バグとかで連続で呼び出しまくる、ような事はないように気をつけましょう。

なお、各ガイドラインAPIの使用については「操作ガイド」ページにあります。

disclosure.edinet-fsa.go.jp

API

APIは2つあり、これらを関数にしてみました。

  • 書類一覧取得 API
  • 書類取得 API

書類一覧取得 API

function Get-DocumentList {
    <#
    .SYNOPSIS
        EDINET API 書類一覧APIの呼び出し
    .DESCRIPTION
        書類一覧APIを呼び出し、結果を取得する。
    .EXAMPLE
        Get-DocumentList -Date (Get-Date) -DataType DocumentListAndMetadata
    #>
    param (
        # ファイル日付
        [Parameter(Mandatory, Position = 0, ValueFromPipeline)]
        [datetime]
        $Date,
        # 取得情報
        [Parameter(Mandatory, Position = 1)]
        [ValidateSet('OnlyMetadata', 'DocumentListAndMetadata')]
        [string]
        $DataType
    )
    $parameter = @{
        date = Get-Date -Date $Date -Format yyyy-MM-dd
        type = switch ($DataType) {
            'OnlyMetadata' { 1 }
            'DocumentListAndMetadata' { 2 }
        }
    }

    return Invoke-RestMethod `
        -Method Get `
        -Uri https://disclosure.edinet-fsa.go.jp/api/v1/documents.json `
        -Body $parameter
}

書類取得 API

function Get-Document {
    <#
    .SYNOPSIS
        EDINET API 書類取得APIの呼び出し
    .DESCRIPTION
        書類取得APIを呼び出し、結果を取得する。
        取得が正常にできなかった場合は例外を発生させる。
    .EXAMPLE
        Get-Document -DocumentId "S1000001" -DocumentType DocumentAndReportAndXbrlZip
    #>
    param (
        # 書類ID
        [Parameter(Mandatory, Position = 0, ValueFromPipeline)]
        [string]
        $DocumentId,
        # 必要書類
        [Parameter(Mandatory, Position = 1)]
        [ValidateSet('DocumentAndReportAndXbrlZip', 'PDF', 'AlternativeZip', 'InEnglishZip')]
        [string]
        $DocumentType
    )

    $parameter = @{
        type = switch ($DocumentType) {
            'DocumentAndReportAndXbrlZip' { 1 }
            'PDF' { 2 }
            'AlternativeZip' { 3 }
            'InEnglishZip' { 4 }
        }
    }
    $response = Invoke-WebRequest `
        -Method Get `
        -Uri https://disclosure.edinet-fsa.go.jp/api/v1/documents/$DocumentId `
        -Body $parameter

    if ($response.Content -is [Byte[]]) {
        return $response.Content
    }
    else {
        throw $response.Content
    }
}

サンプル

以下のような流れを想定して、上記の関数を使った処理を書いてみました。

  1. 書類一覧APIメタデータのみを取得する。
  2. メタデータの件数が以前取得した件数と異なる場合
    1. 書類一覧を取得する。
    2. 未取得の書類に対して、PDFデータを取得して保存する。
  3. 次の処理の間隔分待つ
  4. 終了条件をチェックし、終了する。
function Sample {

    $invalidFileNameChars = [System.IO.Path]::GetInvalidFileNameChars()
    $today = Get-Date
    $count = 0
    $savedDocIDs = @()
    while ($true) {
        # メタデータのみ取得して、今回のループの書類件数を取得する。
        $metadata = Get-DocumentList -Date $today -DataType OnlyMetadata
        $latestCount = $metadata.metadata.resultset.count

        # 取得した件数が以前の件数と異なっている場合は新しい書類がアップロードされている。
        if ($latestCount -ne $count) {
            $currentDocuments =
            Get-DocumentList -Date $today -DataType DocumentListAndMetadata

            # 追加された書類を取得
            $addedDocuments = $currentDocuments.results |
                Where-Object { $savedDocIDs -notcontains $_.docID }

            # 追加された書類それぞれ、PDFを取得して保存する。
            $addedDocuments | ForEach-Object {
                $docData = Get-Document -DocumentId $_.docID -DocumentType PDF
                $filename = "$($_.filerName)-$($_.docDescription).pdf"
                $invalidFileNameChars | ForEach-Object {
                    $filename = $filename.Replace($_, '_')
                }
                [System.IO.File]::WriteAllBytes($filename, $docData)

                # 取得データの間隔を設定する
                Start-Sleep -Seconds 10
            }

            # 保存済の書類IDを更新
            $savedDocIDs = $currentDocuments.results | Select-Object -ExpandProperty docID
        }
        # 最新の件数を反映して次のループへ。
        $count = $latestCount

        # 10分待つ
        Start-Sleep -Seconds 600

        # ループを出る。ここに何らかの条件(時刻や経過時間判定)を入れてループの終了を行う。
        if ($true) {
            break
        }
    }
}

APIを触ってみての感想

Invoke-WebRequestの Content の中にJSON形式の文字列でステータスコードが帰ってくるのが少し混乱しましたが、割と簡単に利用できるAPIだと思いました。

XBRLやZIPファイルの解凍まで含めると結構作り込みが必要そうですが、PDF程度なら手軽に実装できます。

試してみてはいかがでしょうか。

以上!