[Tips] Python で URL で指定した JSON を読み込んで日付型で絞り込む ( Pandas 使用 )

2022.04.08

Pandas を使って JSON をごにょごにょする機会があったので使った Tips をまとめておきます。

URLで取得するJSONのイメージ

下記の JSON が https://example.com/sample.json で取れるとします。

{
  "elements": [
    {
      "id": "001",
      "name": "Sample Name 1",
      "tags": [
        "sample",
        "test"
      ],
      "enabled": true,
      "registeredAt": "2022-04-01T00:00:00Z",
      "updatedAt": "2022-04-03T09:04:10Z"
    },
    {
      "id": "002",
      "name": "Sample Name 2",
      "tags": [
        "sample",
        "test"
      ],
      "enabled": true,
      "registeredAt": "2022-03-31T00:00:00Z",
      "updatedAt": "2022-04-04T12:00:00Z"
    },
    {
      "id": "003",
      "name": "Sample Name 3",
      "tags": [
        "sample",
        "test"
      ],
      "enabled": true,
      "registeredAt": "2022-04-05T00:00:00Z",
      "updatedAt": "2022-04-08T08:15:21Z"
    }
  ]
}

実装

この JSON を Python で読み込んで、登録日( registeredAt )が実行日の月初( 本記事の執筆時点では 2022/04/01 )以降の要素だけ取り出すこと考えます。 環境には aurl コマンドと jq コマンドがインストール済みであるとします。

実装コードは次の通りです。

import pandas as pd
import subprocess
import datetime
from dateutil.relativedelta import relativedelta

# 月初の日付を取得
today = datetime.date.today()
beginning_of_the_month = today + relativedelta(day=1)

# JSON の elements の配列を取得
json = subprocess.getoutput("aurl 'https://example.com/sample.json' | jq -r '.elements'")

# Pandas で絞り込んで出力
df = pd.read_json(json)
df_filtered = df[["id", "name", "tags", "enabled", "registeredAt"]].copy()
df_filtered['registeredAtDatetime'] = pd.to_datetime(df['registeredAt'])
print(df_filtered[df_filtered['registeredAtDatetime'].dt.date >= beginning_of_the_month])

実行結果は次のようになります。

   id           name            tags  enabled          registeredAt      registeredAtDatetime
0   1  Sample Name 1  [sample, test]     True  2022-04-01T00:00:00Z 2022-04-01 00:00:00+00:00
2   3  Sample Name 3  [sample, test]     True  2022-04-05T00:00:00Z 2022-04-05 00:00:00+00:00

登録日が 2022/03/31 である Sample Name 2 の要素がフィルタされて消えているのがわかります。

このコードのポイントは次の通りです。

月初の日付の取得

月初の日付は today を取得した上で、 dateutil.relativedelta を使うことで簡単に取得できます。

today = datetime.date.today()
beginning_of_the_month = today + relativedelta(day=1)

コマンドの実行結果の取得

シェルコマンドの実行結果の取得は subprocess.getoutput( "command's string" ) を使うのが簡単です。 サンプルでは aurl で JSON を取得した結果をパイプして jq に渡し、 elements 要素だけ取り出しています。

json = subprocess.getoutput("aurl 'https://example.com/sample.json' | jq -r '.elements'")

Pandas で取得するフィールドの設定

df = pd.read_json(json)

によって、 JSON から DataFrame が作成できるので、取得した DataFrame に対して、取得したいフィールド名を与えます。 また、 copy() メソッドで元の DataFrame とは別個の DataFrame を明示的に生成しています。 明示的に生成しておかないと、後続の操作で SettingWithCopyWarning が発生します。

参考) DataFrameでSettingWithCopyWarningの意味と対処法 - Qiita

df_filtered = df[["id", "name", "tags", "enabled", "registeredAt"]].copy()

日付で結果を絞り込む

registeredAt カラムの値を元に datetime64 型の値を生成し、フィールド registeredAtDatetime にセットします。 さらに、フィルター条件で df_filtered['registeredAtDatetime'].dt.date とすることで datetime.date 型に変換します。 これで、 datetime.date 型の月初の日付( beginning_of_the_month ) との比較が可能になり、目的の絞り込みが行えます。

df_filtered['registeredAtDatetime'] = pd.to_datetime(df['registeredAt'])
print(df_filtered[df_filtered['registeredAtDatetime'].dt.date >= beginning_of_the_month])

JSON 形式で結果を返す

最後の結果を JSON で返したい場合は下記のようにします。

print(df_filtered[df_filtered['registeredAtDatetime'].dt.date >= beginning_of_the_month].to_json(orient='records', indent=2))

indent を指定すると結果を整形してくれます。 結果は次のようになります。

[
  {
    "id":1,
    "name":"Sample Name 1",
    "tags":[
      "sample",
      "test"
    ],
    "enabled":true,
    "registeredAt":"2022-04-01T00:00:00Z",
    "registeredAtDatetime":1648771200000
  },
  {
    "id":3,
    "name":"Sample Name 3",
    "tags":[
      "sample",
      "test"
    ],
    "enabled":true,
    "registeredAt":"2022-04-05T00:00:00Z",
    "registeredAtDatetime":1649116800000
  }
]