はじめに
WordCloudを使って単語の出現頻度を視覚的にわかりやすく表示することができます。
こういうやつです。
多く出現するキーワードは大きく表示されます。
見てわかるようにこれは太宰治の「走れメロス」のデータを使っています。
「走れメロス」ではメロスというキーワードが多く使われていることが見てすぐにわかりますね。
まずはこのような画像をGoogle Colab
で作成して、その後はQiita API
を使って自分の記事を解析して頻出ワードの画像を作ってみたいと思います。
記事を取得
はじめに基となるデータを取得します。青空文庫さんがGithubで公開しているのでそれを使いました。
まずはサイト検索して目的の「走れメロス」のページに行きます。
該当ページを下にスクロールするとファイルのダウンロードがあるのでここからダウンロードしてもいいです。
が、Google Colab
でコマンドで取得したかったのでここから取得はせずにファイル名(リンク)の1567_ruby_4948.zip
をメモしておいて、青空文庫のGithubに行きます。
フォルダを潜っていくと見つけました!
しかしまだダウンロードはしない。(意地)
リンク先はわかったのでGoogle Colab
でこのURLからファイルを取得していきたいと思います。
!wget https://github.com/aozorabunko/aozorabunko/blob/master/cards/000035/files/1567_ruby_4948.zip
!unzip 1567_ruby_4948.zip
Google colabでは!
マークを使ってlinuxコマンドが使えます。(これがしたかった)
weget
コマンドでファイルを取得して unzip
コマンドで解凍したいと思います。
Resolving github.com (github.com)... 140.82.113.3
Connecting to github.com (github.com)|140.82.113.3|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: unspecified [text/html]
Saving to: ‘1567_ruby_4948.zip’
1567_ruby_4948.zip [ <=> ] 266.30K --.-KB/s in 0.1s
2025-04-02 12:33:41 (2.66 MB/s) - ‘1567_ruby_4948.zip’ saved [272691]
Archive: 1567_ruby_4948.zip
End-of-central-directory signature not found. Either this file is not
a zipfile, or it constitutes one disk of a multi-part archive. In the
latter case the central directory and zipfile comment will be found on
the last disk(s) of this archive.
unzip: cannot find zipfile directory in one of 1567_ruby_4948.zip or
1567_ruby_4948.zip.zip, and cannot find 1567_ruby_4948.zip.ZIP, period.
zipファイルじゃないようなエラーになりました。
原因はURLの途中にあるblob
というURLだとWebページを表示するURLなので直接ダウンロードはできないようでした。
改めてblob
の部分をraw
に変更することで無事にファイルを取得できました。
!wget https://github.com/aozorabunko/aozorabunko/raw/master/cards/000035/files/1567_ruby_4948.zip
!unzip 1567_ruby_4948.zip
# 現在のディレクトリを確認
!ls
# 出力
1567_ruby_4948.zip hashire_merosu.txt sample_data
「hashire_merosu.txt」が取得できているのを確認できました。
中身を確認してみます。
!head hashire_merosu.txt
# 出力
���ꃁ���X
���Ɏ�
-------------------------------------------------------
�y�e�L�X�g���Ɍ����L���ɂ��āz
�s�t�F���r
�i��j�גq�\�s�s���Ⴟ�ڂ����Ⴍ�t
�b�F���r�̕t��������̎n�܂����肷��L��
めちゃくちゃ文字化けしてますね。文字コードを調べる nkf
(Network Kanji Filter)というLinuxコマンドを使ってみます。
# インストール
!apt install nkf
--guess
オプションを使用すると、nkf
はファイルのエンコーディングを検出し、その結果を出力します。
!nkf --guess hashire_merosu.txt
# 出力
Shift_JIS (CRLF)
文字コードはShift_JIS
が使われていることがわかりました。
次にファイルの中身を取り出していきます。その時にShift_JIS
も指定します。
text_list = []
with open('hashire_merosu.txt', encoding='shift_jis') as f:
# readlines()で1行ずつ取り出す
text_list = f.readlines()
これでtext_list
には「走れメロス」の中身が1行ずつリスト形式で格納されているはず。
先頭の5行だけ確認してみます。
text_list[:5]
# 出力
['走れメロス\n',
'太宰治\n',
'\n',
'-------------------------------------------------------\n',
'【テキスト中に現れる記号について】\n']
# 行数を確認
len(text_list)
# 出力
105
105行入っていることがわかりました。内容あまり覚えてないですが「走れメロス」意外と短いんですね。
データの前処理
形態素解析ライブラリの「janome」をインストールします。
形態素解析とは
ざっくり言うと
- 文章を単語に区切る
- それぞれの単語が名詞、動詞、形容詞…のどれかを判断する
これらをすることです。
「私は学校へ行きます。」という文を形態素解析すると、
- 私/名詞
- は/助詞
- 学校/名詞
- へ/助詞
- 行き/動詞
- ます/助動詞
- 。/記号
となります。
!pip install janome
Tokenizerを使って形態素解析を行います。
from janome.tokenizer import Tokenizer
t = Tokenizer()
words = []
for text in text_list: # 一行ずつ取り出す
tokens = t.tokenize(text) # 取り出した行を解析する
for token in tokens: # 単語ごとに取り出す
pos = token.part_of_speech.split(',')[0] # 取り出した単語の品詞をposに入れる
if pos in '名詞': # 品詞が名詞だけ指定
words.append(token.surface) # 単語をリストに追加する
text = ' '.join(words) # リストの内容をスペース区切りで連結
# 中身を確認
print(text[100:150])
# 出力
列 始まり 特定 記号 例 疲労 困憊 こんぱい #]: 入力 者 注 主 外字 説明 傍点 位置
名詞だけ指定していても関係ないものもありますね。とりあえずこのまま次に進みます。
WordCloudで可視化
wordcloud は、デフォルトでは英語のフォントを使用するように設定されています。
日本語を表示するためのフォントをインストールします。
!apt -y install fonts-ipafont-gothic
表示させるためにWordCloud
とmatplotlib
をインポートします。
matplotlib
はグラフとか図を表示させるライブラリのイメージがありましたが、ここでもWordCloudを表示させるために使います。
from wordcloud import WordCloud
import matplotlib.pyplot as plt
次のコードでようやく表示できます。
fpath = '/usr/share/fonts/opentype/ipafont-gothic/ipagp.ttf' # フォントを指定
wordcloud = WordCloud(background_color="white", font_path=fpath, width=900, height=500).generate(text) # テキストデータからワードクラウドを生成
plt.figure(figsize=(10, 10)) # 画面の縦横の大きさ
plt.imshow(wordcloud) # 画像準備
plt.axis("off") # 軸を非表示
plt.show # 画像表示
できた!
けど「の」とかいらないです。他にも不要な言葉が結構含まれています。
不要な単語は削除したいと思います。
import re
def normalize_text(text):
# 削除する単語のリスト
words_to_remove = [
'あれ', 'うち', 'え', 'えい', 'いま', 'お',
'こと', 'こ', 'これ', 'さ', 'さま', 'しわ',
'そ', 'それ', 'た', 'たち', 'ため', 'とき',
'つもり', 'とこ', 'な', 'なん', 'の', 'ぱい',
'ほう', 'ま', 'まま', 'も', 'もの', 'や',
'よう', 'ら', 'ろ', 'ん', 'い', 'め', 'わ', 'ゆ'
]
# 正規表現パターンを生成
pattern = '|'.join(map(re.escape, words_to_remove)) # 特殊文字をエスケープ
# 正規表現で単語を削除
text = re.sub(pattern, "", text)
# 前後の空白を削除
text = text.strip()
return text
この関数を使って再度テキストデータを作成していきます
new_text_list = []
for text in text_list:
text = normalize_text(text)
new_text_list.append(text)
from janome.tokenizer import Tokenizer
t = Tokenizer()
words = []
for text in new_text_list:
tokens = t.tokenize(text)
for token in tokens:
pos = token.part_of_speech.split(',')[0]
if pos in '名詞':
words.append(token.surface)
text = ' '.join(words)
再度画像を作成してみます。
fpath = '/usr/share/fonts/opentype/ipafont-gothic/ipagp.ttf'
wordcloud = WordCloud(background_color="white", font_path=fpath, width=900, height=500).generate(text)
plt.figure(figsize=(10, 10))
plt.imshow(wordcloud)
plt.axis("off")
plt.show()
だいぶいい感じになったんじゃないでしょうか?
配置とかは実行するたびに変わるので気に入らなければ再実行してみてください。
Qiitaの記事で作成する
次はQiita APIを使って自分の記事を全て取得して、同様に解析してWordCloudの画像を表示させたいと思います。
Qiitaから投稿一覧を取得する
以下のコードで特定のユーザーの記事を全て取得することができます。今回は私自身の記事を取得していきたいと思います。
まずはマイページに行ってユーザーIDをメモしておきます。
ユーザーIDはURLのこの部分です。
次にpython
のrequests
を使って記事を取得します。
さっきメモしたユーザーIDをurlの以下の部分に入れてください。
# 使用例
http://qiita.com/api/v2/users/ユーザーID/items
以下のようにして取得できます。requests
の使い方などは今回は省きます。
import requests
url = 'http://qiita.com/api/v2/users/kojiro513/items' #
response = requests.get(url)
response.encoding = response.apparent_encoding
data = response.json()
取得した記事を一つのテキストにまとめます。
all_text = ''
for article in data:
all_text += article['body'] + '\n' # 各記事のbodyをall_textに追加。記事ごとに改行を入れる
一旦確認してみます。
# 文字数を確認
len(all_text)
# 出力
66992
現時点で20記事くらい書いていたのでそこそこの量がありました。
データ整形
すでにわかっている不要なものは削除しようと思います。
def clean_text(text):
text = re.sub(r'https?://\S+', '', text) # URLの削除
text = re.sub(r'\n', '', text) # 改行の削除
text = re.sub(r'[!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~「」…、。]', '', text) # 記号の削除
return text
cleaned_text = clean_text(all_text)
len(cleaned_text)
# 出力
60983
URLや記号を削除して6000文字ほど削減できました。
前回と同様に解析して名詞のみを取り出します。
from janome.tokenizer import Tokenizer
t = Tokenizer()
words = []
for text in text_list:
tokens = t.tokenize(cleaned_text)
for token in tokens:
pos = token.part_of_speech.split(',')[0]
if pos in '名詞':
words.append(token.surface)
text = ' '.join(words)
text[:100]
# 出力
はじめ 普段 MySQL 使用 こと の PostgreSQL 使用 際 コマンド 違い 備忘録 コマンド 違い # 前提 ローカル インストール Docker PostgreSQL コンテナ 使
不要な「こと」や「の」など削除する関数を作成してさらに整形します。
ここの単語は一度WordCoudで画像を作成して不要なものを追加していってます。
import re
def normalize_text(text):
# 削除する単語のリスト
words_to_remove = [
'あれ', 'うち', 'え', 'えい', 'いま', 'お',
'こと', 'こ', 'これ', 'さ', 'さま', 'しわ',
'そ', 'それ', 'た', 'たち', 'ため', 'とき',
'つもり', 'とこ', 'な', 'なん', 'の', 'ぱい',
'ほう', 'ま', 'まま', 'も', 'もの', 'や',
'よう', 'ら', 'ろ', 'ん', 'い', 'め', 'わ',
'ゆ', 'れ'
]
# 正規表現パターンを生成
pattern = '|'.join(map(re.escape, words_to_remove)) # 特殊文字をエスケープ
# 正規表現で単語を削除
text = re.sub(pattern, "", text)
# 前後の空白を削除
text = text.strip()
return text
text = normalize_text(text)
len(text)
# 出力
59641
さらに1000文字くらい減りました。
WordCloudで可視化
fpath = '/usr/share/fonts/opentype/ipafont-gothic/ipagp.ttf'
wordcloud = WordCloud(background_color="white", font_path=fpath, width=900, height=500).generate(text)
plt.figure(figsize=(10, 10))
plt.imshow(wordcloud)
plt.axis("off")
plt.show()
できました!ちょっと削除できていない単語もあるようですが、そこそこの出来ではないでしょうか。
もう少しデータ整形の精度を上げたほうが良さそうですが、それは今後の課題にしたいと思います。
グラフにしてみる
最後に単語の出現回数をグラフにしてみたいと思います。
matplotlib
で日本語が文字化けしないように以下をインストールします。
!pip install japanize_matplotlib
グラフ描画は以下のようにしました。
seaborn
のcountplot
というやつで回数を数えてグラフにしてくれます。
import japanize_matplotlib
import seaborn as sns
import matplotlib.pyplot as plt
import string
japanize_matplotlib.japanize() # matplotlibを日本語対応にする
# テキストの前処理を行う関数
def preprocess_text(text):
text = re.sub(r'[0-9/]', '', text) # 数字とスラッシュを削除
text = re.sub(f"[{re.escape(string.punctuation)}]", '', text) # 句読点を削除
text = text.lower() # すべて小文字に変換
return text
processed_text = preprocess_text(text) # データ整形
words = processed_text.split() # 単語のリストを作成
# 単語の出現回数をカウント(word_countsには辞書型でデータが入る)
word_counts = Counter(words)
# 上位20個の単語を抽出
top_words = [word for word, count in word_counts.most_common(20)]
plt.figure(figsize=(10, 6))
sns.countplot(y=words, order=top_words, palette="pastel") # 単語のリストwordsを渡す
plt.title("単語の出現回数")
plt.xlabel("出現回数")
plt.ylabel("単語")
plt.tight_layout()
plt.show()
こんな感じに出力されました!
さいごに
最後まで読んでいただきありがとうございます。
これが誰かの参考になれば幸いです。