はじめに
頑張れば、何かがあるって、信じてる。nikkieです。
先日「エンジニアの登壇を応援する会」の忘年LT大会にて、週1でブログを書くブログ駆動開発を知りました。
今週のネタ「自分の今年のブログのWordCloudを作る」についてブログを書きます。
宣言:私のブログ駆動開発
ブログ駆動開発がよさそうに思ったのは、「本当に学びたいことをブログ記事にする」という点です。
2019年4月から業務で自然言語処理に取り組んでいますが、この分野のキャッチアップはまだまだと感じます。
そこで直近1クール(2020年3月末まで)は、自然言語処理のネタで毎週1本ブログを書くことにします。
目的は自然言語処理へのキャッチアップですので、イベントレポートや雑記はブログ駆動開発としてはカウントしないという制約を追加します。
[ここから本題] WordCloudを作ってみた
忘年LT大会で知った「自分のブログのWordCloud」に取り組みました。
WordCloudとは
文章中で出現頻度が高い単語を複数選び出し、その頻度に応じた大きさで図示する手法。ウェブページやブログなどに頻出する単語を自動的に並べることなどを指す。文字の大きさだけでなく、色、字体、向きに変化をつけることで、文章の内容をひと目で印象づけることができる。
デジタル大辞泉 より
1年のブログをWordCloudにすることで、どんなことについて、頻繁に書いた1年だったかが掴めると考えています。
今回は公開している記事のタイトルと本文を対象にしました
- 2019年:32件
- 2018年:78件
2019年のブログのWordCloud
出現頻度が高いものは
ですね。
「PyCon」は、PyCon JPのスタッフ活動を始めたり、海外のPyConに参加したりと、私の2019年を言い表している単語のように思います。
2018年のブログのWordCloud
出現頻度が高いものは
といったところでしょうか。
2018年はDjango Girls Tutorialの翻訳に取り組んだ年で、ブログにもコードを載せて翻訳中の気づきをアウトプットしています(postやcommentはTutorialのコードをカウントしたためと思われます)。
Dockerは2019年はめっきり登場しなくなっていますが、使わなくなったわけではありません(最近は業務の中で、コンテナ内のuserとホストマシンのuserとの重ね合わせやコンテナの/etc/hostsの書き換えでハマりました)
2018年からずっとこのブログはPythonについてアウトプットしていて、改めて「Pythonに夢中になっているんだなあ」としみじみ思いました。
どのように手を動かしたか
WordCloudは(このブログでは未アウトプットですが、)過去の #pyhack で実装したコードがあります。
#pyhack
— nikkie 技書博のPython argparse本 boothにて頒布中 (@ftnext) 2019年5月25日
先日のDjango CongressのCFPとトークの紹介の文章をそれぞれWordCloudに。(CFPが1枚め、単語が多い方)
「Django」「実務」など、CFPの中心部分をトークの紹介文章にしていたんだと実感。
自分でCFP書くときに使おう。
英語でも対応するモードだけ用意したら次のもくもくへ pic.twitter.com/UQhWflk9cS
今回は前処理に手を入れて使っています。
はてなブログの記事の取得は新規に実装しました。
ソースコードはこちら
今回の開発で学んだのは大きく2点です:
はてなブログの記事一覧のパース
長くなったのでQiitaにまとめました。
今回、XMLの解析とは別の、思ってもみないところでつまづきました。
それがdatetime
のタイムゾーン周りで発生した以下のエラーです:
TypeError: can't compare offset-naive and offset-aware datetimes
原因ですが、
- はてなブログのAtomPubから返ってきたXML中の日時(文字列)はタイムゾーンを含む形式(例:2019-10-30 11:25:23+09:00)。
fromisoformat
メソッドでdatetime
にしたところ、タイムゾーンを持ったdatetime
となった(aware)- そのため、タイムゾーンを持たない
datetime
(naive)と比較できなかった
わけです。
取得した記事は別々のファイルに分かれてフォルダに格納されます。
一方、私の実装の都合により、後続のWordCloudのスクリプトにはテキストを1ファイルで渡す必要があります。
そこでシェルからcat
で1ファイルにまとめます:cat 2019/*.txt > 2019_all_blog.txt
正規表現を使ってブログ記事から不要な部分の除去
これまでのコードでは、前処理が不十分なためにURLに使われる単語もカウントされていました(先のツイートのhttpsやcom)。
これを削除することに取り組みました。
また、はてなブログの記事では [awesome_link:embed]
といった埋め込みや [^1]
のような脚注も登場します。
この []
で囲まれた部分も削除しました。
さらに、2018年の記事を見返すと、Markdown記法ではなく見たまま記法で書いていた時期がありました。
そのためにWordCloudにdivやpxなどのHTMLタグに使われる語が登場していました。
見たまま記法で作ったHTMLからHTMLタグも削除しました。
text = fin.read() # ファイルから読み込み square_bracket_removed = re.sub(r'\[.+?\]', '', text) # はてな記法の[]を削除 uri_removed = re.sub( # リンクのURIを削除 r'https?://[\w/:%#$&?~.=+-]+', '', square_bracket_removed) # <div>や</blockquote>などのHTMLタグを削除 html_tags_removed = re.sub('</?.+?>', '', uri_removed)
このコードがどのように動くのか、理解したことを以下に書きます。
正規表現で .
は「改行以外の任意の文字」にマッチします1。
.+
は「改行以外の任意の文字を 1 回以上繰り返したもの」です。
.+?
で最小のマッチにします。
最小とはどういうことかと言うと、[^1]: mofu [hoge:title]
のような文字列を考えた時、[^1]
や [hoge:title]
にマッチするということです。
?
をつけない.+
の場合は、[^1]: mofu [hoge:title]
全体にマッチします(続く処理により、mofu の部分が捨てられてしまいます)2。
URIを表す正規表現は 正規表現サンプル集 を参考にしました。
https?
でhttpもhttpsも表せていて、うまいなと思います(?
は「直前の正規表現を 0 回か 1 回繰り返し」を表す)。
[]
は「文字の集合を指定」します。
この中では「特殊文字はその特殊な意味を失」うそうです(バックスラッシュによるエスケープが不要でした)。
HTMLタグを表す正規表現</?.+?>
は、URIの場合を真似ました。
<h2>日記の見出し</h2>
という文字列では、<h2>
と</h2>
だけを除いてほしいので、?
を使って最小マッチにしています。
こうして指定した正規表現を使い、re.sub
で空文字列に置き換えて除去します。
今回の開発で re.sub
の使い方にはだいぶ慣れました。
不要な部分を除去した後は、kz_moritaさんから教わった参考記事を真似て
- STOPWORDの追加
- 品詞の絞り込み
を試しました。
その結果が先ほどお見せした2枚のWordCloudです。
通して実行した様子
# はてなブログの記事一覧を取得するスクリプトのディレクトリに移動 cd ~/hatenablog-atompub-python source env/bin/activate python main.py nikkie-ftnext nikkie-ftnext.hatenablog.com 2019 --output output/2019 cd output cat 2019/*.txt > 2019_all_blog.txt deactivate # WordCloudのスクリプトのディレクトリに移動 cd ~/cfp_wordcloud source env/bin/activate python draw_cloud.py ~/hatenablog-atompub-python/output/2019_all_blog.txt # ブログ記事をまとめたファイルを渡す
その他のリソース
手を動かす中や動かした後に見つけたリソースを挙げます。
janomeのチュートリアルはブログ駆動開発(自然言語処理編)の1つのネタとしておきます
- Welcome to janome's documentation! (Japanese) — Janome v0.3 documentation (ja)
- 初心者向けチュートリアル&ハンズオン教材「Janome ではじめるテキストマイニング」に興味
- janomeのAnalyzerフレームワークで前処理・後処理がテンプレ化できるらしい
- kz_moritaさんのブログ word cloud で今年のブログを振り返る
- 辞書の配置やSTOPWORDの設定が参考になります
終わりに
初回のブログ駆動開発は以上です。
迫るタイムリミットで平日はプレッシャーを感じるものですね。
実装したコードにはブラッシュアップできる点がいくつか浮かぶので、Issue管理して整えていきます。
それでは、翌週のブログ駆動開発(自然言語処理編)でまたお会いしましょう。
-
正規表現については re --- 正規表現操作 — Python 3.8.1 ドキュメント を参照しています↩
-
『Pythonによるあたらしいデータ分析の教科書』5章の青空文庫のテキストの前処理で見たコードを参考にドキュメントを引きながら手を動かしました。今回ようやく意味がつかめました↩