Qiitaの記事データは、機械学習のためのデータセットに向いている

有効性を試してみたい手法があるけど、データがない、という場面があります。
特に、テキストマイニングに関する手法を、日本語の文書データに対して試そうとした時に多いです。

手法の検証に向いている日本語の文書データを探した所、Qiitaの記事データがテキストマイニング手法の検証に向いている良質なデータセットであることが分かりました。
また、Qiitaの記事データは、テキストマイニング以外に、時系列予測など、他の機械学習手法を試すことにも向いていると考えます。

今回の記事では、以下について説明したいと思います。
1. [Qiita記事データの収集方法](#qiitaの記事データの収集方法)
2. [Qiita記事データが良質なデータセットである11の理由](#qiita記事データが良質なデータセットである11の理由)

# Qiitaの記事データの収集方法

Qiita記事データの収集方法は、以下の記事に詳しくまとまっています。
[Qiitaの記事データをQiita API, Scrapyで収集](https://qiita.com/nkmk/items/fbac13cd05b80334eb2b)

この記事は、個人的には、もっと評価されるべきと思います。
ここでは、先行記事でまとめられている方法に従いつつ、要所だけを抽出して解説します。

### ①Qiitaに、ユーザ登録する

まずは、Qiitaにユーザ登録します。
画面右上の「ユーザ登録」から、ユーザ情報を登録しましょう。特別難しい箇所はありません。

![ユーザ登録.PNG](https://qiita-image-store.s3.amazonaws.com/0/254780/bb86433b-e16a-dc5b-910d-981f42152a58.png)

### ②「設定」→「アプリケーション」→「新しくトークンを発行する」

Qiitaの記事データを大量に取得するためには、アクセストークンの取得が必要です。
以下の手順で簡単に取得できます。

まずは「設定」を開き、

![ユーザ登録_設定.PNG](https://qiita-image-store.s3.amazonaws.com/0/254780/224855a1-20f5-a867-0498-06b9fa55c0b3.png)

次いで「アプリケーション」を開き、

![ユーザ登録_マイページ.PNG](https://qiita-image-store.s3.amazonaws.com/0/254780/285fdb09-c357-c58e-bb2e-d512c50a7ae0.png)

最後に「新しくトークンを発行する」を押します。

![ユーザ登録_トークン.PNG](https://qiita-image-store.s3.amazonaws.com/0/254780/a9c13c7e-7fb3-2987-47c5-104dd7f1d6e2.png)

これで終わりです。
アクセストークンは、40桁の半角英数字で取得できます。

### ③Qiita記事データをスクレイピングする

Pythonで、Qiitaの記事データをスクレイピングします。

ここで、②で取得したアクセストークンを使います。
注意点は、Qiita APIでは、1回のアクセスで最大100件の記事情報しか取得できない点と、1時間あたりのアクセス回数が1000回までに制限されている点です（2018/10/20時点、詳細は[Qiita API v2ドキュメント - Qiita:Developer](https://qiita.com/api/v2/docs)）

上記を意識して、Qiitaの記事データを収集するにあたり、以下のように準備します。

```python
# Qiitaデータの収集
import time
import pandas as pd

url = 'https://qiita.com/api/v2/items'  # アクセス先
h = {'Authorization': 'Bearer XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'}  # 取得したアクセストークン

start = '2018-10-15'  # 取得開始日
end = '2018-10-18'  # 取得終了日

date_list = [d.strftime('%Y-%m-%d') for d in pd.date_range(start, end)]  # date_rangeで日付を補完する
start_list = date_list[:-1]
end_list = date_list[1:]

result_dir_path = 'data/qiita'  # 任意のフォルダ名称（出力先）
sleep_sec = 3.6  # 1時間に1000回までのリクエスト制限を超過しないようにするため

print(start_list)  # 確認用
print(end_list)  # 確認用

```
出力：
['2018-10-15', '2018-10-16', '2018-10-17']
['2018-10-16', '2018-10-17', '2018-10-18']
***
次いで、分析・機械学習で利用できそうなデータに絞ることを目的として、以下の関数を定義します。

```python
def get_simple_df(df):
    df['tags_str'] = df['tags'].apply(lambda tags: ','.join(tag['name'] for tag in tags))
    return df[['id', 'title', 'body', 'created_at', 'updated_at','likes_count', 'comments_count', 'tags_str',
               'user_permanent_id', 'user_id', 'user_description', 'user_followees_count', 'user_followers_count', 'user_items_count']]
```

pandasのDataFrame型で諸々の操作を行うことを想定します。
記事タグ('tags')のデータだけは、カンマ区切りの文字列に変換して扱いやすくしておきます。

ここで収集対象としているカラム情報は、以下の14個です。
※例は、私の過去記事から取りました。

まずは、記事IDと紐づいていると想定される9個のカラム情報から説明します。

|カラム名|内容|例|
|:---|:---|:---|
|id|記事のID|de4b512b8212e53bbba3|
|title|記事のタイトル|「クッパ姫」に関するツイートをpythonで収集して、バースト検出してみた|
|body|記事の本文（Markdown）|"「Twitterデータを使って、何か面白いことができないか」の第二弾です。・・・|
|created_at|記事の作成日|2018-09-25T01:23:24+09:00|
|updated_at|記事の更新日|2018-09-25T01:24:04+09:00|
|likes_count|いいねの数|98|
|comments_count|コメントの数|2|
|tags_str|記事のタグ（'tag'を加工）|"Python,Twitter,自然言語処理,python3,バースト検知"|
|user_permanent_id|ユーザID(おそらく番号が小さいほど、ユーザ登録が古い)|254780|

次いで、ユーザIDと紐づいていると想定される5個のカラム情報を説明します。

|カラム名|内容|例|
|:---|:---|:---|
|user_id|ユーザ名|pocket_kyoto|
|user_description|ユーザの自己紹介|(なし)|
|user_followees_count|ユーザフォロー数|0|
|user_followers_count|ユーザフォロワー数|8|
|user_items_count|ユーザ記事投稿数|6|

他にも取得可能な項目はありますが、主に以下の2点の理由から、収集対象から除外しました。

* 整形が大変そう
 - HTML形式のテキストなど（例：「'rendered_body'」カラム）
* 情報の欠損率が高い
 - 情報を提供している人が少ない（例：「'user_organization'」カラム） 
 - 情報が公開されていない（例：「'page_views_count'」カラム）

最後に、データを収集します。

```python
# データ取得する
import os
import requests

# 指定した日付けの区間で繰り返し実行
for start, end in zip(start_list, end_list):
    p = {
        'per_page': 100,  # 最大100件まで
        'query': 'created:>{} created:<{}'.format(start, end)  # 作成日毎で抽出
    }

    print("created_date %s : page 1" % start)
    time.sleep(sleep_sec)  # アクセス制限対策
    r = requests.get(url, params=p, headers=h)  # 記事データ取得
    total_count = int(r.headers['Total-Count'])  # 取得上限対策

    # 取得なし対応
    if total_count == 0:
        continue
        
    df_list = [get_simple_df(pd.io.json.json_normalize(r.json(), sep='_'))]
    
    # 取得上限対策
    if total_count > 100:
        for i in range(2, (total_count - 1) // 100 + 2):
            p['page'] = i
            print("created_date %s : page %s" % (start, i))
            time.sleep(sleep_sec)  # アクセス制限対策
            r = requests.get(url, params=p, headers=h)  # 記事データ取得
            df_list.append(get_simple_df(pd.io.json.json_normalize(r.json(), sep='_')))

    pd.concat(df_list, ignore_index=True).to_csv(os.path.join(result_dir_path, start + '.csv'), index=False)  # 取得したデータを、記事の作成日ごとにファイルを分けてcsv出力
```
出力：
created_date 2018-10-15 : page 1
created_date 2018-10-15 : page 2
created_date 2018-10-15 : page 3
created_date 2018-10-15 : page 4
created_date 2018-10-16 : page 1
created_date 2018-10-16 : page 2
created_date 2018-10-16 : page 3
created_date 2018-10-16 : page 4
created_date 2018-10-17 : page 1
created_date 2018-10-17 : page 2
created_date 2018-10-17 : page 3
created_date 2018-10-17 : page 4
***

収集したデータは、pandas.DataFrameを経由して、CSVで出力しています。
この方法で収集した場合、記事データの1日あたりの件数は300件程度で、1ファイルのデータサイズは1.5MB程度になります。

![Qiita取得データ.PNG](https://qiita-image-store.s3.amazonaws.com/0/254780/8b0eca8e-569c-d3db-1234-fd3f26ef64ff.png)

データの中身は、こんな感じです。

![qiita_dataframe.PNG](https://qiita-image-store.s3.amazonaws.com/0/254780/3c7d3270-3a24-eb36-dc5b-56645d5569ad.png)

以上で、データ収集完了です。

# Qiita記事データが良質なデータセットである11の理由

続いて、Qiitaの記事データが良質なデータセットであると判断した**11の理由**を説明します。

### その１：データが構造化されている

RDBで管理されているようなデータであれば普通ですが、このようにpandasでサクッと取り込めるデータであること自体が良い点です。
フォルダの中に無秩序にExcel、Word、PDFが並べられたデータ、自由形式のHTMLのテキスト群など、恐ろしいデータセットも世の中には沢山あります。

### その２：データ量が多い

量が多いことも、質の一部です。
2011年～現在までのQiitaに投稿された記事データを収集できます。（おそらく50万件以上はある）
100件のデータで統計分析・機械学習しなければならない、となると泣けてきます。

### その３：データ取得が比較的容易

本記事に従って取得すれば、データ取得は楽なはずです。
利用申請、審査などが面倒で、取得時点で挫折してしまうデータも結構あります。
またTwitterデータのように、本気で使おうとすると有償のデータもあります。

### その４：データ利用の制限が厳しくない（おそらく）

いざ取得しても、第三者へのデータ公開が禁止されている、
データを利用した研究成果の報告が義務付けられている、など利用の制限が厳しいデータもあります。
Qiitaの記事データは、公開されているAPIで容易に取得できるので、
データ利用の制限は、厳しくないと推測されます。

### その５：ありふれてない

テキストマイニングの手法を試そうとすると、[青空文庫](https://www.aozora.gr.jp/)、[livedoorニュースコーパス](https://www.rondhuit.com/download.html)、[Wikipedia](https://ja.wikipedia.org/wiki/Wikipedia:%E3%83%87%E3%83%BC%E3%82%BF%E3%83%99%E3%83%BC%E3%82%B9%E3%83%80%E3%82%A6%E3%83%B3%E3%83%AD%E3%83%BC%E3%83%89)など、利用するデータの候補が限定されがちです。
誰かが一度分析済みのデータセットよりは、過去に分析・機械学習の適用の例がないデータセットのほうが、手法を試していて楽しいのではないかと思います（私見）。

### その６：テキストの品質が一定で、欠損が少ない

記事データという性質上、どのテキストの品質も（程度の差はありますが）高いです。
SNSデータなどを分析していると、記述内容が意味不明であったり、形式がフリーダムすぎる場合があって困るのですが、Qiita記事データの場合、そのような心配はなさそうです。
また、記事が書いてない、というケースは当然ないので、欠損が少ない点も良いです。

### その７：「タイトル－本文」の対応がとれる

タイトルは、ある種、本文の要約とみなすことができると思います。
自然言語処理の分野において、文章要約は主要なタスクの一つであるため、文章要約の妥当性を定性的に判断できる材料を用意できる点が良いです。

### その８：時間情報が付与されている

記事の作成日のデータが存在し、時系列で管理されている点が良いです。
これにより、記事のトレンドを分析することが可能となります。
自然言語処理の分野において、トレンドの変化点検知、ならびに変化予測は主要なタスクの一つです。

### その９：ユーザ評価が付与されている

いいね数、コメント数などのユーザ評価により、記事の人気に関する情報を特徴づけることが可能である点が良いです。
これにより、記事の重要度を評価することが可能となります。
自然言語処理の分野において、重要な文書の抽出は主要なタスクの一つです。

### その１０：記事のタグが付与されている

記事のタグが付与されており、文書の分類に関する教師が存在する点が良いです。
これにより、記事の自動分類の精度を評価することが可能となります。
自然言語処理の分野において、文書の自動分類は主要なタスクの一つです。

### その１１：著者に関する情報が紐づいている

著者に関する情報が複数紐づいている点が良いです。
これにより、テキスト以外のメタ情報がユーザ評価等に与える影響を分析することが可能となります。
一般的にあまり着目されていない観点かもしれませんが、
「文書を誰が書いたか？」という観点は、実用上、非常に重要と考えます。
また、著者情報がSNSとリンクしており、Qiita内を超えた範囲で連携できる可能性がある点も良いです。

# まとめと今後

本記事では、Qiitaの記事データの収集方法、Qiitaが分析・機械学習の実践に適したデータセットである理由を説明しました。
本当に、良質なデータセットだと思っているので、今後記事を書く際に、データ適用例として利用していきたいと思います。

# 参考
[1] [Qiitaの記事データをQiita API, Scrapyで収集](https://qiita.com/nkmk/items/fbac13cd05b80334eb2b)
[2] [Qiita API v2ドキュメント - Qiita:Developer]
(https://qiita.com/api/v2/docs)