Taste of Tech Topics

Acroquest Technology株式会社のエンジニアが書く技術ブログ

はてなブログをPython+Elasticsearch+Kibanaで可視化してみる

こんにちは。
普段はデータ分析している新人の佐々木です。

このブログに投稿したいと思いましたが、どんなトピックで書くか悩んでいました。
そこで、この記事ではどのようなトピックが今まで投稿されているのかを見てみたいと思います。

そこで、今回はこの「Taste of Tech Topics」自体をElasticsearch+Kibanaで可視化してみました。
完成画面の一部はこのようになりました。

f:id:acro-engineer:20170715173254p:plain

対象読者ですは次を想定しています。

  1. Elasticsearchを使い始めて、もっと使いこみたい人
  2. 日本語文書の可視化をしてみたい人

可視化のための手順は次の通りです

  1. Elasticsearchの設定
  2. データの解析、投入
  3. 可視化

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種類使用しています。

  1. kuromoji_part_of_speech  助詞などを検索にヒットさせないために、特定の品詞を検索のときに除外します。
  2. ja_stop  kuromojiに登録されている日本語のストップワードを除外します。
  3. html_strip ブログのエクスポートデータはHTMLデータなので、htmlタグを除去します。
  4. 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")

データのパース

順序としては

  1. メタ情報とコンテント情報を分離
  2. メタ情報を正規表現でパースしてdocumentに追加
  3. コンテント情報からフッターを除去してdocumentに追加

となっています。

2のメタ情報のパースではあらかじめメタフィールド名を正規表現のパターンとして登録しています。
あとは1の際にCOMMENTフィールドは今回は解析対象に含まないためElasticsearchに入れていません。

データをElasticsearchに投入する

データの投入にはpythonのelasticsearchクライアントを使用しています。
pythonからElasticsearchへの投入、検索などが手軽に出来て便利なので重宝しています。
辞書型(dict)でドキュメントを定義すれば、Elasticsearch#indexで投入できます。

3.可視化

それでは、投入したデータをKibanaを使って可視化していきましょう。
まず、どのような単語がブログ本文で使われる頻度が多いのか見るために、
タグクラウドを作ってみます。

f:id:acro-engineer:20170717203244p:plain

重要そうな単語が短い単語と数字で埋もれてしまっていますね。
これではブログの内容が全然わかりません。
なのでExcludeに.|..|...|[0-9]+と指定して3文字以内の単語と
数字を取り除きます。すると

f:id:acro-engineer:20170717204057p:plain

このような画面になりました。技術用語などが出現するようになって
ブログの内容がわかるようなタグクラウドができてきましたね。

技術用語や挨拶の中に@cero_tのceroが混じっているのが面白いですね。
しかしまだ、「ちょっと」や「ください」などの単体では意味を持たない単語が気になります。

また、「アクセス」や「ファイル」など特徴的ではない単語も拾ってしまっています。
これらの単語は4文字がおおい印象です。そのため、まずは試しに、除外する単語を4文字以内にしてみます。

f:id:acro-engineer:20170717204804p:plain

すると、特徴的な言葉がタグクラウドに表されています。
Elasticsearchに関する記事が多いことが反映されていますね。
他にはAcroquestがどのような技術を扱っているか時系列で見てみたいので、
カテゴリ出現回数の時系列グラフも作ってみました。

f:id:acro-engineer:20170717205459p:plain

Elasticsearchが2015年あたりから増えた様子がわかりますね。
あとは詳細な回数を見るためのテーブルや
カテゴリの出現回数を分かりやすく表示するための棒グラフを
作成してダッシュボードにしてみました。

f:id:acro-engineer:20170717205952p:plain
f:id:acro-engineer:20170717210007p:plain

最後に

この記事ではブログのデータを取ってきてから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