Edited at

素人の言語処理100本ノック:65

More than 1 year has passed since last update.

言語処理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"というアーティストに関する情報を取得せよ.さらに,これと同様の処理を行うプログラムを実装せよ.



出来上がったコード:


main.py

# 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行になって非常に見にくくなります。


1件目をprint(doc)した結果

{'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)とかするとエラーになってしまいます。


json.dumps(doc)のエラー

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 非移植日本語訳)です。