さて、前回に引き続き Elasticsearch でのデータ登録です。
★関連記事
Elasticsearch 6 を使ったデータ検証 その1(Dockerでコンテナの作成と確認)
Elasticsearch 6 を使ったデータ検証 その2(マッピングの登録をしてみる)
Elasticsearch 6 を使ったデータ検証 その4(チュートリアル記事とデータの検索での比較)
Elasticsearch 6 を使ったデータ検証 その5(クエリでの検索)
Elasticsearch 6 を使ったデータ検証 その6(Aggregationを使った分類・集計)
Elasticsearch 6 を使ったデータ検証 その7(Analyzerについて)
今回は大量のデータがあるのでファイルから一括登録のbulk APIで登録してみます。
bulk登録での注意点などは以下のサイトが参考になります。
★ elasticsearch のBulk APIでは改行してはならない
http://sora-sakaki.hatenablog.com/entry/2015/08/19/160956
ドキュメントにもあるようにフォーマットは一行づつ「\n」で終了してデータを登録をする形になります。
https://www.elastic.co/guide/en/elasticsearch/guide/current/bulk.html
1行目にアクション、2行目にリクエストボディを書くことで登録ができるようです。
アクションは「create」、「index」、「update」、「delete」の4つ。
createではドキュメントの新規作成。
indexではドキュメントの新規作成かアップデートになるようです。
データの作成
さて、ここは環境によって違いそうですからサクッと。
今回はpythonで変換行いましたが、あくまでサンプルということで。
本当はjqだけでやりたかったのですが、綺麗にまとまりませんでした。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
# -*- coding: utf-8 -*- import json import csv import codecs file = "restaurants.csv" class RestaurantDocument: def __init__(self, name, name_alphabet, name_kana, address, description, purpose, category, photo_count, menu_count, access_count, closed, location): self.name = name self.name_alphabet = name_alphabet self.name_kana = name_kana self.address = address self.description = description self.purpose = purpose self.category = category self.photo_count = photo_count self.menu_count = menu_count self.access_count = access_count self.closed = closed self.location = location def create_dictionay(self): return {"name": self.name, "name_alphabet": self.name_alphabet, "name_kana": self.name_kana, "address": self.address, "description": self.description, "purpose": self.purpose, "category": self.category, "photo_count": self.photo_count, "menu_count": self.menu_count, "access_count": self.access_count, "closed": self.closed, "location": self.location} def main(): write_file = codecs.open("restaurant_data.json", "w", "utf-8") # CSVファイルを読み込む. with open(file, "r") as csvFile: reader = csv.reader(csvFile) header = next(reader) for row in reader: category = str(row[16]) category = category + " " + str(row[17]) if row[17] and int(row[17]) > 0 else category category = category + " " + str(row[18]) if row[18] and int(row[18]) > 0 else category category = category + " " + str(row[19]) if row[19] and int(row[19]) > 0 else category category = category + " " + str(row[20]) if row[20] and int(row[20]) > 0 else category closed = "true" if int(row[37]) == 1 else "false" if row[23]: lat_array = str(row[23]).split(".") lat = lat_array[0] + "." + lat_array[1] + lat_array[2] + lat_array[3] if row[24]: lon_array = str(row[24]).split(".") lon = lon_array[0] + "." + lon_array[1] + lon_array[2] + lon_array[3] if row[23] and row[24]: location = lat + "," + lon else: location = "" document = RestaurantDocument(row[1], row[3], row[4], row[22], row[25], row[26], category, int(row[30]), int(row[32]), int(row[34]), closed, location) json_text = json.dumps(document.create_dictionay(), ensure_ascii=False, encoding='utf8') write_file.write('{"index":{}}' + "\n") write_file.write(json_text + "\n") main() |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
# -*- coding: utf-8 -*- import json import csv import codecs file = "ratings.csv" class RatingDocument: def __init__(self, rating_id, restaurant_id, total, food, service, atmosphere, cost_performance, title, body, purpose, created_on): self.rating_id = rating_id self.restaurant_id = restaurant_id self.total = total self.food = food self.service = service self.atmosphere = atmosphere self.cost_performance = cost_performance self.title = title self.body = body self.purpose = purpose self.created_on = created_on def create_dictionay(self): return {"rating_id": self.rating_id, "restaurant_id": self.restaurant_id, "total": self.total, "food": self.food, "service": self.service, "atmosphere": self.atmosphere, "cost_performance": self.cost_performance, "title": self.title, "body": self.body, "purpose": self.purpose, "title": self.title, "purpose": self.purpose, "created_on": self.created_on} def main(): write_file = codecs.open("rating_data.json", "w", "utf-8") # CSVファイルを読み込む. with open(file, "r") as csvFile: reader = csv.reader(csvFile) header = next(reader) for row in reader: if row[11] != "0000-00-00 00:00:00": created_on = row[11][:10] + 'T' + row[11][11:] + '+09:00' else: created_on = "2018-08-13T00:00:00+09:00" document = RatingDocument(int(row[0]), int(row[1]), int(row[3]), int(row[4]), int(row[5]), int(row[6]), int(row[7]), row[8], row[9], int(row[10]), created_on) json_text = json.dumps(document.create_dictionay(), ensure_ascii=False, encoding='utf8') write_file.write('{"index":{}}' + "\n") write_file.write(json_text + "\n") main() |
それでは実行してみます。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
$ time python csv_to_json_restaurant.py real 0m17.029s user 0m14.492s sys 0m2.354s $ time python csv_to_json_rating.py real 0m16.331s user 0m12.920s sys 0m3.260s |
そこそこの時間で変換が完了しました。
データの投入
次にデータの投入を行います。
まずはrestaurantでのデータの登録です。
上が標準出力あり、下が無しでの結果です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
$ time curl -H "Content-Type: application/json" -XPOST 'http://localhost:9200/restaurant/type/_bulk?pretty' --data-binary @restaurant_data.json > /dev/null % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 72.0M 0 0 100 72.0M 0 7353k 0:00:10 0:00:10 --:--:-- 0{ "took" : 9414, "errors" : false, "items" : [ { "index" : { "_index" : "restaurant", "_type" : "type", ・ ・ ・ 中略 real 0m31.079s user 0m2.218s sys 0m3.577s $ time curl -H "Content-Type: application/json" -XPOST 'http://localhost:9200/restaurant/type/_bulk?pretty' --data-binary @restaurant_data.json > /dev/null % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 149M 100 77.1M 100 72.0M 8231k 7683k 0:00:09 0:00:09 --:--:-- 1465k real 0m9.661s user 0m0.098s sys 0m0.261s |
ファイルからの登録件数が「214,263」件なので思ったよりもかなり早いです。
次にratingの取り込みです。
さて、ここでデータを一気に投入しようと思いましたがデータが全く入りませんでした。
どうもファイルサイズの制限(100M)を超えてしまったようです。
1 2 3 4 5 |
$ ls -lah rating_data.json restaurant_data.json -rw-r--r-- 1 foo bar 255M 8 13 17:09 rating_data.json -rw-r--r-- 1 foo bar 72M 8 13 17:09 restaurant_data.json |
★ Elasticsearch のbulk APIのMaxファイルサイズを探る
https://syossan.hateblo.jp/entry/2017/09/04/170609
検証なんでファイル制限を変更してテストしようかとも思いましたが、多分この制限を超えるようなことは実際にはしないのでsplitで分割しました。
設定の変更は以下のURLを参考に変更できます。
https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-http.html
今回利用した分割後のファイルは以下になります。
1 2 3 4 5 6 7 8 |
$ ls -lah rating_data.json* -rw-r--r-- 1 foo bar 73M 8 13 17:44 rating_data.json1 -rw-r--r-- 1 foo bar 61M 8 13 17:45 rating_data.json2 -rw-r--r-- 1 foo bar 62M 8 13 17:45 rating_data.json3 -rw-r--r-- 1 foo bar 49M 8 13 17:45 rating_data.json4 -rw-r--r-- 1 foo bar 9.9M 8 13 17:46 rating_data.json5 |
それではデータを分割したファイルのアップロードをしてみます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
$ time -p { > curl -H "Content-Type: application/json" -XPOST 'http://localhost:9200/rating/type/_bulk?pretty' --data-binary @rating_data.json1 > curl -H "Content-Type: application/json" -XPOST 'http://localhost:9200/rating/type/_bulk?pretty' --data-binary @rating_data.json2 > curl -H "Content-Type: application/json" -XPOST 'http://localhost:9200/rating/type/_bulk?pretty' --data-binary @rating_data.json3 > curl -H "Content-Type: application/json" -XPOST 'http://localhost:9200/rating/type/_bulk?pretty' --data-binary @rating_data.json4 > curl -H "Content-Type: application/json" -XPOST 'http://localhost:9200/rating/type/_bulk?pretty' --data-binary @rating_data.json5 > } real 70.97 user 2.22 sys 3.84 $ time -p { > curl -H "Content-Type: application/json" -XPOST 'http://localhost:9200/rating/type/_bulk?pretty' --data-binary @rating_data.json1 > /dev/null > curl -H "Content-Type: application/json" -XPOST 'http://localhost:9200/rating/type/_bulk?pretty' --data-binary @rating_data.json2 > /dev/null > curl -H "Content-Type: application/json" -XPOST 'http://localhost:9200/rating/type/_bulk?pretty' --data-binary @rating_data.json3 > /dev/null > curl -H "Content-Type: application/json" -XPOST 'http://localhost:9200/rating/type/_bulk?pretty' --data-binary @rating_data.json4 > /dev/null > curl -H "Content-Type: application/json" -XPOST 'http://localhost:9200/rating/type/_bulk?pretty' --data-binary @rating_data.json5 > /dev/null > } % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 90.8M 100 17.7M 100 73.0M 1281k 5264k 0:00:14 0:00:14 --:--:-- 0 % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 78.7M 100 17.8M 100 60.8M 1590k 5433k 0:00:11 0:00:11 --:--:-- 0 % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 79.4M 100 17.8M 100 61.6M 1385k 4788k 0:00:13 0:00:13 --:--:-- 0 % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 67.1M 100 17.8M 100 49.3M 2021k 5594k 0:00:09 0:00:09 --:--:-- 0 % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 11.9M 100 2130k 100 9.8M 1176k 5582k 0:00:01 0:00:01 --:--:-- 5580k real 50.02 user 0.19 sys 0.63 |
同じように上が標準出力ありで下が標準出力無しです。
ファイルのデータ件数は「205,833」件なので、速度的には予想以上には早いですが、実質はエラーを見ながらの対応が必要になります。
日付の扱い
さて、ratingの方では日付の扱いを行っています。
今回は加工の際に、ISOフォーマットに従いJSTの+09:00でデータの作成を行っています。
日付の扱いは以下のサイトが参考になりました。
★公式ドキュメント
https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-date-format.html
★Elasticsearch の日付検索を検証
http://blog.yoslab.com/entry/2014/04/24/000317
実際に少量のサンプルデータので検証ではきちんと範囲指定が動いてくれています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 |
$ curl -H "Content-Type: application/json" -XGET 'http://localhost:9200/rating/_search' -d '{ > "query": { > "range" : { > "created_on" : { > "gte" : "2005-08-16T16:18:00+09:00", "lte": "now", "format" : "date_time_no_millis" > } > } > } > }' | jq % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 3184 100 2987 100 197 117k 7931 --:--:-- --:--:-- --:--:-- 121k { "took": 14, "timed_out": false, "_shards": { "total": 5, "successful": 5, "skipped": 0, "failed": 0 }, "hits": { "total": 3, "max_score": 1, "hits": [ { "_index": "rating", "_type": "type", "_id": "YFi6MWUBqTWP-Dutq8Jm", "_score": 1, "_source": { "body": "名前は忘れましたが、札幌で食べたお店よりも、全然こっちの方が美味しかったので、載せました。お店も綺麗(新規オープン・・)でランチは結構混んでいます。個人的にはゆったりと食事できるので夜の方がオススメです。 辛さが0倍から50倍まで選べるのもGOOD!、スープも2種類みたいで、友達は黄色がオススメと言っていましたが、自分は赤の方を食べました。かなり美味しかったです。店長も好感のもてるお兄さんでした。 駅近くなので一度お試しあれです!", "atmosphere": 0, "food": 0, "restaurant_id": 310595, "created_on": "2006-10-07T05:06:09+09:00", "purpose": 0, "cost_performance": 0, "total": 5, "service": 0, "title": "", "rating_id": 156445 } }, { "_index": "rating", "_type": "type", "_id": "Yli6MWUBqTWP-Dutq8Jm", "_score": 1, "_source": { "body": "意外と昔からあるお店で 近江牛、特選黒毛和牛など高級肉を扱ってるお肉屋さん ランチも、きっとおいしいと思い駐車場もあるので行ってみました ステーキ焼ランチ1050円。とてもリーズナブル 炭火かなと思ったら、ガスでした でてきたランチのお肉は、ロース肉 サシが入ってなく、見た目オージービーフっぽい 食べてみたら、うーんこれはやっぱ。。。 外国産のお肉みたいな味がする 高くておいしいのは当たり前 1000円ランチといえども肉の質は落として欲しくないな~と思った 他にもお客さん(サラリーマン系)が来てたけど ランチでも、和牛サーロインステーキとか高い値段のを注文してました ↑ 焼き方を聞いていたので、和牛サーロインステーキ等の場合は 焼いてもらえるみたいですね ", "atmosphere": 0, "food": 0, "restaurant_id": 3334, "created_on": "2006-06-03T16:07:43+09:00", "purpose": 0, "cost_performance": 0, "total": 2, "service": 0, "title": "", "rating_id": 144379 } }, { "_index": "rating", "_type": "type", "_id": "Y1i6MWUBqTWP-Dutq8Jm", "_score": 1, "_source": { "body": "ランチがおすすめです お肉自体の旨み、そしてあまみが お口にじゅわぁ~と広がります。 あのお値段で、ここまでおいしかったお店は初めてです 駐車場は30台くらいとめられて 周りに別のお店はナイので、近くに看板もあるし場所もわかりやすい", "atmosphere": 0, "food": 0, "restaurant_id": 15163, "created_on": "2006-06-03T15:14:45+09:00", "purpose": 0, "cost_performance": 0, "total": 5, "service": 0, "title": "", "rating_id": 144377 } } ] } } |
色々と実施して見ましたがとりあえずデータ投入としてはここまでで。
次回にようやく検索を行ってみます。
★関連記事
Elasticsearch 6 を使ったデータ検証 その1(Dockerでコンテナの作成と確認)
Elasticsearch 6 を使ったデータ検証 その2(マッピングの登録をしてみる)
Elasticsearch 6 を使ったデータ検証 その4(チュートリアル記事とデータの検索での比較)
Elasticsearch 6 を使ったデータ検証 その5(クエリでの検索)
Elasticsearch 6 を使ったデータ検証 その6(Aggregationを使った分類・集計)
Elasticsearch 6 を使ったデータ検証 その7(Analyzerについて)
このブログは株式会社CoLabMixによる技術ブログです。
GCP、AWSなどでのインフラ構築・運用や、クローリング・分析・検索などを主体とした開発を行なっています。
Ruby on RailsやDjango、Pythonなどの開発依頼などお気軽にお声がけください。
開発パートナーを増やしたいという企業と積極的に繋がっていきたいです。
お問い合わせやご依頼・ご相談など