2012年6月13日

csvkit を使ってみる

"csvkit" は CSV ファイルの処理や変換のためのユーティリティ群です。 Python で記述されており、コマンドラインスクリプトとして利用する方法と、 Python のモジュールとして利用する方法があります。

ソースコードのリポジトリとドキュメントはこちらにあります。

インストールする

まずは Python の実行環境を整えます。 easy_installpip を使えるようにして、 インストール用の引数に "csvkit" を指定するとインストールできます。

Python の実行環境が整っていない場合は、 こちらを参考にして virtualenv を使えるようにしておくと色々と便利です。

virtualenv を使える場合は、次のようにして環境を構築できます。

$ source bin/activate
$ pip install csvkit

"csvkit" をインストールすると、"csv" から始まるコマンドラインスクリプトを使えるようになります。 "virtualenv" が有効な環境では、 bin ディレクトリ (Windows の場合は Scripts フォルダ) にインストールされたスクリプトです。

$ ls bin/csv*
bin/csvclean* bin/csvgrep*  bin/csvjson*  bin/csvsort*  bin/csvstack*
bin/csvcut*   bin/csvjoin*  bin/csvlook*  bin/csvsql*   bin/csvstat*

サンプルデータを用意する

サンプルとして、日本の郵便番号データを使ってみます。

全部を処理すると時間がかかりますので、ここでは北海道の先頭10件だけにします。 また、フィールドも次の7つに限定しておきます。

  • 郵便番号(7桁) ... 半角数字
  • 都道府県名 ... 半角カタカナ
  • 市区町村名 ... 半角カタカナ
  • 町域名 ... 半角カタカナ
  • 都道府県名 ... 漢字
  • 市区町村名 ... 漢字
  • 町域名 ... 漢字

このデータにヘッダー行を付与し、次のデータを kogaki-ken-all.csv として保存します。 文字コードは Shift_JIS とします。(オリジナルデータが Shift_JIS であるため)

zipcode,prefecture_kana,city_kana,town_kana,prefecture,city,town
"0600000","ホッカイドウ","サッポロシチュウオウク","イカニケイサイガナイバアイ","北海道","札幌市中央区",下に掲載がない場合"
"0640941","ホッカイドウ","サッポロシチュウオウク","アサヒガオカ","北海道","札幌市中央区","旭ケ丘"
"0600041","ホッカイドウ","サッポロシチュウオウク","オオドオリヒガシ","北海道","札幌市中央区","大通東"
"0600042","ホッカイドウ","サッポロシチュウオウク","オオドオリニシ(1-19チョウメ)","北海道","札幌市中央区","大通西(1〜19丁目)"
"0640820","ホッカイドウ","サッポロシチュウオウク","オオドオリニシ(20-28チョウメ)","北海道","札幌市中央区","大通西(20〜28丁目)"
"0600031","ホッカイドウ","サッポロシチュウオウク","キタ1ジョウヒガシ","北海道","札幌市中央区","北一条東"
"0600001","ホッカイドウ","サッポロシチュウオウク","キタ1ジョウニシ(1-19チョウメ)","北海道","札幌市中央区","北一条西(1〜19丁目)"
"0640821","ホッカイドウ","サッポロシチュウオウク","キタ1ジョウニシ(20-28チョウメ)","北海道","札幌市中央区","北一条西(20〜28丁目)"
"0600032","ホッカイドウ","サッポロシチュウオウク","キタ2ジョウヒガシ","北海道","札幌市中央区","北二条東"
01101,"060  ","0600002","ホッカイドウ","サッポロシチュウオウク","キタ2ジョウニシ(1-19チョウメ)","北海道","札幌市中央区","北二条西(1〜 19丁目)"

CSV データを JSON に変換する

CSV データを JSON に変換するためには csvjson を使います。

$ csvjson -h | head
usage: csvjson [-h] [-d DELIMITER] [-t] [-q QUOTECHAR] [-u {0,1,2,3}] [-b]
               [-p ESCAPECHAR] [-z MAXFIELDSIZE] [-e ENCODING] [-v] [-l]
               [--zero] [-i INDENT] [-k KEY]
               [FILE]

Convert a CSV file into JSON.

positional arguments:
  FILE                  The CSV file to operate on. If omitted, will accept
                        input on STDIN.

コマンドライン引数にファイルを与えた場合はそのファイルを処理、省略された場合は標準入力から読み込みます。 前述の kogaki-ken-all.csv を引数に指定するか、標準入力として与えます。

$ csvjson kogaki-ken-all.csv
'utf8' codec can't decode byte 0xb6 in position 2: invalid start byte

