軽量ETLライブラリpetlを使ってみた

Pythonの軽量ETLライブラリ[petl](https://petl.readthedocs.io/en/stable/index.html)の検証コードの紹介となります。
EDINET 書類一覧API[^1]を用いてJSONデータを取得し、加工、テーブルおよびログを行っています。

## 検証コード
~~~python:petltest.py
import logging
import logging.config
import yaml
import requests
import petl
import json
import MySQLdb
from collections import OrderedDict

logger = logging.getLogger(__name__)

def main():
    # JSONデータ取得（EDINET API）
    res = requests.get("https://disclosure.edinet-fsa.go.jp/api/v1/documents.json?date=2020-09-01&type=2")
    
    # resultsの配列を抽出
    jarray = json.loads(res.content)["results"]

    # ↓↓↓↓ ここからPETL ↓↓↓↓
    # JSON配列をPETLのfromdictsで必要なフィールドだけ抽出
    table_base = petl.fromdicts(jarray, header = ["docID", "edinetCode", "filerName", "docDescription", "xbrlFlag"])

    # 結果出力
    logger.debug(petl.look(table_base))

    # フラグxbrlFlagが1のレコードをフィルタリング
    table_filtered = petl.select(table_base, lambda rec: rec.xbrlFlag == '1')

    # 日付フィールドxbrl_dateを追加
    table_addfield = petl.addfield(table_filtered, "xbrl_date", "2020-09-01", 1)

    # フラグxbrl_flagのフィールドを削除
    table_cutout = petl.cutout(table_addfield, "xbrlFlag")

    # ヘッダー変更
    table_header = petl.setheader(table_cutout, ["doc_id", "xbrl_date", "edinet_code", "filer_name", "doc_description"])

    # DB登録
    conn = MySQLdb.connect(user='xxxx',passwd='xxxxxx', host='localhost', db='petltest')
    conn.cursor().execute('SET SQL_MODE=ANSI_QUOTES')
    petl.todb(table_header, conn, "edinet_docs")

    # 集約も！
    agglist = OrderedDict()
    agglist['cnt'] = len
    agglist['docs'] = "doc_id", petl.strjoin(', ')
    table_aggregate = petl.aggregate(table_header, "filer_name", agglist)

    # スタイル指定結果出力
    logger.debug(petl.look(table_aggregate, style='simple'))

    # 先頭から10件抽出
    table_head10 = petl.head(table_aggregate, 10)

    # petl.util.base.Record型リスト化（後にループで処理するためヘッダーを除去）
    table_records = petl.records(table_head10)
    logger.debug(table_records)

    # ループ処理
    for record in table_records:
        logger.debug("企業名:{},  docIDs:[{}],  {}件".format(record.get("filer_name"), record.get("docs"), record.get("cnt")))

if __name__ == "__main__":
    logging.config.dictConfig(yaml.load(open("logging.yaml").read(), Loader=yaml.SafeLoader))
    main()
~~~

## 結果出力（企業名は加工しています）
最初の結果出力は以下のようになります。
~~~log
2022-12-15 00:15:31,287 - __main__ - DEBUG - +------------+------------+--------------+----------------+----------+
| docID      | edinetCode | filerName    | docDescription | xbrlFlag |
+============+============+==============+================+==========+
| 'S100JMSV' | None       | None         | None           | '0'      |
+------------+------------+--------------+----------------+----------+
| 'S100JM3Z' | 'E26419'   | '株式会社xxxxxx' | '変更報告書'        | '1'      |
+------------+------------+--------------+----------------+----------+
| 'S100JK18' | None       | None         | None           | '0'      |
+------------+------------+--------------+----------------+----------+
| 'S100JMTE' | None       | None         | None           | '0'      |
+------------+------------+--------------+----------------+----------+
| 'S100JLX6' | None       | None         | None           | '0'      |
+------------+------------+--------------+----------------+----------+
...
~~~
スタイル指定結果出力は以下のようになります。
~~~log
2022-12-15 00:22:37,620 - __main__ - DEBUG - =====================================================================================================================  ===  ==============================
filer_name                                                                                                             cnt  docs
=====================================================================================================================  ===  ==============================
'ちxxxxxxxxxxxx株式会社'                                                                                                     2  'S100JKCF, S100JKBL'
'みxxxxxxxxxxxx株式会社'                                                                                                            1  'S100JMLT'
'アxxxxxxxxxxxx株式会社'                                                                                                      3  'S100JL21, S100JL1Z, S100JLYM'
'エxxxxxxxxxxxxＬｔｄ'                                                                                                          1  'S100JM9W'
'オxxxxxxxxxxxx株式会社'                                                                                                              3  'S100JMMD, S100JMME, S100JMMH'
=====================================================================================================================  ===  ==============================
...                                                                     
~~~
table_recordsのlogger出力は以下のようになります。
~~~log
2022-12-15 00:26:08,703 - __main__ - DEBUG - 企業名:ちxxxxxxxxxxxx株式会社,  docIDs:[S100JKCF, S100JKBL],  2件
2022-12-15 00:26:08,704 - __main__ - DEBUG - 企業名:みxxxxxxxxxxxx株式会社,  docIDs:[S100JMLT],  1件
2022-12-15 00:26:08,705 - __main__ - DEBUG - 企業名:アxxxxxxxxxxxx株式会社,  docIDs:[S100JL21, S100JL1Z, S100JLYM],  3件
2022-12-15 00:26:08,706 - __main__ - DEBUG - 企業名:エxxxxxxxxxxxxＬｔｄ,  docIDs:[S100JM9W],  1件
2022-12-15 00:26:08,708 - __main__ - DEBUG - 企業名:オxxxxxxxxxxxx株式会社,  docIDs:[S100JMMD, S100JMME, S100JMMH],  3件
2022-12-15 00:26:08,709 - __main__ - DEBUG - 企業名:キxxxxxxxxxxxxカンパニー,  docIDs:[S100JMBC],  1件
2022-12-15 00:26:08,711 - __main__ - DEBUG - 企業名:ケxxxxxxxxxxxxエルエルシー,  docIDs:[S100JMRU],  1件
2022-12-15 00:26:08,712 - __main__ - DEBUG - 企業名:フxxxxxxxxxxxx株式会社,  docIDs:[S100JK45, S100JK46],  2件
2022-12-15 00:26:08,713 - __main__ - DEBUG - 企業名:フxxxxxxxxxxxx有限会社,  docIDs:[S100JMKR],  1件
2022-12-15 00:26:08,714 - __main__ - DEBUG - 企業名:三xxxxxxxxxxxx,  docIDs:[S100JMCT],  1件
~~~
テーブル出力は以下のようになります。
~~~log
mysql> select * from edinet_docs limit 5;
+----+------------+----------+-------------+-----------------------------------------------------+---------------------------------------------------------------------------------------------------------------+
| id | xbrl_date  | doc_id   | edinet_code | filer_name                                          | doc_description                                                                                               |
+----+------------+----------+-------------+-----------------------------------------------------+---------------------------------------------------------------------------------------------------------------+
|  1 | 2020-09-01 | S100JM3Z | E26419      | 株式会社xxxxxxxxxxxx                                | 変更報告書                                                                                                    |
|  2 | 2020-09-01 | S100J63J | E12430      | xxxxxxxxxxxx株式会社                                | 有価証券報告書（内国投資信託受益証券）－第16期(令和1年6月1日－令和2年6月1日)                                  |
|  3 | 2020-09-01 | S100JMOQ | E32064      | xxxxxxxxxxxx有限公司                                | 変更報告書                                                                                                    |
|  4 | 2020-09-01 | S100J63F | E12430      | xxxxxxxxxxxx株式会社                                | 有価証券届出書（内国投資信託受益証券）                                                                        |
|  5 | 2020-09-01 | S100JL21 | E10677      | xxxxxxxxxxxx株式会社                                | 有価証券報告書（内国投資信託受益証券）－第5期(令和1年6月4日－令和2年6月1日)                                   |
+----+------------+----------+-------------+-----------------------------------------------------+---------------------------------------------------------------------------------------------------------------+
5 rows in set (0.00 sec)
~~~

## 登録に用いたテーブル
~~~sql
create table edinet_docs (
  id bigint not null auto_increment comment 'id',
  xbrl_date date not null,
  doc_id varchar(8) not null,
  edinet_code varchar(6),
  filer_name varchar(128),
  doc_description varchar(256),
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARACTER SET utf8 COLLATE utf8_bin;
~~~

## おわりに
評判通り、手軽にETL操作が出来る印象でした。
データが巨大な場合やパフォーマンスを求める場合は当ライブラリは不向きとの情報もあるため[^2]、パフォーマンス面での検証も必要そうです。



[^1]:[EDINET 操作ガイド等](https://disclosure.edinet-fsa.go.jp/EKW0EZ0015.html)の「EDINET_API仕様書.pdf」及び「EDINET API利用規約」を参照ください。
[^2]:https://petl.readthedocs.io/en/stable/intro.html