QiitaのSelenium全記事からワードクラウドを作る

# はじめに

@kiyokiyo_kzsby さんの素敵な記事を拝見し、ワードクラウドというものを作ってみたくなりました。
題材としては、Qiitaに投稿されたSeleniumタグの付いた記事のタイトルを利用します。
全期間だけでなく、1年毎で作成した際に何か傾向があるのか見てみます。


https://qiita.com/kiyokiyo_kzsby/items/554c571404f3f9844aaf

# 完成形
記事作成時点(2021/06/19)のSeleniumタグの全1,859記事のタイトルから作成すると次のようなワードクラウドが出来ました。

![selenium_wordcloud_2012-2021.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1423540/0f3211bd-0765-5a9c-eda3-d2433bf8e7fe.png)

以下は、2014年と2020年のワードクラウドを並べたものです。

![2014vs2020.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1423540/7df36ee5-f688-1cf0-b00e-603bb260a526.png)

# 環境
- Google Colaboratory（Google Colab）

作成環境としては、Google Colabを利用して、Webブラウザ上で作業しました。

https://colab.research.google.com/notebooks/welcome.ipynb?hl=ja

# 必要ライブラリインストール

```python
!apt update
!apt install fonts-ipafont fonts-ipaexfont
!pip install mecab-python3 unidic-lite
```