エンコーディングを解釈できないためにエラーになります。 "-e" オプションで Shift_JIS を指定します。

$ csvjson -e shift_jis kogaki-ken-all.csv
[{"city": "札幌市中央区", "town_kana": "イカニケイサイガナイバアイ", "zipcode": "0600000", "prefecture_kana": "ホッカイドウ", "city_kana": "サッポロシチュウオウク", "prefecture": "北海道"}, {"city": "札幌市中央区", "town_kana": "アサヒガオカ", "zipcode": "0640941", "prefecture_kana": "ホッカイドウ", "city_kana": "サッポロシチュウオウク", "prefecture": "北海道"}, {"city": "札幌市中央区", "town_kana": "オオドオリヒガシ", "zipcode": "0600041", "prefecture_kana": "ホッカイドウ", "city_kana": "サッポロシチュウオウク", "prefecture": "北海道"}, {"city": "札幌市中央区", "town_kana": "オオドオリニシ(1-19チョウメ)", "zipcode": "0600042", "prefecture_kana": "ホッカイドウ", "city_kana": "サッポロシチュウオウク", "prefecture": "北海道"}, {"city": "札幌市中央区", "town_kana": "オオドオリニシ(20-28チョウメ)", "zipcode": "0640820", "prefecture_kana": "ホッカイドウ", "city_kana": "サッポロシチュウオウク", "prefecture": "北海道"}, {"city": "札幌市中央区", "town_kana": "キタ1ジョウヒガシ", "zipcode": "0600031", "prefecture_kana": "ホッカイドウ", "city_kana": "サッポロシチュウオウク", "prefecture": "北海道"}, {"city": "札幌市中央区", "town_kana": "キタ1ジョウニシ(1-19チョウメ)", "zipcode": "0600001", "prefecture_kana": "ホッカイドウ", "city_kana": "サッポロシチュウオウク", "prefecture": "北海道"}, {"city": "札幌市中央区", "town_kana": "キタ1ジョウニシ(20-28チョウメ)", "zipcode": "0640821", "prefecture_kana": "ホッカイドウ", "city_kana": "サッポロシチュウオウク", "prefecture": "北海道"}, {"city": "札幌市中央区", "town_kana": "キタ2ジョウヒガシ", "zipcode": "0600032", "prefecture_kana": "ホッカイドウ", "city_kana": "サッポロシチュウオウク", "prefecture": "北海道"}, {"city": "キタ2ジョウニシ(1-19チョウメ)", "town_kana": "ホッカイドウ", "zipcode": "01101", "prefecture_kana": "060  ", "city_kana": "0600002", "prefecture": "サッポロシチュウオウク"}]

確かに JSON 形式に変換されています。

csvkit の各ツールはパイプでつなげるように設計されていますので、 標準入力から CSV を読み込んで Python の JSON モジュールを使って整形してみます。

