言語処理100本ノック 2015の挑戦記録です。環境はUbuntu 16.04 LTS + Python 3.5.2 :: Anaconda 4.1.1 (64-bit)です。過去のノックの一覧はこちらからどうぞ。
第7章: データベース
artist.json.gzは,オープンな音楽データベースMusicBrainzの中で,アーティストに関するものをJSON形式に変換し,gzip形式で圧縮したファイルである.このファイルには,1アーティストに関する情報が1行にJSON形式で格納されている.JSON形式の概要は以下の通りである.
フィールド 型 内容 例 id ユニーク識別子 整数 20660 gid グローバル識別子 文字列 "ecf9f3a3-35e9-4c58-acaa-e707fba45060" name アーティスト名 文字列 "Oasis" sort_name アーティスト名(辞書順整列用) 文字列 "Oasis" area 活動場所 文字列 "United Kingdom" aliases 別名 辞書オブジェクトのリスト aliases[].name 別名 文字列 "オアシス" aliases[].sort_name 別名(整列用) 文字列 "オアシス" begin 活動開始日 辞書 begin.year 活動開始年 整数 1991 begin.month 活動開始月 整数 begin.date 活動開始日 整数 end 活動終了日 辞書 end.year 活動終了年 整数 2009 end.month 活動終了月 整数 8 end.date 活動終了日 整数 28 tags タグ 辞書オブジェクトのリスト tags[].count タグ付けされた回数 整数 1 tags[].value タグ内容 文字列 "rock" rating レーティング 辞書オブジェクト rating.count レーティングの投票数 整数 13 rating.value レーティングの値(平均値) 整数 86 artist.json.gzのデータをKey-Value-Store (KVS) およびドキュメント志向型データベースに格納・検索することを考える.KVSとしては,LevelDB,Redis,KyotoCabinet等を用いよ.ドキュメント志向型データベースとして,MongoDBを採用したが,CouchDBやRethinkDB等を用いてもよい.
###65. MongoDBの検索
MongoDBのインタラクティブシェルを用いて,"Queen"というアーティストに関する情報を取得せよ.さらに,これと同様の処理を行うプログラムを実装せよ.
####出来上がったコード:
# coding: utf-8
import json
from pymongo import MongoClient
from bson.objectid import ObjectId
def support_ObjectId(obj):
'''json.dumps()でObjectIdを処理するための関数
ObjectIdはjsonエンコードできない型なので、文字列型に変換する
戻り値:
ObjectIdから変換した文字列
'''
if isinstance(obj, ObjectId):
return str(obj) # 文字列として扱う
raise TypeError(repr(obj) + " is not JSON serializable")
# MongoDBのデータベースtestdbにコレクションartistにアクセス
client = MongoClient()
db = client.testdb
collection = db.artist
# 検索
for i, doc in enumerate(collection.find({'name': 'Queen'}), start=1):
# 整形して表示
print('{}件目の内容:\n{}'.format(i,
json.dumps(
doc, indent='\t', ensure_ascii=False,
sort_keys=True, default=support_ObjectId
)
))
####実行結果:
まず、インタラクティブシェルの実行結果です。
segavvy@ubuntu:~$ mongo
MongoDB shell version v3.4.1
connecting to: mongodb://127.0.0.1:27017
MongoDB server version: 3.4.1
Server has startup warnings:
2017-01-14T17:47:12.528+0900 I STORAGE [initandlisten]
2017-01-14T17:47:12.528+0900 I STORAGE [initandlisten] ** WARNING: Using the XFS filesystem is strongly recommended with the WiredTiger storage engine
2017-01-14T17:47:12.528+0900 I STORAGE [initandlisten] ** See http://dochub.mongodb.org/core/prodnotes-filesystem
2017-01-14T17:47:12.898+0900 I CONTROL [initandlisten]
2017-01-14T17:47:12.898+0900 I CONTROL [initandlisten] ** WARNING: Access control is not enabled for the database.
2017-01-14T17:47:12.898+0900 I CONTROL [initandlisten] ** Read and write access to data and configuration is unrestricted.
2017-01-14T17:47:12.898+0900 I CONTROL [initandlisten]
2017-01-14T17:47:12.898+0900 I CONTROL [initandlisten]
2017-01-14T17:47:12.898+0900 I CONTROL [initandlisten] ** WARNING: /sys/kernel/mm/transparent_hugepage/enabled is 'always'.
2017-01-14T17:47:12.898+0900 I CONTROL [initandlisten] ** We suggest setting it to 'never'
2017-01-14T17:47:12.898+0900 I CONTROL [initandlisten]
2017-01-14T17:47:12.898+0900 I CONTROL [initandlisten] ** WARNING: /sys/kernel/mm/transparent_hugepage/defrag is 'always'.
2017-01-14T17:47:12.898+0900 I CONTROL [initandlisten] ** We suggest setting it to 'never'
2017-01-14T17:47:12.898+0900 I CONTROL [initandlisten]
> show dbs
admin 0.000GB
local 0.000GB
testdb 0.131GB
> use testdb
switched to db testdb
> show collections
artist
> db.artist.find( { 'name': 'Queen' } );
{ "_id" : ObjectId("5879fda21d41c85827363e72"), "name" : "Queen", "id" : 701492, "type" : "Character", "tags" : [ { "value" : "kamen rider w", "count" : 1 }, { "value" : "related-akb48", "count" : 1 } ], "gid" : "420ca290-76c5-41af-999e-564d7c71f1a7", "ended" : true, "sort_name" : "Queen", "gender" : "Female", "area" : "Japan", "aliases" : [ { "name" : "Queen", "sort_name" : "Queen" } ] }
{ "_id" : ObjectId("5879fda51d41c8582737051e"), "name" : "Queen", "id" : 192, "tags" : [ { "value" : "hard rock", "count" : 2 }, { "value" : "70s", "count" : 1 }, { "value" : "queen family", "count" : 1 }, { "value" : "90s", "count" : 1 }, { "value" : "80s", "count" : 1 }, { "value" : "glam rock", "count" : 1 }, { "value" : "british", "count" : 4 }, { "value" : "english", "count" : 1 }, { "value" : "uk", "count" : 2 }, { "value" : "pop/rock", "count" : 1 }, { "value" : "pop-rock", "count" : 1 }, { "value" : "britannique", "count" : 1 }, { "value" : "classic pop and rock", "count" : 1 }, { "value" : "queen", "count" : 1 }, { "value" : "united kingdom", "count" : 1 }, { "value" : "langham 1 studio bbc", "count" : 1 }, { "value" : "kind of magic", "count" : 1 }, { "value" : "band", "count" : 1 }, { "value" : "rock", "count" : 6 }, { "value" : "platinum", "count" : 1 } ], "gid" : "0383dadf-2a4e-4d10-a46a-e9e041da8eb3", "sort_name" : "Queen", "ended" : true, "area" : "United Kingdom", "type" : "Group", "begin" : { "date" : 27, "month" : 6, "year" : 1970 }, "aliases" : [ { "name" : "女王", "sort_name" : "女王" } ], "rating" : { "value" : 92, "count" : 24 } }
{ "_id" : ObjectId("5879fdab1d41c8582738bf76"), "id" : 992994, "name" : "Queen", "gid" : "5eecaf18-02ec-47af-a4f2-7831db373419", "sort_name" : "Queen", "ended" : true }
> exit
bye
続いてプログラムの実行結果です。
1件目の内容:
{
"_id": "5879fda21d41c85827363e72",
"aliases": [
{
"name": "Queen",
"sort_name": "Queen"
}
],
"area": "Japan",
"ended": true,
"gender": "Female",
"gid": "420ca290-76c5-41af-999e-564d7c71f1a7",
"id": 701492,
"name": "Queen",
"sort_name": "Queen",
"tags": [
{
"count": 1,
"value": "kamen rider w"
},
{
"count": 1,
"value": "related-akb48"
}
],
"type": "Character"
}
2件目の内容:
{
"_id": "5879fda51d41c8582737051e",
"aliases": [
{
"name": "女王",
"sort_name": "女王"
}
],
"area": "United Kingdom",
"begin": {
"date": 27,
"month": 6,
"year": 1970
},
"ended": true,
"gid": "0383dadf-2a4e-4d10-a46a-e9e041da8eb3",
"id": 192,
"name": "Queen",
"rating": {
"count": 24,
"value": 92
},
"sort_name": "Queen",
"tags": [
{
"count": 2,
"value": "hard rock"
},
{
"count": 1,
"value": "70s"
},
{
"count": 1,
"value": "queen family"
},
{
"count": 1,
"value": "90s"
},
{
"count": 1,
"value": "80s"
},
{
"count": 1,
"value": "glam rock"
},
{
"count": 4,
"value": "british"
},
{
"count": 1,
"value": "english"
},
{
"count": 2,
"value": "uk"
},
{
"count": 1,
"value": "pop/rock"
},
{
"count": 1,
"value": "pop-rock"
},
{
"count": 1,
"value": "britannique"
},
{
"count": 1,
"value": "classic pop and rock"
},
{
"count": 1,
"value": "queen"
},
{
"count": 1,
"value": "united kingdom"
},
{
"count": 1,
"value": "langham 1 studio bbc"
},
{
"count": 1,
"value": "kind of magic"
},
{
"count": 1,
"value": "band"
},
{
"count": 6,
"value": "rock"
},
{
"count": 1,
"value": "platinum"
}
],
"type": "Group"
}
3件目の内容:
{
"_id": "5879fdab1d41c8582738bf76",
"ended": true,
"gid": "5eecaf18-02ec-47af-a4f2-7831db373419",
"id": 992994,
"name": "Queen",
"sort_name": "Queen"
}
###インタラクティブシェルでの検索
mongo
でシェルを起動して、use
でデータベースを切り替えて、db.(コレクション).find()
で検索できます。前問同様、svjunicさんがまとめられているMongoDB コマンドメモとか書きが便利です。
###プログラムでの検索
インタラクティブシェル同様、collection.find()
(最新はこちら)で検索できます。検索するとCursor
(最新)オブジェクトが返って、結果を列挙できるようになっています。
###JSON形式で整形
結果から取り出したドキュメントは辞書型になっているので、そのまま表示すると1行になって非常に見にくくなります。
{'tags': [{'count': 1, 'value': 'kamen rider w'}, {'count': 1, 'value': 'related-akb48'}], 'type': 'Character', '_id': ObjectId('5879fda21d41c85827363e72'), 'id': 701492, 'gender': 'Female', 'gid': '420ca290-76c5-41af-999e-564d7c71f1a7', 'sort_name': 'Queen', 'name': 'Queen', 'aliases': [{'sort_name': 'Queen', 'name': 'Queen'}], 'ended': True, 'area': 'Japan'}
この辞書を見やすく整形できないか考えていたところ、Hyperion13fleetさんの[python] JSONファイルのフォーマットを整えてDumpするを見つけました。辞書をjson.dumps()
でJSON形式に戻せば整形できそうです。
ところが、単純にjson.dumps(doc, indent='\t', sort_keys=True)
とかするとエラーになってしまいます。
TypeError: ObjectId('5879fda21d41c85827363e72') is not JSON serializable
これは、docで指定した辞書の中にJSON形式に変換できないObjectId
というオブジェクトがあるためです。問題20で勉強しましたが、JSON形式とPythonの辞書への変換は変換表に従って行われるので、ここにない型はエラーになってしまうのです。
でも、前問でJSON形式のデータを読み込んで辞書に変換してMongoDBに登録し、今回それをMongoDBから辞書として取り出してJSON形式に戻そうとしただけです。可逆じゃないのが不思議で最初はちょっと悩みましたが、このObjectId
は元データにはなく、MongoDBに登録する時に自動的に追加される12バイトのユニークキーであることがわかりました。ObjectId
の詳細はこちら(最新)です。
###JSON形式の変換表にない型の扱い
幸い、変換表にない型については、自分で変換関数を実装して指定することができます。それがjson.dumps()
のdefault
という引数で指定できるコールバック関数です。引数の説明はjson.dump()
と共通なのでそちらに説明があります。このdefault
の説明部分は翻訳されておらず英語のままなのですが、ちゃんと読むと使い方がわかりました。今回はsupport_ObjectId()
を作って文字列に変換しています。
なお、引数default
を使うのではなく、JSONEncoderの派生クラスを指定する方法もあるそうです。どちらもpodhmoさんのpythonでjson出力する際で対応していない型(e.g. datetime)の値を変換しながら出力したいの解説がわかりやすいです。
66本目のノックは以上です。誤りなどありましたら、ご指摘いただけますと幸いです。
実行結果には、100本ノックで用いるコーパス・データで配布されているデータの一部が含まれます。この第7章で用いているデータのライセンスはクリエイティブ・コモンズ 表示 - 非営利 - 継承 3.0 非移植(日本語訳)です。