こんにちは。
普段はデータ分析している新人の佐々木です。
このブログに投稿したいと思いましたが、どんなトピックで書くか悩んでいました。
そこで、この記事ではどのようなトピックが今まで投稿されているのかを見てみたいと思います。
そこで、今回はこの「Taste of Tech Topics」自体をElasticsearch+Kibanaで可視化してみました。
完成画面の一部はこのようになりました。
対象読者ですは次を想定しています。
- Elasticsearchを使い始めて、もっと使いこみたい人
- 日本語文書の可視化をしてみたい人
可視化のための手順は次の通りです
- Elasticsearchの設定
- データの解析、投入
- 可視化
1.Elasticsearchの設定
今回は次のmapping templateを作成し、適用しました。
適用方法はKibanaのDev Toolsを開き以下のリクエストを入力します。
PUT _template/blog { "template": "blog.*", "settings": { "analysis": { "tokenizer": { "kuromoji": { "type": "kuromoji_tokenizer", "mode": "search" } }, "analyzer": { "kuromoji-analyzer": { "type": "custom", "tokenizer": "kuromoji", "filter": [ "ja_stop", "kuromoji_part_of_speech", "lowercase" ], "char_filter": [ "html_strip" ] } } } }, "mappings": { "blog": { "properties": { "body": { "type": "text", "fielddata": true, "analyzer": "kuromoji-analyzer" } } } } }
Elasticsearchではドキュメントの投入時にどのようにインデクシングするかを
tokenizerとfilterの二つの設定によって決められます。
それらと各フィールドの設定を定義するMappingを上で設定しています。
それぞれの設定について説明していきます。
tokenizer
tokenizerはテキストをどのように分割するかを決定する設定です。
kuromoji_tokenizer
を使用しています。
modeをsearchに指定することによって、登録されている複合語を残しつつ、
分かち書きした単語が検索にヒットします。
filter
filterはtokenizerで分割したテキストをどのようにElasticsearch内に登録するかを決定する設定です。
filterは、次の4種類使用しています。
kuromoji_part_of_speech
助詞などを検索にヒットさせないために、特定の品詞を検索のときに除外します。ja_stop
kuromojiに登録されている日本語のストップワードを除外します。html_strip
ブログのエクスポートデータはHTMLデータなので、htmlタグを除去します。lowercase
「Elasticsearch」と「elasticsearch」のような表記ゆれがあるので、アルファベットをすべて小文字に変換します。
Mapping
mapping定義ではbody(ブログ本文)を集計できる状態にしたかったので、
fielddata
をtrueにしてあります。これでタグクラウドを単語ごとに表示できます。
2.データの解析、投入
使用したデータははてなブログをエクスポートしたテキストデータ(Movable Type)です。
エクスポート方法は以下の記事を参考にしました。
staff.hatenablog.com
形式は次のとおりです。
AUTHOR: acro-engineer TITLE: X-Pack Machine Learningが正式リリースされました BASENAME: 2017/07/08/150749 STATUS: Publish ALLOW COMMENTS: 1 CONVERT BREAKS: 0 DATE: 07/08/2017 15:07:49 CATEGORY: Machine Learning CATEGORY: Elasticsearch CATEGORY: X-Pack IMAGE: https://cdn-ak.f.st-hatena.com/images/fotolife/a/acro-engineer/20170707/20170707085241.png ----- BODY: ===============本文=============== ----- COMMENT: ===============コメント=============== --------
本当はCOMMENTフィールド以外にもEXTENDED BODYフィールドなどがあるのですが、
今回のデータの中に含まれていませんでした。詳しいフォーマットは以下に記載されています
staff.hatenablog.com
これでは、これをElasticsearchに投入するために解析していきましょう。
今回はpythonを使いました。コードは次のようになりました。
import re from elasticsearch import Elasticsearch import datetime columns = "AUTHOR|TITLE|BASENAME|STATUS|ALLOW COMMENTS|CONVERT BREAKS|DATE|CATEGORY|IMAGE" blog_host = " http://acro-engineer.hatenablog.com/entry/" template_pattern = re.compile("\<blockquote\>.+是非以下のページをご覧ください.+\<\/blockquote\>", flags=re.DOTALL) class MovableParser(object): def __init__(self): """ Elasticsearchと接続する """ self.es = Elasticsearch(hosts=["localhost:9200"], http_auth=('elastic', 'changeme')) self.document = {} self.seq = "" def parse(self): """ MovableTypeをパースする """ #meta部分とbody部分を分割する elements = self.seq.split("-----\n") meta = elements[0] body = elements[1] #meta情報のパース meta_pattern = re.compile("({0}): (.*)".format(columns),flags=(re.MULTILINE | re.DOTALL)) for metaline in meta.split("\n")[:-1]: matches = re.match(meta_pattern, metaline) if matches.group(1).lower() in self.document: print(matches) self.document[matches.group(1).lower()] += ",{0}".format(matches.group(2)) else: self.document[matches.group(1).lower()] = matches.group(2) if "category" in self.document: self.document["category"] = self.document["category"].split(",") print(self.document["category"]) body = re.sub("BODY:","",body) body = re.sub(template_pattern, "", body) self.document["body"] = body url = blog_host + self.document["basename"] self.document["source"] = url self.document["date"] = datetime.datetime.strptime(self.document["date"], "%m/%d/%Y %H:%M:%S") self.es.index(index="blog.{0}".format(datetime.date.today().strftime("%Y.%m.%d")), doc_type="blog", body=self.document) print(self.document) self.document = {} def read_file(self, filename): """ ファイルを読む。'--------'で区切ってparse()を呼び出す :param filename: データファイル名 """ with open(filename, encoding="utf-8") as f: for line in f: if line == "--------\n": self.parse() self.seq = "" else: self.seq += line if __name__ == "__main__": parser = MovableParser() parser.read_file("acro-engineer.hatenablog.com.export.txt")
3.可視化
それでは、投入したデータをKibanaを使って可視化していきましょう。
まず、どのような単語がブログ本文で使われる頻度が多いのか見るために、
タグクラウドを作ってみます。
重要そうな単語が短い単語と数字で埋もれてしまっていますね。
これではブログの内容が全然わかりません。
なのでExcludeに.|..|...|[0-9]+
と指定して3文字以内の単語と
数字を取り除きます。すると
このような画面になりました。技術用語などが出現するようになって
ブログの内容がわかるようなタグクラウドができてきましたね。
技術用語や挨拶の中に@cero_tのceroが混じっているのが面白いですね。
しかしまだ、「ちょっと」や「ください」などの単体では意味を持たない単語が気になります。
また、「アクセス」や「ファイル」など特徴的ではない単語も拾ってしまっています。
これらの単語は4文字がおおい印象です。そのため、まずは試しに、除外する単語を4文字以内にしてみます。
すると、特徴的な言葉がタグクラウドに表されています。
Elasticsearchに関する記事が多いことが反映されていますね。
他にはAcroquestがどのような技術を扱っているか時系列で見てみたいので、
カテゴリ出現回数の時系列グラフも作ってみました。
Elasticsearchが2015年あたりから増えた様子がわかりますね。
あとは詳細な回数を見るためのテーブルや
カテゴリの出現回数を分かりやすく表示するための棒グラフを
作成してダッシュボードにしてみました。
最後に
この記事ではブログのデータを取ってきてからKibanaで
可視化するところまで一通りやってみました。
ブログのデータから、トピックの傾向やキーワードが
ダッシュボードで見えるようになり、ブログの内容を
可視化することができました。
Elasticsearchの出現数が多く、現状のブログのカテゴリにも反映されていて、面白いですね!
ただ、固有の単語(x-pack,cero_tなど)が分割されてしまっています。
この点ですが、ユーザー辞書に固有の単語を登録して分割されないようにすれば、よくなると思います。
それではまたよろしくお願いします。
Acroquest Technologyでは、キャリア採用を行っています。
- ビッグデータ(Hadoop/Spark、NoSQL)、データ分析(Elasticsearch、Python関連)、Web開発(SpringCloud/SpringBoot、AngularJS)といった最新のOSSを利用する開発プロジェクトに関わりたい。
- マイクロサービス、DevOpsなどの技術を使ったり、データ分析、機械学習などのスキルを活かしたい。
- 社会貢献性の高いプロジェクトや、顧客の価値を創造するようなプロジェクトで、提案からリリースまで携わりたい。
- 書籍・雑誌等の執筆や、対外的な勉強会の開催・参加を通した技術の発信、社内勉強会での技術情報共有により、エンジニアとして成長したい。
少しでも上記に興味を持たれた方は、是非以下のページをご覧ください。Elasticsearchを仕事で使いこみたいデータ分析エンジニア募集中! - Acroquest Technology株式会社のエンジニア中途・インターンシップ・契約・委託の求人 - Wantedlywww.wantedly.com