[PowerShell] e-Gov データポータルで検索してみる

## はじめに
先日、[e-Gov データポータル](https://data.e-gov.go.jp/info/ja)を利用する機会がありました。
> このサイト（e-Govデータポータル）は、行政機関等が保有する公共データのうち、オープンデータとして提供するものを対象としてカタログを整備し、横断的な検索を可能とするとともに、提供されるオープンデータの内容をわかりやすく提示することにより、オープンデータの活用に資することを目的としてデジタル庁が整備、運営するWebサイトです。
　
(<i>https://data.e-gov.go.jp/info/ja/about-site</i> から引用)

ある統計データを探していたのですが、各機関のリソースが無秩序に突っ込まれている感があり、残念ながらWebサイトでの検索は使い勝手がいいとはいえません。

PowerShellでの検索操作を試行したところ、それなりに活用できそうなので備忘録も兼ねて投稿します。

## 動作確認環境
PowerShell 7.3.4

## データセットの検索
例として「人口動態」に関するデータセットを検索してみます。
APIの使い方は[CKANのAPIガイド](https://docs.ckan.org/en/2.10/api/index.html)が参考になります。

https://docs.ckan.org/en/2.10/api/index.html

とりあえず「人口動態」をキーワードに検索してみます。
URLで呼び出しが可能なAPI関数は多数ありますが、今回は[`package_search`](https://docs.ckan.org/en/2.10/api/index.html#ckan.logic.action.get.package_search)のみを使っています。

`Invoke-WebRequest`でAPIを呼び出した結果はJSONで得られるので`PSCustomObject`に変換します。

```powershell:PowerShell 7.3
$egovApi = 'https://data.e-gov.go.jp/data/api/3/action/package_search?'
$query = 'q=人口動態'
$dataset = Invoke-WebRequest "$egovApi$query" | ConvertFrom-Json
```

引っかかったデータセットの数です。（※ 以下、ヒット数は 2023.6.15 現在の値）
当然、Webサイトで検索した結果も同数です。

```powershell:PowerShell 7.3
$dataset.result.count
# 2171
```

公開されているデータセットは複数のリソース（個別データ等）から成り立っています。
検索結果はデータセットの表題以外に、リソースの名前や説明文などにもヒットしているので、その数は大きくなります。

なお、ヒット数に関係なく、`Invoke-WebRequest`が返すデータセットは「関連性」順の上位10セット（既定値：変更可能）です。

```powershell:PowerShell 7.3
$dataset.result.results | % title
# 人口動態調査_人口動態統計_確定数_総覧_年次_2012年
# 人口動態調査_人口動態統計_確定数_総覧_年次_2013年
# 人口動態調査_人口動態統計_確定数_総覧_年次_2011年
# 人口動態調査_人口動態統計_確定数_総覧_年次_2019年
# 人口動態調査_人口動態統計_確定数_総覧_年次_2018年
# 人口動態調査_人口動態統計_確定数_総覧_年次_2016年
# 人口動態調査_人口動態統計_確定数_総覧_年次_2017年
# 人口動態調査_人口動態統計_確定数_死亡_年次_2016年
# 人口動態調査_人口動態統計_確定数_用語及び比率の解説_年次_2020年
# 人口動態調査_人口動態統計_確定数_用語及び比率の解説_年次_2019年
```

雰囲気がわかったので、次の条件でクエリを書き直し、再度検索してみます。
　<font color=blue>● 表題に「人口動態」と「総覧」を含む
　● 結果を表題で降順ソートする
　● ソートされたデータセットの先頭から最大20セットのデータを取得する<font>

各パラメータのフィールドや値はWebサイトで検索した際のアドレスバーから推測して動作確認しています。

```powershell:PowerShell 7.3
$query = 'q=title:人口動態+title:総覧&sort=title_string desc&start=0&rows=20'
$dataset = Invoke-WebRequest "$egovApi$query" | ConvertFrom-Json
$dataset.result.count
# 16
```

ヒット数は16まで絞られました。
20セットに収まったので、全表題を書き出してみます。

```powershell:PowerShell 7.3
$dataset.result.results | % title
# 人口動態調査_人口動態統計_確定数_総覧_年次_2021年
# 人口動態調査_人口動態統計_確定数_総覧_年次_2020年
# 人口動態調査_人口動態統計_確定数_総覧_年次_2019年
# 人口動態調査_人口動態統計_確定数_総覧_年次_2019年
# 人口動態調査_人口動態統計_確定数_総覧_年次_2018年
# 人口動態調査_人口動態統計_確定数_総覧_年次_2018年
# 人口動態調査_人口動態統計_確定数_総覧_年次_2017年
# 人口動態調査_人口動態統計_確定数_総覧_年次_2017年
# 人口動態調査_人口動態統計_確定数_総覧_年次_2016年
# 人口動態調査_人口動態統計_確定数_総覧_年次_2016年
# 人口動態調査_人口動態統計_確定数_総覧_年次_2015年
# 人口動態調査_人口動態統計_確定数_総覧_年次_2014年
# 人口動態調査_人口動態統計_確定数_総覧_年次_2014年
# 人口動態調査_人口動態統計_確定数_総覧_年次_2013年
# 人口動態調査_人口動態統計_確定数_総覧_年次_2012年
# 人口動態調査_人口動態統計_確定数_総覧_年次_2011年
```

何故か同じ表題のデータセットが複数あります。
Webサイトで同条件を検索すると（下のリンクカード）残念な事実が判明します。

https://data.e-gov.go.jp/data/ja/dataset/?q=title:%E4%BA%BA%E5%8F%A3%E5%8B%95%E6%85%8B+title:%E7%B7%8F%E8%A6%A7&sort=title_string+desc-20

データセットをフォーマットで分割した年としない年もあれば、 <i>XLS</i> と <i>XLSX</i> も混在しています。😩

## リソースの取得
気を取り直してリソースの取得まで頑張ります。
### 例１ Excelファイルの場合
幸い、クエリにはフォーマットも指示できます。
Excel データを検索する場合は`q`パラメータに`res_format:XLS*`を追加します。
`XLS`は大文字です。また、`res_format:XLS`では <i>XLSX</i> にヒットしません。

```powershell:PowerShell 7.3
$query = 'q=title:人口動態+title:総覧+res_format:XLS*&sort=title_string desc&start=0&rows=20'
$dataset = Invoke-WebRequest "$egovApi$query" | ConvertFrom-Json
$dataset.result.results | % title
# 人口動態調査_人口動態統計_確定数_総覧_年次_2019年
# 人口動態調査_人口動態統計_確定数_総覧_年次_2018年
# 人口動態調査_人口動態統計_確定数_総覧_年次_2017年
# 人口動態調査_人口動態統計_確定数_総覧_年次_2016年
# 人口動態調査_人口動態統計_確定数_総覧_年次_2015年
# 人口動態調査_人口動態統計_確定数_総覧_年次_2014年
# 人口動態調査_人口動態統計_確定数_総覧_年次_2013年
# 人口動態調査_人口動態統計_確定数_総覧_年次_2012年
# 人口動態調査_人口動態統計_確定数_総覧_年次_2011年
```

検索結果中、フォーマットが混在していた2015年のデータセットを例にしてExcelファイルを探します。

```powershell:PowerShell 7.3
$dataset.result.results[4].resources | ? format -match XLS | Format-Table position, name, format
#
# position name                              format
# -------- ----                              ------
#        6 上巻_3-4_世界各国における人口動態 XLSX
```

リソースの位置や見出しがわかったのでファイルを保存します。
先にデータのアドレスを取得します。

```powershell:PowerShell 7.3
$url = $dataset.result.results[4].resources[6].url
```

ユーザーの環境（拡張子の関連づけ）にもよりますが、通常は次の1行でブラウザが起動し、ファイルのダウンロードまで完了します。

```powershell:PowerShell 7.3
Start-Process $url
```

任意の場所や名前で保存したい場合は`Invoke-WebRequest`に`-OutFile`オプションをつけて保存します。

```powershell:PowerShell 7.3
# 例
$filename = "$($dataset.result.results[4].resources[6].name)2015.xlsx"
Invoke-WebRequest $url -OutFile $filename

# 保存されたか確認
Get-ChildItem $filename | Format-Table Name, LastWriteTime, Length
#
# Name                                       LastWriteTime      Length
# ----                                       -------------      ------
# 上巻_3-4_世界各国における人口動態2015.xlsx 2023/06/15 5:55:00  33370
```

### 例２ csvファイルの場合
ファイルの保存は、Excelの場合と同様ですので省略します。
ここではcsvの直接処理を試みます。

先の検索結果から、最新になる2021年のデータを題材にします。

```powershell:PowerShell 7.3
$query = 'q=title:人口動態+title:総覧+title:2021'
$dataset = Invoke-WebRequest "$egovApi$query" | ConvertFrom-Json
$dataset.result.count
# 1
$dataset.result.results[0].num_resources
# 54
```

ヒットしたデータセットは想定どおり1つで、そのリソース数は54です。

少し多いのでリソースの先頭10項目を表示してみます。

```powershell:PowerShell 7.3
$dataset.result.results[0].resources[0..9] | Format-Table position, name, format
#
# position name                                                                             format
# -------- ----                                                                             ------
#        0 上巻_3-1-1_人口動態総覧及び平均発生間隔 －対前年比較－                           CSV
#        1 上巻_3-1-2_本報告において別掲とした件数                                          CSV
#        2 上巻_3-2-1_年次別にみた人口動態総覧                                              CSV
#        3 上巻_3-2-2_年次別にみた人口動態総覧（率）                                        CSV
#        4 上巻_3-3-1_都道府県（特別区－指定都市再掲）別にみた人口動態総覧                  CSV
#        5 上巻_3-3-2_都道府県（特別区－指定都市再掲）別にみた人口動態総覧（率）            CSV
#        6 中巻_1_人口動態総覧，都道府県（特別区－指定都市再掲）別                          CSV
#        7 中巻_2_人口動態総覧（件数），都道府県・市部－郡部－保健所－市区町村別_01　北海道 CSV
#        8 中巻_2_人口動態総覧（件数），都道府県・市部－郡部－保健所－市区町村別_02　青森県 CSV
#        9 中巻_2_人口動態総覧（件数），都道府県・市部－郡部－保健所－市区町村別_03　岩手県 CSV
```

残りは都道府県別データと推測できます。
新潟県のデータを探してみます。

```powershell:PowerShell 7.3
$dataset.result.results[0].resources | ? name -Match 新潟県 | Format-Table position, name, format
#
# position name                                                                             format
# -------- ----                                                                             ------
#       21 中巻_2_人口動態総覧（件数），都道府県・市部－郡部－保健所－市区町村別_15　新潟県 CSV
```

上記リソースのurlからデータを読み込みます。

```powershell:PowerShell 7.3
$url = $dataset.result.results[0].resources[21].url
$response = Invoke-WebRequest $url
```

csvテキストと想定されるデータについて最初の100文字を表示します。

```powershell:PowerShell 7.3
$response.Content.Substring(0, 99)
# �ߘa�R�N,�l�����ԓ��v,,
# �����@�����@��Q�\�i15�V�����j�@�l�����ԑ����i�����j�C�s���{���E�s���|�S���|�ی
```

お察しのとおり、シフトJISが原因で文字化けしていますが、化け方に違和感があります。
この手の文字化け対策は過去にも記事が投稿されていますが、この方法が通用しません。

https://qiita.com/zaki-lknr/items/1ae3258d7b77c5e2a2ba

違和感の原因はこれです。

```powershell:PowerShell 7.3
$response.Headers.'Content-Type'
# text/csv; charset=UTF-8
```

あろうことか、シフトJISのバイト列をUTF-8でデコードするように指示されています。😩😩
`Invoke-WebRequest`は、指示どおりシフトJISのバイト列をUTF-8として解釈しようとするので、2バイト文字の大半は'�'`U+FFFD<REPLACEMENT CHARACTER>`に置き換えられています。

つまり`$response.Content`で取得した文字列から、元のシフトJISバイト列への復元は不可能です。

なお、上記の記事のように、`charset`がISO-8859-1(未指定時の既定文字コード)であれば、1バイト文字コードなのでバイト列の復元は可能です。

ここでは、`System.Net.Http.HttpClient`を使うことにします。
本体をバイト列で受け取り、シフトJISでデコードします。

```powershell:PowerShell 7.3
$httpClient = New-Object System.Net.Http.HttpClient
$dataBin = $httpClient.GetByteArrayAsync($url).GetAwaiter().GetResult()
$encSJIS = [System.Text.Encoding]::GetEncoding(932)
$dataCsv = $encSJIS.GetString($dataBin)
```

先頭の10行を表示してみます。

```powershell:PowerShell 7.3
($dataCsv -split "`r`n")[0..9]
# 令和３年,人口動態統計,,
# 中巻　総覧　第２表（15新潟県）　人口動態総覧（件数），都道府県・市部－郡部－保健所－市区町村別
# ,出生数,（再掲）,死亡数,（再掲）, ,死産数, ,周産期死亡, , ,婚姻件数,離婚件数
# ,　　　,2500g未満,　　　,乳児死亡数,新生児,自然死産,人工死産,総　数,22週以後,早期新生児,　　　,　　　
# ,,,,,死亡数,,,,の死産数,死亡数,,
# 15 新潟県　　,12608,1114,30990,21,12,138,124,59,47,12,7088,2617
# 　 新潟市　　,5132,472,9595,7,4,50,52,24,20,4,2906,1023
# 　 その他の市,7131,627,20111,14,8,87,70,35,27,8,3980,1511
# 　 郡　部　　,345,15,1284,-,-,1,2,-,-,-,202,83
# 1501新潟市　　　　　　　,5132,472,9595,7,4,50,52,24,20,4,...,...
```

データを正しく取得できました。
csvデータとしてはかなり汚い（おそらく、Excelの表をそのままcsvで保存した？）ものですが、とりあえず文字列を取得できればPowerShellで加工できます。

なお、現在は非推奨とされていますが`System.Net.WebClient`でもデータを読むことができます。

```powershell:PowerShell 7.3
$webClient = New-Object System.Net.WebClient
$webClient.Encoding = $encSJIS
$dataCsv = $webClient.DownloadString($url)
```

### 例３ csvファイルで「正統派？」文字化けの例
もう一例、「国民の祝日」を取り上げますが、こちらは正統派の文字化けです。

```powershell:PowerShell 7.3
$query = 'q=title:祝日'
$dataset = Invoke-WebRequest "$egovApi$query" | ConvertFrom-Json
$dataset.result.count
# 1
$dataset.result.results[0].num_resources
# 1
```

ヒットしたデータセットは１つで、そのリソースも１つです。

```powershell:PowerShell 7.3
$dataset.result.results[0].resources[0] | Format-Table name, format
#
# name                                                                                             format
# ----                                                                                             ------
# 昭和30年（1955年）から令和2年（2020年）国民の祝日等（いわゆる振替休日等を含む）（csv形式：19KB） CSV
```

目的のデータで間違いありません。

文字化けを見るために先頭100文字を表示します。

```powershell:PowerShell 7.3
$url = $dataset.result.results[0].resources[0].url
$response = Invoke-WebRequest $url
$response.Content.Substring(0, 99)
# ¯ÌjúExúú,¯ÌjúExú¼Ì
# 1955/1/1,³ú
# 1955/1/15,Ìú
# 1955/3/21,tªÌú
# 1
```

前の例と文字化けの質が違います。「正統派」の文字化けです。
charsetは未指定なので、ISO-8859-1(Latin-1)が適用されています。

```powershell:PowerShell 7.3
$response.Headers.'Content-Type'
# text/csv
$response.Encoding.WebName
# iso-8859-1
```

あらためて`$response.Content`を見ると、２バイト文字がISO-8859-1の上位領域に割り振られている文字に化けています。

復元は前例のように`HttpClient`を使うこともできますが、こちらは先ほど参照した記事の手法を検証します。

```powershell:PowerShell 7.3
$enc8859 = [System.Text.Encoding]::Latin1
$dataBin = $enc8859.GetBytes($response.Content)
$dataCsv = $encSJIS.GetString($dataBin)
```

先頭10行を見ます。

```powershell:PowerShell 7.3
($dataCsv -split "`r`n")[0..9]
# 国民の祝日・休日月日,国民の祝日・休日名称
# 1955/1/1,元日
# 1955/1/15,成人の日
# 1955/3/21,春分の日
# 1955/4/29,天皇誕生日
# 1955/5/3,憲法記念日
# 1955/5/5,こどもの日
# 1955/9/24,秋分の日
# 1955/11/3,文化の日
# 1955/11/23,勤労感謝の日
```

しっかり復元できています。

データのフォーマットも世間の荒波に揉まれて前例より行儀のよいcsvになっています。
その推移についてはこちらにまとめられています。 → [「祝日オープンデータを巡って」](https://okumuralab.org/~okumura/stat/holidays.html)

ついでにデータをいじってみます。
今年度（2023.4 〜 2024.3）の祝日一覧を抽出し、クリップボードにコピーします。

```powershell:PowerShell 7.3
-split $dataCsv -match '2023|2024/[1-3]/' -notmatch '2023/[1-3]/' | % replace ',' `t | Set-Clipboard
```

タブ区切りにしたのでスプレッドシートにそのままペーストできます。
<img src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209885/e8f36053-614d-fe1a-b743-690bf5da9432.png width=320px>

まだまだ整備が不十分なe-Govデータポータルですが、データ処理の練習材料にできそうです。