$ cat kogaki-ken-all.csv | csvjson -e shift_jis | python -m json.tool | head
[
    {
        "city": "\u672d\u5e4c\u5e02\u4e2d\u592e\u533a",
        "city_kana": "\uff7b\uff6f\uff8e\uff9f\uff9b\uff7c\uff81\uff6d\uff73\uff75\uff73\uff78",
        "prefecture": "\u5317\u6d77\u9053",
        "prefecture_kana": "\uff8e\uff6f\uff76\uff72\uff84\uff9e\uff73",
        "town_kana": "\uff72\uff76\uff86\uff79\uff72\uff7b\uff72\uff76\uff9e\uff85\uff72\uff8a\uff9e\uff71\uff72",
        "zipcode": "0600000"
    },
    {

インデントで整形したいだけならば "-i" オプションも利用できます。

$ csvjson -e shift_jis -i 2 kogaki-ken-all.csv | head
[
  {
    "city": "札幌市中央区",
    "town_kana": "イカニケイサイガナイバアイ",
    "zipcode": "0600000",
    "prefecture_kana": "ホッカイドウ",
    "city_kana": "サッポロシチュウオウク",
    "prefecture": "北海道"
  },
  {

扱うデータセットの郵便番号が一意に定める場合などは、 "-k" オプションで出力形式を変更できます。 CSV に主キーとなるカラムが存在する場合には便利です。

$ csvjson -e shift_jis -i 2 -k zipcode kogaki-ken-all.csv | head
{
  "0600001": {
    "city": "札幌市中央区",
    "town_kana": "キタ1ジョウニシ(1-19チョウメ)",
    "zipcode": "0600001",
    "prefecture_kana": "ホッカイドウ",
    "city_kana": "サッポロシチュウオウク",
    "prefecture": "北海道"
  },
  "0600000": {

CSV データをデータベースに入れる

CSV データをデータベースに入れるためには csvsql を使います。

$ csvsql -h | head
usage: csvsql [-h] [-d DELIMITER] [-t] [-q QUOTECHAR] [-u {0,1,2,3}] [-b]
              [-p ESCAPECHAR] [-z MAXFIELDSIZE] [-e ENCODING] [-v] [--zero]
              [-y SNIFFLIMIT]
              [-i {access,sybase,sqlite,informix,firebird,mysql,oracle,maxdb,postgresql,mssql}]
              [--db CONNECTION_STRING] [--insert] [--table TABLE_NAME]
              [--no-constraints] [--no-create] [--blanks]
              [FILE [FILE ...]]

Generate SQL statements for a CSV file or create execute those statements
directly on a database.

エンコーディングとファイル名を指定すると、DDL を発行してくれます。 デフォルトではファイル名の拡張子を抜いた部分がテーブル名になります。

$ csvsql -e shift_jis kogaki-ken-all.csv
CREATE TABLE "kogaki-ken-all" (
        zipcode VARCHAR(7) NOT NULL,
        prefecture_kana VARCHAR(7) NOT NULL,
        city_kana VARCHAR(12) NOT NULL,
        town_kana VARCHAR(20) NOT NULL,
        prefecture VARCHAR(12) NOT NULL,
        city VARCHAR(19) NOT NULL
);

テーブル名を指定する場合には "--table" オプションを使います。

$ csvsql -e shift_jis --table jppost kogaki-ken-all.csv
CREATE TABLE jppost (
        zipcode VARCHAR(7) NOT NULL,
        prefecture_kana VARCHAR(7) NOT NULL,
        city_kana VARCHAR(12) NOT NULL,
        town_kana VARCHAR(20) NOT NULL,
        prefecture VARCHAR(12) NOT NULL,
        city VARCHAR(19) NOT NULL
);

"-i" オプションでデータベースを切り替えると DDL も変わります。 この辺は SQLAlchemy に準拠します。

$ csvsql -e shift_jis --table jppost -i oracle kogaki-ken-all.csv
CREATE TABLE jppost (
        zipcode VARCHAR(7 CHAR) NOT NULL,
        prefecture_kana VARCHAR(7 CHAR) NOT NULL,
        city_kana VARCHAR(12 CHAR) NOT NULL,
        town_kana VARCHAR(20 CHAR) NOT NULL,
        prefecture VARCHAR(12 CHAR) NOT NULL,
        city VARCHAR(19 CHAR) NOT NULL
);

"--db" オプションに接続文字列を指定すると、実際に SQL 文を発行できます。 SQLite を使い、データベースファイル名を jppost.sqlite とする場合は次のコマンドを実行します。

$ csvsql -e shift_jis --table jppost --db sqlite:///jppost.sqlite kogaki-ken-all.csv
$ echo "select count(*) from jppost;" | sqlite3 jppost.sqlite
0

"--insert" オプションを指定するとデータを登録できます。

$ rm jppost.sqlite
$ csvsql -e shift_jis --table jppost --db sqlite:///jppost.sqlite --insert kogaki-ken-all.csv
$ echo "select count(*) from jppost;" | sqlite3 jppost.sqlite
10

CREATE 文を省略したい場合は "--no-create" オプションを指定します。 データファイルが複数の CSV ファイルに分割されている場合には便利です。 (上記のコマンド群で rm を使わないイメージ)

終わりに

csvkit をインストールしてスクリプトを使ってみました。 エンコーディングを指定できますので、日本語が含まれるデータを扱う場合にも利用できます。

コマンドラインスクリプトは統一的なオプションを受け取りますので、 ひとつの使い方を覚えれば転用は簡単だと思います。 引数にファイル名を指定するか、標準入力を渡すかは直感的に切り替えてくれます。 ドキュメントトップの "Principles" にある "Unix哲学" に沿って動作しますので、 他のツールと協調して動作させることは簡単でしょう。

csvkit はスクリプトとして使うだけでなく、Python のモジュールとしても利用できます。 簡単な使い方はテストケースにまとめられていますので、まずは一通り目を通してみると良さそうです。 たとえば、エクセルの1枚目のワークシートを CSV に変換するコードは tests/test_convert/test_xlsx.py に記述されています。

設定ファイルも含め、ソースコードの構成も分かりやすいので、 サッとプロジェクトを眺めてみるのも良いと思いました。

コメントを投稿