アシアルブログ

アシアルの中の人が技術と想いのたけをつづるブログです

Python x 自然言語処理でアシアルブログの単語ランキングを算出する

はじめまして、江口です。

機械学習やらAIやらtensorflowやらで最近耳にすることが増えたと思うPythonについての記事です。
先日書店でも並んでいる本が殆どRやPythonばかりで、機械学習の盛り上がりが伝わってきました!
今回はネタですが、以下の技術を使って、アシアルブログの頻出単語ランキングを算出して円グラフ化してみました。

▼使用言語


Python 3.6

▼使用library


unicodedata -> 全角文字判定に使用

requests -> アシアルブログのHTML文字列取得に使用

BeautifulSoup -> スクレイピングに使用

MeCab -> 形態素解析に使用

CountVectorizer -> 単語頻出数算出に使用

matplotlib -> データ可視化(円グラフ)に使用

▼その他ツール


conda 4.3.16 ->パッケージ管理に使用
spyder 3.1.2 -> IDE
jupyter notebook 4.3.1 -> 対話式にコードを実行し、グラフ等も即時に可視確認できる

最初に結果です!

▼アシアルブログ頻出単語ランキング




・処理概要


下記ソースコードにも記載してあるのですが、アシアルブログのTOPページ(http://blog.asial.co.jp/)から「/[0-9]{4}」形式のリンクのみをスクレイピングで抽出し、ブログの詳細記事をクローリングして全てのブログ記事からBODYタグを抽出して、さらにそこから全角の一般名詞のみを抽出してます。

・考察


最終的にTF解析で上位10件を円グラフで頻出単語ランキングとして表示させています。
さすがOnsenUIの効果でしょうか。「温泉」が1位の143個でした!
2位の「アプリケーション」は汎用的すぎて弾いても良かったですね^^;
コンポーネントは最近は宇都宮さんのブログでvuejsの「コンポーネント」が結構目立ったのだと思います。
それ以降の単語は、汎用的と言いますか、あまり参考にならなそうなので、こちらも弾いていいかもですね!

ソースコード


・asialblog_ranking.pyで下記を保存
・TTF_PATHは人それぞれ場所が異なります。



#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Pythonでアシアルブログの単語ランキングを算出する
[前提]アシアルブログの記事詳細はhttp://blog.asial.co.jp/[0-9]{4}の形式になっている
1. TOPサイトから上記形式のhrefを全て抽出する
2. 1で取得したURLを全てクローリングし、BODYタグの全ての全角文字を抽出する
3. 2で抽出した全角文字を形態素解析に掛けて、一般名詞のみを抽出する
4. 3で取得した一般名詞をTF解析で単語頻出度を算出する
5. 最終的にmatplotlibで円グラフに上位10件を表示する

Created on Wed Apr 12 11:38:42 2017

@author: eguchi
"""
import unicodedata
import requests as req
from bs4 import BeautifulSoup as bs
import MeCab as mc
from sklearn.feature_extraction.text import CountVectorizer
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.font_manager
import pylab
import re

# 定数(と言って良いのかは微妙ですが)
BASE_URL = 'http://blog.asial.co.jp/'
EXCLUDE_STR_LIST = ['年月日', '年月', 'アシ', 'アシアル', 'アル', '情報', '株式会社', '全国']
TTF_PATH = '/Users/hogehoge/anaconda/lib/python3.6/site-packages/matplotlib/mpl-data/fonts/ttf/ipam.ttf'

# 1. TOPサイトからhttp://blog.asial.co.jp/[0-9]{4}の形式のhrefを全て抽出する
html = req.get(BASE_URL).text
# BeautifulSoup4でHTMLを解析
soup = bs(html, 'html.parser')
for script in soup(["script", "style"]):
    script.extract()

# 記事詳細のlinkのみを取得
blogs = []
for link in soup.select('a[href]'):
    if (re.match('^/[0-9]{4}$', link.attrs['href'])):
        blogs.append(BASE_URL + link.attrs['href'])

# 2. 1で取得したURLを全てクローリングし、BODYタグの全ての全角文字を抽出する
# 全てのblogを走査
contents_lo = ''
for blog in blogs:
    html = req.get(blog).text
    # BeautifulSoupでHTMLを解析
    soup = bs(html, 'html.parser')
    for script in soup(["script", "style"]):
        script.extract()
        # bodyコンテンツ内のinner textのみを抽出
    contents = soup.find('body').get_text()
    # extractで救いきれなかったので、unicodedata.east_asian_widthで全角かどうか判定
    for uniStr in contents:
        str_width = unicodedata.east_asian_width(uniStr)
        if str_width == 'W':
            contents_lo += uniStr

# 3. 2で抽出した全角文字を形態素解析に掛けて、一般名詞のみを抽出する
# 形態素解析にかけて、一般名詞だけを抽出
m = mc.Tagger('mecabrc')
mecab_result = m.parse(contents_lo)
info_of_words = mecab_result.split('\n')
words = []
for info in info_of_words:
    if '\t' in info:
        kind = info.split('\t')[1].split(',')[0]
        category = info.split('\t')[1].split(',')[1]
        if kind == '名詞' and category == '一般' and (info.split('\t')[0] not in EXCLUDE_STR_LIST):
            words.append(info.split('\t')[0])

# 4. 3で取得した一般名詞をTF解析で単語頻出度を算出する
# TF解析で単語数を取得する
vectorizer = CountVectorizer(min_df=1)
tf_idf_result = vectorizer.fit_transform([' '.join(words)])
# スコアの配列を取得
tf_idf_result_array = tf_idf_result.toarray()

# ラベルを取得
feature_names = vectorizer.get_feature_names()

# スコアとラベルが一対になるように辞書型に変換(ついでに小数点の第3位切り捨て)
dic = {}
for k, v in enumerate(feature_names):
    dic[v] = round(tf_idf_result_array[0][k], 3)

# 辞書型をvalueで降順sortしてlistにして返却
sorted_list = sorted(dic.items(), key=lambda x: x[1], reverse=True)
plot_value = np.zeros([10])
plot_label = [0 for i in range(10)]
i = 0
for rank in sorted_list:
    if i >= 10:
        break
    plot_value[i] = rank[1]
    plot_label[i] = rank[0] + '(' + str(rank[1]) + ')'
    i += 1

# 5. 最終的にmatplotlibで円グラフに上位10件を表示する
# マッププロット
prop = matplotlib.font_manager.FontProperties(fname=TTF_PATH, size=14)
pylab.clf()
patches, texts = plt.pie(plot_value, labels=plot_label, counterclock=False, startangle=90)
plt.axis('equal')
pylab.setp(texts, fontproperties=prop)


▼苦労したとこ


1. script.extract()で救いきれないscript


 今回は一般名詞だけ集計したかったので、スクレイピング結果からscriptタグに関する記載は除外したかったのですが、完全には救えず。。
 そのため、苦しい感じではありましたが、「全角の単語に絞ろう」という逃げに走りました。
 


unicodedata.east_asian_width(uniStr)


pythonの標準モジュール「unicodedata」にある「east_asian_width」関数を使って、対象の文字が全角かどうかを判定することで回避することにしました!
https://docs.python.jp/3/library/unicodedata.html

標準でも随分リッチなことが手軽にできて便利だな〜。。。

2. 除外対象単語



ソースコードに思いっきり「EXCLUDE_STR_LIST」って羅列しているのですが、
mecabで使う辞書をカスタマイズすればもっとスマートに管理できるのだろうなと思いました!
今回はネタということで。

3. matplotlibの日本語ラベル豆腐問題



これが地味に一番ハマりました。
matplotlibを標準デフォルトのまま使うと、日本語ttfファイルがlib下に存在しないため、
いわゆる豆腐問題「□」に陥りました。

日本語ttfを「matplotlib/mpl-data/fonts/ttf/」下にコピーして配置し、パスを



prop = matplotlib.font_manager.FontProperties(fname=TTF_PATH, size=14)
pylab.setp(texts, fontproperties=prop)


とすることで、plot時に日本語フォントを見るようにして解決しました。

4. pythonのversion


今回はlocalのmacでサクッとやってたのですが、当初考えなしにpythonいじってたら
macのデフォルトのpythonのversionが2系だとわかり、急遽環境を整えることにしました^^;

そこで今回チョイスしたのはanacondaです。

anacondaにはpythonの開発に必要なモジュールが一通り揃ってますので、楽チンでした。

anacondaをインストールすると、環境変数に自動で以下のように追記されるので、



# added by Anaconda3 4.3.1 installer
export PATH="/Users/hogehoge/anaconda/bin:$PATH"


source ~/.bash_profileでmacで扱うpythonをインストールしたanaconda下を見るようにしました。

anacondaを入れると、「Anaconda-Navigator」というアプリケーションも同胞されるため、
そちらを起動すると、以下のような管理画面が立ち上がるので、



「spyder」というアプリケーションを立ち上げますと、pythonIDE(統合開発環境)が起動します。
今回の円グラフもspyder上のconsole(IPython)で実行、表示させたものを貼り付けてます。

spyderでの作業風景はこんな感じです。



▼最後に・・・



そもそもの話ではありますが、ランキングって円グラフで見せるものなんですかね?^^;
バ〜っと作りきってから、ふと思ってしまいました。。。

Pythonには数値を簡単に扱うライブラリが豊富なので、「あ、これやってみたい」が驚くほど手軽にできてしまうのが魅力だなと感じました。

今回のネタも急に思いついたのですが、次回は機械学習、特にscikit-learn(サイキットラーン)やtensorflowあたりでネタを考えて展開してみようと思います!

拙い文章ではありましたが、読んで頂き本当にありがとうございました!

※クローリングする際は、対象のサイトでクローリングしても問題ないか確認した上で実行するようにしましょう!!