[mecab-python3](https://github.com/SamuraiT/mecab-python3)のREADMEに従って、MeCabのPythonライブラリと辞書、日本語フォントをインストールしました。
[wordcloud](https://github.com/amueller/word_cloud)とグラフ描画ライブラリの[matplotlib](https://matplotlib.org/)は既にインストールされていました。

## MeCab動作確認
MeCabが使えることを次のコマンドで確認します。

```python
import MeCab
print(MeCab.Tagger().parse("すもももももももものうち"))
```

```text
すもも	スモモ	スモモ	李	名詞-普通名詞-一般			0
も	モ	モ	も	助詞-係助詞			
もも	モモ	モモ	桃	名詞-普通名詞-一般			0
も	モ	モ	も	助詞-係助詞			
もも	モモ	モモ	桃	名詞-普通名詞-一般			0
の	ノ	ノ	の	助詞-格助詞			
うち	ウチ	ウチ	内	名詞-普通名詞-副詞可能			0
EOS
```

# 記事タイトルの収集
[Qiita API v2](https://qiita.com/api/v2/docs)を利用して記事を取得し、各記事のタイトル部分を集めます。

```python
import urllib.request
import json

def get_article_titles(token = None, query = None):
    text = ""
    page_num = 0
    while page_num < 100:
        page_num += 1
        # クエリパラメータの準備
        params = {
            'page': str(page_num),
            'per_page': '100'
        }
        if (query is not None):
            params['query'] = query
        # アクセストークンが指定された場合に付与
        req_headers = {}
        if (token is not None):
            req_headers = {
                'Authorization': 'Bearer ' + token
            }

        url = "https://qiita.com/api/v2/items?" + urllib.parse.urlencode(params)

        req = urllib.request.Request(url, headers=req_headers)
        with urllib.request.urlopen(req) as res:
            body = json.load(res)
            for article in body:
                # print("Title: " + article["title"])
                text += article["title"] + " "
            print("Page: " + str(page_num))
            res_headers = res.info()
            # 最後のページまで取得したかを判定
            if page_num >= (int(res_headers['Total-Count']) + 99 ) // 100:
                print('# of articles: ', res_headers['Total-Count'])
                break
        
    return text
```

- 関数の引数としては、Qiita APIのアクセストークンと検索クエリを指定可能なようにしました(省略可)。
- Qiita API v2の[ドキュメント](https://qiita.com/api/v2/docs#%E3%83%9A%E3%83%BC%E3%82%B8%E3%83%8D%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3)に`Total-Count`というHTTPレスポンスヘッダに要素数(記事数)が含まれていると記載されているので、これを使って何ページ分取得するかを判断しています。

上記を使って、Seleniumタグの付いた2021年に投稿された記事のタイトルを取得し、その最初の部分を表示すると、次のようになりました。

```python
text = get_article_titles(token = 'YOUR ACCESS TOKEN', query = 'tag:Selenium created:2021')
print(text[0:100])
```

```text
Page: 1
Page: 2
# of articles:  184
Python+Seleniumを用いたGoogle検索結果のCSV保存方法 メモ youtubeのURLから字幕を生成 karateでWebUIのテストをする。seleniumとのソース比較も ホーム
```

# 形態素解析
形態素解析の部分については、[参考記事](https://qiita.com/kiyokiyo_kzsby/items/554c571404f3f9844aaf#%E5%BD%A2%E6%85%8B%E7%B4%A0%E8%A7%A3%E6%9E%90)のコードを参考にさせていただきました。


```python
import MeCab

def mecab_analysis(text):
    print("start mecab analysis")
    tagger = MeCab.Tagger()
    # tagger.parse("")
    node = tagger.parseToNode(text)
    output = []
    while(node):
        if node.surface != "":
            word_type = node.feature.split(",")[0]
            if word_type not in ["助詞","助動詞","副詞","連体詞","記号"]:
                output.append(node.surface)
        node = node.next
    print("end mecab analysis")
    return output
```

# ワードクラウド出力
ワードクラウド出力部分についても、[参考記事](https://qiita.com/kiyokiyo_kzsby/items/554c571404f3f9844aaf#%E3%83%AF%E3%83%BC%E3%83%89%E3%82%AF%E3%83%A9%E3%82%A6%E3%83%89%E3%81%AE%E4%BD%9C%E6%88%90」)のコードを拝借しました。
ストップワードの部分は、関数の引数として指定できるようにしています。

```python
import matplotlib.pyplot as plt
from wordcloud import WordCloud

def create_wordcloud(text, stop_words = []):
    print("start create wordcloud")

    # フォントのパスを指定
    fpath = "/usr/share/fonts/truetype/fonts-japanese-mincho.ttf"

    wordcloud = WordCloud(background_color="white",font_path=fpath, width=1024, height=768, \
                          stopwords=set(stop_words)).generate(text)
    print("end create wordcloud")

    print("now showing")
    plt.figure(figsize=(15,12))
    plt.imshow(wordcloud)
    plt.axis("off")
    plt.show()
```

# Selenium記事の全期間のワードクラウド作成
ここまでのコードを使って、全期間のワードクラウドを作成します。

```python
stop_words = ['Selenium', u'する', u'ため', u'でき', u'使っ', u'いる', u'とき', u'みる', u'作っ', u'よう', u'こと', u'なっ', u'せる', u'れる']
text = get_article_titles(token = 'YOUR ACCESS TOKEN', query = 'tag:Selenium')
wordlist = mecab_analysis(text)
create_wordcloud(" ".join(wordlist), stop_words = stop_words)
```

![selenium_wordcloud_2012-2021.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1423540/0f3211bd-0765-5a9c-eda3-d2433bf8e7fe.png)


# 期間別
次に、2014年〜2021年について、1年毎に区切ってワードクラウドを作成します。
2011〜2013年も投稿がありますが、記事数が少ないため割愛します。
(2011年: 2記事、2012年: 9記事、2013年: 20記事)

```python
stop_words = ['Selenium', u'する', u'ため', u'でき', u'使っ', u'いる', u'とき', u'みる', u'作っ', u'よう', u'こと', u'なっ', u'せる', u'れる']
for year in ['2021', '2020', '2019', '2018', '2017', '2016', '2015', '2014']:
    print('◆', year, '年', '◆')
    text = get_article_titles(token = 'YOUR ACCESS TOKEN', query = 'tag:Selenium created:' + year)
    wordlist = mecab_analysis(text)
    create_wordcloud(" ".join(wordlist), stop_words = stop_words)
```

## 2021年
- 記事数: 184記事(2021/06/19 時点)
- 記事一覧: https://qiita.com/search?q=tag%3ASelenium+created%3A2021&sort=created

![selenium_wordcloud_2021.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1423540/9381bf55-3c71-e30f-44fa-6adbc1f8cb0e.png)

## 2020年
- 記事数: 381記事
- 記事一覧: https://qiita.com/search?q=tag%3ASelenium+created%3A2020&sort=created

![selenium_wordcloud_2020.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1423540/811ec37b-61c9-4bec-d88c-665530b9b54d.png)

## 2019年
- 記事数: 388記事
- 記事一覧: https://qiita.com/search?q=tag%3ASelenium+created%3A2019&sort=created

![selenium_wordcloud_2019.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1423540/d1485ecb-5c17-2f7e-7be0-840562be1912.png)

## 2018年
- 記事数: 283記事
- 記事一覧: https://qiita.com/search?q=tag%3ASelenium+created%3A2018&sort=created

![selenium_wordcloud_2018.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1423540/415ca070-4cc0-1676-be91-679f8c3658d0.png)

## 2017年
- 記事数: 220記事
- 記事一覧: https://qiita.com/search?q=tag%3ASelenium+created%3A2017&sort=created

![selenium_wordcloud_2017.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1423540/0396c98c-cbcd-11d4-4634-45c7afeb6d8c.png)

## 2016年
- 記事数: 148記事
- 記事一覧: https://qiita.com/search?q=tag%3ASelenium+created%3A2016&sort=created

![selenium_wordcloud_2016.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1423540/6a550279-2d88-08ea-1e8e-3cd02edda1d7.png)

## 2015年
- 記事数: 133記事
- 記事一覧: https://qiita.com/search?q=tag%3ASelenium+created%3A2015&sort=created

![selenium_wordcloud_2015.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1423540/c3ca79ed-11e7-b10a-93b1-447289015ff5.png)

## 2014年
- 記事数: 90記事
- 記事一覧: https://qiita.com/search?q=tag%3ASelenium+created%3A2014&sort=created

![selenium_wordcloud_2014.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1423540/089d161e-650c-e579-2c98-58bc4fc26a57.png)

# 考察
考察と言えるほどのものではなく、できたものを眺めた時の印象レベルですが、以下のようなことが言えるのかなと思います。

- 2018年頃までは「**テスト**」というキーワードが大きく出力されているが、それ以降は「**スクレイピング**」というキーワードの方が目立つように見える。
- 「**Python**」というキーワードが大きく表現されているので、Qiitaの記事上はPythonからSeleniumを利用してることが多そう。
- 2014年時点では、「**Firefox**」が目立っていたが、そこからはあまり大きな存在感はない。2018年以降は「**Chrome**」が台頭しているように見える。2017年6月にリリースされたChrome 59で、ヘッドレスモードがサポートされたことに関係するかもしれない。
  - 参考：[ヘッドレス Chrome ことはじめ](https://developers.google.com/web/updates/2017/04/headless-chrome?hl=ja) 

![2014vs2020.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1423540/7df36ee5-f688-1cf0-b00e-603bb260a526.png)

# ipynbファイル
参考までに、今回利用したipynbファイルを記載します。

```python:wordcloud_selenium.ipynb
{
  "nbformat": 4,
  "nbformat_minor": 0,
  "metadata": {
    "colab": {
      "name": "wordcloud_selenium.ipynb",
      "provenance": [],
      "collapsed_sections": []
    },
    "kernelspec": {
      "name": "python3",
      "display_name": "Python 3"
    },
    "language_info": {
      "name": "python"
    }
  },
  "cells": [
    {
      "cell_type": "code",
      "metadata": {
        "id": "1wBwTbF0ZeAz"
      },
      "source": [
        "!apt update\n",
        "!apt install fonts-ipafont fonts-ipaexfont\n",
        "!pip install mecab-python3 unidic-lite"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "cfpYeOOf8WtZ"
      },
      "source": [
        "import MeCab\n",
        "print(MeCab.Tagger().parse(\"すもももももももものうち\"))"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "RG5aXwcoZuST"
      },
      "source": [
        "import urllib.request\n",
        "import json\n",
        "\n",
        "def get_article_titles(token = None, query = None):\n",
        "    text = \"\"\n",
        "    page_num = 0\n",
        "    while page_num < 100:\n",
        "        page_num += 1\n",
        "        # クエリパラメータの準備\n",
        "        params = {\n",
        "            'page': str(page_num),\n",
        "            'per_page': '100'\n",
        "        }\n",
        "        if (query is not None):\n",
        "            params['query'] = query\n",
        "        # アクセストークンが指定された場合に付与\n",
        "        req_headers = {}\n",
        "        if (token is not None):\n",
        "            req_headers = {\n",
        "                'Authorization': 'Bearer ' + token\n",
        "            }\n",
        "\n",
        "        url = \"https://qiita.com/api/v2/items?\" + urllib.parse.urlencode(params)\n",
        "\n",
        "        req = urllib.request.Request(url, headers=req_headers)\n",
        "        with urllib.request.urlopen(req) as res:\n",
        "            body = json.load(res)\n",
        "            for article in body:\n",
        "                # print(\"Title: \" + article[\"title\"])\n",
        "                text += article[\"title\"] + \" \"\n",
        "            print(\"Page: \" + str(page_num))\n",
        "            res_headers = res.info()\n",
        "            # 最後のページまで取得したかを判定\n",
        "            if page_num >= (int(res_headers['Total-Count']) + 99 ) // 100:\n",
        "                print('# of articles: ', res_headers['Total-Count'])\n",
        "                break\n",
        "        \n",
        "    return text"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "R-5QOn1BZvcd"
      },
      "source": [
        "text = get_article_titles(token = 'YOUR ACCESS TOKEN', query = 'tag:Selenium created:2021')\n",
        "print(text[0:100])"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "XiDk7lc8ZyiU"
      },
      "source": [
        "import MeCab\n",
        "\n",
        "def mecab_analysis(text):\n",
        "    print(\"start mecab analysis\")\n",
        "    tagger = MeCab.Tagger()\n",
        "    # tagger.parse(\"\")\n",
        "    node = tagger.parseToNode(text)\n",
        "    output = []\n",
        "    while(node):\n",
        "        if node.surface != \"\":\n",
        "            word_type = node.feature.split(\",\")[0]\n",
        "            if word_type not in [\"助詞\",\"助動詞\",\"副詞\",\"連体詞\",\"記号\"]:\n",
        "                output.append(node.surface)\n",
        "        node = node.next\n",
        "    print(\"end mecab analysis\")\n",
        "    return output"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "L5NBEBFOZ4vs"
      },
      "source": [
        "wordlist = mecab_analysis(text)\n",
        "#print(wordlist)"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "LrQaNkUYZ-oP"
      },
      "source": [
        "import matplotlib.pyplot as plt\n",
        "from wordcloud import WordCloud\n",
        "\n",
        "def create_wordcloud(text, stop_words = []):\n",
        "    print(\"start create wordcloud\")\n",
        "\n",
        "    # フォントのパスを指定\n",
        "    fpath = \"/usr/share/fonts/truetype/fonts-japanese-mincho.ttf\"\n",
        "\n",
        "    wordcloud = WordCloud(background_color=\"white\",font_path=fpath, width=1024, height=768, \\\n",
        "                          stopwords=set(stop_words)).generate(text)\n",
        "    print(\"end create wordcloud\")\n",
        "\n",
        "    print(\"now showing\")\n",
        "    plt.figure(figsize=(15,12))\n",
        "    plt.imshow(wordcloud)\n",
        "    plt.axis(\"off\")\n",
        "    plt.show()"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "ggc0WvYdg1A8"
      },
      "source": [
        "stop_words = ['Selenium', u'する', u'ため', u'でき', u'使っ', u'いる', u'とき', u'みる', u'作っ', u'よう', u'こと', u'なっ', u'せる', u'れる']\n",
        "text = get_article_titles(token = 'YOUR ACCESS TOKEN', query = 'tag:Selenium')\n",
        "wordlist = mecab_analysis(text)\n",
        "create_wordcloud(\" \".join(wordlist), stop_words = stop_words)"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "Vt9tQWASaC_a"
      },
      "source": [
        "stop_words = ['Selenium', u'する', u'ため', u'でき', u'使っ', u'いる', u'とき', u'みる', u'作っ', u'よう', u'こと', u'なっ', u'せる', u'れる']\n",
        "for year in ['2021', '2020', '2019', '2018', '2017', '2016', '2015', '2014']:\n",
        "    print('◆', year, '年', '◆')\n",
        "    text = get_article_titles(token = 'YOUR ACCESS TOKEN', query = 'tag:Selenium created:' + year)\n",
        "    wordlist = mecab_analysis(text)\n",
        "    create_wordcloud(\" \".join(wordlist), stop_words = stop_words)"
      ],
      "execution_count": null,
      "outputs": []
    }
  ]
}
```

# 参考
- [自分のQiita記事をスクレイピングしてワードクラウドを作る | Qiita](https://qiita.com/kiyokiyo_kzsby/items/554c571404f3f9844aaf)
- [mecab-python3 | GitHub](https://github.com/SamuraiT/mecab-python3)
- [Pythonでwordcloudを作ってみた。 | Qiita](https://qiita.com/sugarcoder18/items/e66f6043fc17528f81ab)
- [Qiita API v2](https://qiita.com/api/v2/docs)