Qiitaの投稿記事からデータセット作った

## はじめに

こんにちは。今年ももう終わりですね。
普段は記事を読むだけの僕ですが、初めて記事を書きたいと思います。

今回はQiitaのAPIを利用して、これまでのQiitaの記事について分析してみました。その過程で死ぬほどエラーでつまづいたので、二度とこんな無駄な作業が生まれないよう、ここにそのノウハウを共有します。
収集したデータセットも合わせて公開しますのでご査収ください。

## やったこと
- QiitaAPIをPythonで扱う練習
  - 月ごとの投稿数を調査
- Qiitaの投稿データに関するデータセットを作成
  - 2011/9-2018/10までの全記事データを収集

## Qiita APIの基本的な使い方
[Qiita API v2ドキュメント - Qiita:Developer](https://qiita.com/api/v2/docs)

PythonでAPIを扱うためにrequestモジュールを利用します。例えば投稿の一覧を取得してみます。[^1]

[^1]: https://qiita.com/api/v2/docs#get-apiv2items

```python
import requests

url = 'https://qiita.com/api/v2/items'
params = {
    'page'     : 1,
    'per_page' : 100
}
headers = {
    'content-type'  : 'application/json',
    'charset'       : 'utf-8'
}

r = requests.get(url, params=params,  headers=headers)
print('Response={0}, Detail={1}'.format(r.status_code,r.headers))
print(r.json())

```

うまくいけばResponseが200で返ってきます。

```json
Response=200,
Detail={
    'Date':'Thu,
    08 Nov 2018 11:30:05 GMT',
    'Content-Type':'application/json; charset=utf-8',
    'Transfer-Encoding':'chunked',
    'Connection':'keep-alive',
    'Server':'nginx',
    'X-Frame-Options':'SAMEORIGIN',
    'X-XSS-Protection':'1; mode=block',
    'X-Content-Type-Options':'nosniff',
    'Link':'<https://qiita.com/api/v2/items?page=1&per_page=100>; rel="first",
    <https://qiita.com/api/v2/items?page=2&per_page=100>; rel="next",
    <https://qiita.com/api/v2/items?page=3454&per_page=100>; rel="last"',
    'Total-Count':'345301',
    'ETag':'W/"b69bd1299e58ad6e0f3f0a54e937fae4"',
    'Cache-Control':'max-age=0, private, must-revalidate',
    'Rate-Limit':'60',
    'Rate-Remaining':'59',
    'Rate-Reset':'1541680205',
    'Vary':'Origin',
    'X-Runtime':'0.561610',
    'Strict-Transport-Security':'max-age=2592000',
    'X-Request-Id':'03beeecd-a39f-411c-9ad0-77e868584bea'

}
```

2018/11/8現在、Qiita上には345301件の記事が存在します。


レスポンスの中身はこんな感じです。

```json
{'body': '# すべての開発者が学ぶべきたった一つの言語という記事\n\nなんかバズってるし、私もパクって駄文を一つ書いてみることにしました。\nヤマト運輸を待ってる最中なので暇なんですよねー。\nZOZOのジーンズ早く来ないかなー。\n\n## 職場でメインに使われてる言語\n\n例えば、日本国内の職場ならメインの言葉はたいてい日本語。\nそういう職場では日本語ができないと何もできない。\n仕事にならない、\n\n## 英語\n\n大半のライブラリーやプログラム言語の解説は英語。\nこの言語ができないと調べものもできない、知識の習得もできない。\n超重要な言語。\n\n## コミュニケーション能力\n\n言語能力についで必要な能力。言語が荷車の片輪だとするとこの能力はもう片方の車輪。\n\n上司がなにか言う前に忖度していろいろやる技術・・・・・ではもちろんない。\n\n**相手を尊重し、相手を理解し、その上で自分を理解してもらう能力である。**\n\n様々な書籍があり、さまざまな研究がなされているがこれ、ものすごく難しい。言語能力に加えてこの能力があれば最強である。\n\nプログラム言語だけできる人は多い。人間の言語がきちんと話せる人も多い。しかし、コミュニケーション能力がある技術者は少ない（ように思う、とくに某記事への叩き方を見ると）。\n\nこの能力があれば、\n\nなんの役にも立たない意見、人を馬鹿にしたような意見、があっても\n\n**よってたかって叩かず、ネタにせず、静かに笑っていられる人**\n\nになれます。（多分）\n\n',
  'coediting': False,
  'comments_count': 0,
  'created_at': '2018-11-09T09:50:52+09:00',
  'group': None,
  'id': '75890603ac6e6c3b2328',
  'likes_count': 0,
  'page_views_count': None,
  'private': False,
  'reactions_count': 0,
  'rendered_body': '\n<h1>\n<span id="すべての開発者が学ぶべきたった一つの言語という記事" class="fragment"></span><a href="#%E3%81%99%E3%81%B9%E3%81%A6%E3%81%AE%E9%96%8B%E7%99%BA%E8%80%85%E3%81%8C%E5%AD%A6%E3%81%B6%E3%81%B9%E3%81%8D%E3%81%9F%E3%81%A3%E3%81%9F%E4%B8%80%E3%81%A4%E3%81%AE%E8%A8%80%E8%AA%9E%E3%81%A8%E3%81%84%E3%81%86%E8%A8%98%E4%BA%8B"><i class="fa fa-link"></i></a>すべての開発者が学ぶべきたった一つの言語という記事</h1>\n\n<p>なんかバズってるし、私もパクって駄文を一つ書いてみることにしました。<br>\nヤマト運輸を待ってる最中なので暇なんですよねー。<br>\nZOZOのジーンズ早く来ないかなー。</p>\n\n<h2>\n<span id="職場でメインに使われてる言語" class="fragment"></span><a href="#%E8%81%B7%E5%A0%B4%E3%81%A7%E3%83%A1%E3%82%A4%E3%83%B3%E3%81%AB%E4%BD%BF%E3%82%8F%E3%82%8C%E3%81%A6%E3%82%8B%E8%A8%80%E8%AA%9E"><i class="fa fa-link"></i></a>職場でメインに使われてる言語</h2>\n\n<p>例えば、日本国内の職場ならメインの言葉はたいてい日本語。<br>\nそういう職場では日本語ができないと何もできない。<br>\n仕事にならない、</p>\n\n<h2>\n<span id="英語" class="fragment"></span><a href="#%E8%8B%B1%E8%AA%9E"><i class="fa fa-link"></i></a>英語</h2>\n\n<p>大半のライブラリーやプログラム言語の解説は英語。<br>\nこの言語ができないと調べものもできない、知識の習得もできない。<br>\n超重要な言語。</p>\n\n<h2>\n<span id="コミュニケーション能力" class="fragment"></span><a href="#%E3%82%B3%E3%83%9F%E3%83%A5%E3%83%8B%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E8%83%BD%E5%8A%9B"><i class="fa fa-link"></i></a>コミュニケーション能力</h2>\n\n<p>言語能力についで必要な能力。言語が荷車の片輪だとするとこの能力はもう片方の車輪。</p>\n\n<p>上司がなにか言う前に忖度していろいろやる技術・・・・・ではもちろんない。</p>\n\n<p><strong>相手を尊重し、相手を理解し、その上で自分を理解してもらう能力である。</strong></p>\n\n<p>様々な書籍があり、さまざまな研究がなされているがこれ、ものすごく難しい。言語能力に加えてこの能力があれば最強である。</p>\n\n<p>プログラム言語だけできる人は多い。人間の言語がきちんと話せる人も多い。しかし、コミュニケーション能力がある技術者は少ない（ように思う、とくに某記事への叩き方を見ると）。</p>\n\n<p>この能力があれば、</p>\n\n<p>なんの役にも立たない意見、人を馬鹿にしたような意見、があっても</p>\n\n<p><strong>よってたかって叩かず、ネタにせず、静かに笑っていられる人</strong></p>\n\n<p>になれます。（多分）</p>\n',
  'tags': [{'name': 'ポエム', 'versions': []},
   {'name': 'ネタ', 'versions': []},
   {'name': '駄文', 'versions': []},
   {'name': 'ジョーク', 'versions': []}],
  'title': 'すべての開発者が学ぶべき言語と技術',
  'updated_at': '2018-11-09T09:50:52+09:00',
  'url': 'https://qiita.com/TakaakiFuruse/items/75890603ac6e6c3b2328',
  'user': {'description': '',
   'facebook_id': '',
   'followees_count': 83,
   'followers_count': 13,
   'github_login_name': 'TakaakiFuruse',
   'id': 'TakaakiFuruse',
   'items_count': 36,
   'linkedin_id': 'tfuruse',
   'location': '',
   'name': '',
   'organization': '',
   'permanent_id': 52613,
   'profile_image_url': 'https://qiita-image-store.s3.amazonaws.com/0/52613/profile-images/1473692788',
   'twitter_screen_name': None,
   'website_url': ''}},
```

## 月別投稿数を取得してみた

APIの練習として2011-2018[^kikan]の投稿数を月別にプロットしてみます。

[^kikan]: Qiitaのサービスインは2011/9/16


```Python
import pandas as pd

dic={}
for year in range(2011,2019):
  for month in range(1,13):
    key = "{}-{}".format(year,month)
    total_articles,total_pages = get_monthly_data(year,month)
    dic[key]=[total_articles, total_pages]
    print(year, month, total_articles, total_pages)
    time.sleep(60)

x = list(dic.keys())
y = [cnt[0] for cnt in list(dic.values())]
df = pd.DataFrame({'time':x,'count':y},index=x)
df = df.drop(index=['2018-11','2018-12'])
df['count'].plot(figsize=(16,9),
                 title='The Number of Monthly Posts in Qiita',
                 xticks=range(0,len(x))[::3],
                 rot=45)
```


![qiita_analysis.png](https://qiita-image-store.s3.amazonaws.com/0/319677/d36e12ff-c041-f44c-dc56-abedf0f1d9f1.png)


2013年から周期的にスパイクが発生しています。毎年12月頃…そう、みなさんご存知AdventCalendarの影響です。2018年は平均8000postで推移しているので今年のAdventCalendarも前年を超えてきそうですね。

## Qiitaのデータセット
「いいねがたくさん欲しいんだけど、どんなタイトルにすればいいのかな？」
「閲覧数、いいね数、コメント数の相関はあるのだろうか？」

色々調べたくなったのでデータセットを作ってみました。[^caution]

[^caution]: いいね数やコメント数はデータ取得当時のものなので注意してください。

ぱっと見はこんな感じです。

![qiita_data.png](https://qiita-image-store.s3.amazonaws.com/0/319677/6bb4135e-0d1f-8d27-f126-8b410aaffa2f.png)



ラベル|内容
---|---
created_at|データが作成された日時
updated_at |データが最後に更新された日時
id|投稿の一意なID
title|投稿のタイトル
user|ユーザ名[^user]
likes_count|この投稿への「いいね！」の数
comments_count|この投稿へのコメントの数
page_views_count |閲覧数
url|投稿のURL
tags|投稿に付いたタグ一覧

page_views_countは取得したのですが全部`None`でした。また、記事のタイトルに改行コード```\r```が混入しているため、少しハマりました。

11/30までのデータを[Qiita_dataset_1113 | Kaggle](https://www.kaggle.com/si1242/qiita-dataset-1113)に置いておきます。練習用にどーぞ。

[^user]: twitterのフォロー数や紹介文など


## Qiita APIをうまく使うコツ
QiitaのAPIには色々と制約があります。ポイントは下記2点です。

###### APIの利用制限
>認証している状態ではユーザごとに1時間に1000回まで、認証していない状態ではIPアドレスごとに1時間に60回までリクエストを受け付けます。

普通にループで繰り返し処理をするとすぐにエラーが返ってくるはずです。```time.sleep(n)```などで待ち時間を挿入することで解決しましょう。

参考に待ち時間の目安をまとめておきます。

| |制限対象|利用制限|待ち時間|
|---|---|---|---|
|認証あり[^token]|ユーザ|1000回/時間|3.6秒/リクエスト|
|認証なし|ipアドレス|60回/時間|60秒/リクエスト|

[^token]: ユーザ認証用のアクセストークンを用いた場合


###### pageの上限が100
>pageの初期値は1、pageの最大値は100に設定されています。


```python
import math
# 全記事数
total_articles = int(r.headers['Total-Count'])
total_pages = math.ceil(total_articles/float(params['per_page']))

print(total_articles, total_pages)
```

上記のコードを実行してみると、Qiitaの全記事を取得するためには3000page以上リクエストを投げる必要があります。そんなの絶対無理やん。と思っていたところ、とあるブログ[^blog]で紹介されていた「queryを細かく投げてpage数を抑える」という技で何とかなりました。感謝。


[^blog]: [Google Colaboratory で Qiita:Team の全投稿を取得して、可視化したり、人気ランキングを作ってみる - Kaizen Platform 開発者ブログ](https://developer.kaizenplatform.com/entry/takus/2018-04-26/)


## まとめ
今回はQiitaのAPIから過去の投稿記事のデータセットを作成してみました。ちなみにLSTMで2018/12の投稿数を予測してみたところ〇〇件でした。答えはお正月にでも。