【Qiita API】いろんな方法で Views、Likes、Stocksを取得（JavaScript、Google Script、Python, Vue.js）


##はじめに
Qiitaの自分の投稿をみると、「ページビュー（View）」・「いいね（Like）」「ストック（Stock）」の３つの数字があります。すべての投稿の「いいね数」はマイページで合計されていますが、各投稿のViewとStockや合計は、パッとわかりません。投稿も20くらいになってきたので、勉強のために、自分の投稿のView・Like・Stockすべてを取得する、というのをいろいろな方法でやってみようと思いました。

調べてみると、皆さんがいろいろな方法でQiita APIを叩き、情報を取得しており、参考ページもたくさんありますので、それらを確認させていただきながら実施します。

-「いいね（Like）」：APIを叩くと取得できる（likes_count）
-「ページビュー（View）」：アクセストークンを設定して、APIを叩くと取得できる（page_views_count）
-「ストック（Stock）」：APIでは数は取得できないので、ストックしているユーザ数をカウントする

##参考URL
下記の投稿を主に参考にさせていただきました。（他の参照ページは最後に記載）
[【Qiita API】いいね！閲覧数の自動集計(Qiita)](https://qiita.com/Naoto9282/items/252c4b386aeafc0052ba)


##前提
Qiita 個人用アクセストークンの発行
Qiita「設定」→「アプリケーション」→「個人用アクセストークン」 （読み取り専用：read_qiita）

##実装の種類
①HTML/JavaScript
②GoogleScript
③Python + Vue.js


##①HTML/JavaScript
・発行した自分のアクセストークンを設定して「読み込み」ボタンを押下
![html1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/167772/97f256e9-7e8c-184a-7bc5-720c14cac32c.png)
![html2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/167772/d93fbe80-0c2b-f32b-b261-c943b08d7c23.png)

コード

~~~Qiita_data_get.html

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
    <title>Qiita Item Get</title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css">
  </head>

  <body>
    <div class="container">
      <br>
      <h3>Qiita投稿一覧取得</h3>
      <br>
      <div class="input-group flex-nowrap">
        <div class="input-group-prepend">
          <span class="input-group-text" id="addon-wrapping">Qiita アクセストークンの設定</span>
        </div>
        <input type="text" class="form-control" id="accesstoken" placeholder="Access Token" aria-describedby="addon-wrapping">
      </div>
      <br>
      <button type="button" class="btn btn-primary" onclick="getData()">読み込み</button>
      <hr>
      <div id="result"></div>
    </div>

    <script src="https://code.jquery.com/jquery-3.2.1.min.js" integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4=" crossorigin="anonymous"></script>

    <script>
      var title;
      var view;
      var like;
      var stock;

      function getData() {
        // アクセストークンの確認
        var token = document.getElementById('accesstoken').value
        if(token == ''){
          alert('アクセストークンを入れてください')
          return
        }

        // 取得できるまで表示
        var result = $("#result");
        var loading = '<p>Now Loading...</p>';
        result.append(loading);

        // 自分の投稿一覧を取得して、id別にデータを取得する
        var items = [];
        $.ajax({
          type: "GET",
          url: "https://qiita.com/api/v2/authenticated_user/items",
          headers: {
            "Content-Type": "application/json",
            "Authorization": "Bearer " + token,
          },
          dataType: "json",
          success: function(data){
            items = data;

            var sum_view = 0;
            var sum_like = 0;
            var sum_stock = 0;

            var html = '<table class="table table-dark">' +
                      '<thead><tr><th scope="col">No</th><th scope="col">タイトル</th><th scope="col">VIEWS</th><th scope="col">LIKES</th><th scope="col">STOCKS</th><th scope="col">LIKE率</th><th scope="col">STOCK率</th></tr></thead><tbody>'

            // 投稿一覧をループして、ID別にデータを取得、HTMLを設定
            for (var i = 0; i < items.length; i++) {
              qiitaView(items[i].id, token);
              qiitaStock(items[i].id, token);

              var no = items.length - i;

              var per_like = like / view * 1000;
              var math_like = Math.round(per_like) / 1000;

              var per_stock = stock / view * 1000;
              var math_stock = Math.round(per_stock) / 1000;

              html +=
              '<tr>' +
              '<th scope="row">' + no + '</th>' +
              '<td><a href="' + url + '" target="_blank">' + title + '</a></td>' +
              '<td>' + view + '</td>' +
              '<td>' + like + '</td>' +
              '<td>' + stock + '</td>' +
              '<td>' + math_like + '</td>' +
              '<td>' + math_stock + '</td>' +
              '</tr>';

              sum_view += view;
              sum_like += like;
              sum_stock += stock;

            };

            // html に合計を設定し、書き出し
            html +=
              '<tr>' +
              '<th scope="row">計</th>' +
              '<td></td>' +
              '<td>' + sum_view + '</td>' +
              '<td>' + sum_like + '</td>' +
              '<td>' + sum_stock + '</td>' +
              '<td>' + '' + '</td>' +
              '<td>' + '' + '</td>' +
              '</tr>' +
              '</tbody></table>';

            result.empty();
            result.append(html);

          },
          error: function(XMLHttpRequest, textStatus, errorThrown){
            result.empty();
            alert('Error1 : アクセストークンあっていますか？' + errorThrown);
          }
        });

      };

      function qiitaView(id, token) {

        var views = [];

        $.ajax({
          type: "GET",
          url: 'https://qiita.com/api/v2/items/' + id,
          headers: {
            "Content-Type": "application/json",
            "Authorization": "Bearer " + token,
          },
          dataType: "json",
          async: false,	     // 同期通信
          success: function(data){
            views = data;
            title = views.title;
            url = views.url;
            like = views.likes_count;
            view = views.page_views_count;
          },
          error: function(XMLHttpRequest, textStatus, errorThrown){
            alert('Error2 : ' + errorThrown);
          }
        });

      };

      function qiitaStock(id, token) {

        var stocks = [];
        var flg = 'off';

        for (var j = 1; j < 4; j++) {

          $.ajax({
            type: "GET",
            url: 'https://qiita.com/api/v2/items/' + id + '/stockers?page=' + j + '&per_page=100',
            headers: {
              "Content-Type": "application/json",
              "Authorization": "Bearer " + token,
            },
            dataType: "json",
            async: false,	     // 同期通信
            success: function(data){
              stocks = data;
              var stock_len = stocks.length;

              if (stock_len != 100) {
                stock = (j * 100) - 100 + stock_len;
                flg = 'on';
              }

            },
            error: function(XMLHttpRequest, textStatus, errorThrown){
              alert('Error3 : ' + errorThrown);
            }
          });

          if (flg=='on') {
            break;
          }

        };
      };
    </script>
  </body>
</html>
~~~

##②Google Script
・GSスクリプトを作成し、プロジェクトを定期実行設定を行う。（「編集」→「現在のプロジェクトのトリガー」から設定。1日1回の取得を設定）
（google scriptは参考にさせていただいたページにほぼ載っています。stockやviewに対する率を追加で設定しています）
（stockは、各投稿毎に1000stockまでの取得）

毎日取得してみると、view数は50～100viewくらいあるんだなぁとパッとわかります。
![gs1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/167772/b7e021e5-8a47-65b1-cf4c-61f2fe0a3368.png)
![gs2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/167772/802039be-51ce-f7b6-e088-2a858cc656cb.png)

コード

~~~.js
function myFunction() {
  
  // 変数設定
  var sum_likes = 0
  var sum_page_views = 0
  var sum_stocks = 0
  
  // 取得日付設定
  var now = new Date()
  var record_title = ['日付']
  var record_page_views = [now]
  var record_likes = [now]
  var record_stocks = [now]
  
  // Qiita API による自身の投稿を取得
  var url = 'https://qiita.com/api/v2/authenticated_user/items'
  var option = {
    headers : {
      'Authorization' : 'Bearer アクセストークンを設定'
    },
    method : 'get'
  }
  
  var res = UrlFetchApp.fetch(url, option)  
  var list = JSON.parse(res.getContentText())
  list = list.reverse()  // 降順から昇順に変更
  
  // 取得したアイテムをループして、view, like, stockを取得
  for(var i = 0; i < list.length; i++) {
    
    var item = list[i]

    // likes数を取得
    var item_id = item['id']
    var title = item['title']
    var likes_count = item['likes_count']
    sum_likes += likes_count
    
    // page viewを取得
    url = 'https://qiita.com/api/v2/items/' + item_id
    res = UrlFetchApp.fetch(url, option)
    var json = JSON.parse(res.getContentText())
    var page_views_count = json['page_views_count']
    sum_page_views += page_views_count
    
    // stock数を取得
    var cnt = 1
    var stock_count = 0
    while(cnt < 10) {
      var url_stock = url + '/stockers?page=' + cnt + '&per_page=100'
      var res_stock = UrlFetchApp.fetch(url_stock, option)
      var json_stock = JSON.parse(res_stock.getContentText())
      var stock_num = json_stock.length

      if (stock_num != 100) {
        stock_count = (cnt * 100) - 100 + stock_num
        sum_stocks += stock_count
        break
      } else {
        cnt += 1 
      }
    }
    
    // シート書き出しのためにデータをセット
    record_title.push(title)
    record_page_views.push(page_views_count)
    record_likes.push(likes_count)
    record_stocks.push(stock_count)
  }

  // 率を計算  
  var par_likes = sum_likes / sum_page_views
  var par_stocks = sum_stocks / sum_page_views
  // スプレッドシートのセット
  var spreadsheet = SpreadsheetApp.openById('xxxxxxxxxxxxxxxxxxxxxxxxxxx')
  // シート：sum
  var sheet = spreadsheet.getSheetByName('sum')
  sheet.appendRow([new Date(), list.length, sum_page_views, sum_likes, sum_stocks, par_likes, par_stocks])
  // シート：view
  var sheet = spreadsheet.getSheetByName('view')
  sheet.getRange('1:1').clear()
  sheet.getRange(1,1,1,record_title.length).setValues([record_title])
  sheet.appendRow(record_page_views)
  // シート：like
  var sheet = spreadsheet.getSheetByName('like')
  sheet.getRange('1:1').clear()
  sheet.getRange(1,1,1,record_title.length).setValues([record_title])
  sheet.appendRow(record_likes)
  // シート：stock
  var sheet = spreadsheet.getSheetByName('stock')
  sheet.getRange('1:1').clear()
  sheet.getRange(1,1,1,record_title.length).setValues([record_title])
  sheet.appendRow(record_stocks)
}
```


##③Python + PHP/Vue.js
- データをQiita APIにて取得して、DBに設定する（Cron設定にてPythonは1日1回実行）
- Vue.jsにて、axiosを叩きDB情報を取得(php)、表示
![vue1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/167772/02134f6e-2a9d-1657-8a54-e299d56d2fd9.png)

- データ取得ボタンを押下
![vue2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/167772/d7a06ef0-c16b-c27e-e492-be7cbbd4e020.png)

- Python データをQiita APIにて取得して、DBに設定する（Cron設定にて1日1回実行）

~~~Qiita_data_get.py
import requests
import json
import datetime
import MySQLdb

# Qiita アクセストークン
token = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
# Qiita API設定
url = 'https://qiita.com/api/v2/authenticated_user/items'
headers = {'content-type': 'application/json',
           'Authorization': 'Bearer ' + token}
# 接続情報の設定
cnt = MySQLdb.connect(
  user='xxxxxxxxxxxx',
  passwd='xxxxxxxxxxxxx',
  host='xxxxxxxxxxxxxxxxxxxx',
  db='xxxxxxxxxxxxxx',
  charset='utf8'
)

# カーソル取得
db = cnt.cursor()

# データ取得・ソート
res = requests.get(url, headers=headers)
list = res.json()

for item in list:

    item_id = item['id']
    #タイトル、like数のセット
    title = item['title']
    item_url = item['url']
    likes_count = item['likes_count']

    url = 'https://qiita.com/api/v2/items/' + item_id
    res = requests.get(url, headers=headers)
    json = res.json()
    #タイトル別のview数のセット
    page_views_count = json['page_views_count']

    i = 1
    #stock数の取得
    while i < 10:

        url_stock = url + '/stockers?page=' + str(i) + '&per_page=100'
        res_stock = requests.get(url_stock, headers=headers)
        json_stock = res_stock.json()
        stock_num = len(json_stock)

        if stock_num != 100:
            stock_count = (i * 100) - 100 + stock_num
            break
        else:
            i += 1

    # 日付の設定
    now = datetime.datetime.now()
    setdate = now.strftime('%Y-%m-%d')

    # like, stockのパーセントを計算
    per_like = likes_count / page_views_count * 100
    per_like = round(per_like, 2)
    per_stock = stock_count / page_views_count * 100
    per_stock = round(per_stock, 2)

    # DBデータをdateとtitleでselect
    sqlselect = "SELECT * from qiita_data WHERE setdate='" + setdate + "' AND title='" + title + "'"
    db.execute(sqlselect)
    row = db.fetchall()
    dbcnt = len(row)

    # DBにデータがあればupdate(同じ日で実行した場合には、データを上書きするため)
    if dbcnt > 0:
        sqlupdate = "UPDATE qiita_data SET views='" + str(page_views_count) + "', likes='" + str(likes_count) + "', stocks='" + str(stock_count) + "', per_like='" + str(per_like) + "', per_stock='" + str(per_stock) + "' WHERE setdate='" + setdate + "' AND title='" + title + "'"
        db.execute(sqlupdate)
    # DBにデータがなければinsert
    else:
        sqlinsert = "INSERT INTO qiita_data(setdate, title, url, views, likes, stocks, per_like, per_stock) VALUES ('" + setdate + "', '" + title + "', '" + item_url + "', '" + str(page_views_count) + "', '" + str(likes_count) + "', '" + str(stock_count) + "', '" + str(per_like) + "', '" + str(per_stock) + "')"
        db.execute(sqlinsert)

# カーソル終了
db.close()
# コミット
cnt.commit()
# MySQL切断
cnt.close()
~~~

- DB情報を返す

~~~qiita_data_get.php
<?php

  // DB設定のセット
  $host = "xxxxxxxxxxxxxxxxxxxxxxxx";
  $dbname = "xxxxxxxxxxxxxxxxxxxxxxx";
  $user = "xxxxxxxxxxxxx";
  $pass = "xxxxxxxxxxxxx";

  // パラメータの取得
  if (!isset($_GET['setdate'])) {
    $paramdate = '19000101';
  } else {
    $paramdate = $_GET['setdate'];
  }

  // 桁数のチェック
  if (strlen($paramdate)!=8) {
    http_response_code(500);
    echo "setdate length is not 8";
    exit();
  }

  // YYYYMMDD形式のチェック
  $stryear  = substr($paramdate, 0, 4);
  $strmonth = substr($paramdate, 4, 2);
  $strday   = substr($paramdate, 6, 2);
  if (!checkdate($strmonth, $strday, $stryear)) {
    http_response_code(500);
    echo "setdate is not date";
    exit();
  }

  // ヘッダー情報の設定
  header("Content-Type: application/json; charset=utf-8");

  $data = array();

  // DB接続情報設定・SQL準備・接続
  $db = new PDO('mysql:host=' . $host . 'dbname=' . $dbname . 'charset=utf8', $user, $pass);

  // 日付が来なかった場合
  if ($paramdate == '19000101') {
    $sql = "select * from qiita_data";
  // 日付がパラメータにセットされた場合
  } else {
    $sql = "select * from qiita_data where setdate='" . $paramdate . "'";
  }
  $sth = $db -> prepare($sql);
  $sth -> execute();

  // データを取得する
  $data = $sth -> fetchAll(PDO::FETCH_ASSOC);

  // jsonオブジェクト化
  echo json_encode($data);
?>
~~~

- Vues.jsで表示

~~~Qiita_data_get(Vue).html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
    <title>Qiita Data Get</title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css">
  </head>

  <body>
    <div class="container">
      <div id="app">
        <br>
        <h3>Qiita投稿一覧取得</h3>
        <br>
        <p>取得日: <input type="date" id="date" v-model="getdate">（入力しない場合は現在日付で取得）</p>
        <button type="button" class="btn btn-primary" v-on:click="getitem">データ取得</button>
        <hr>
        <table class="table table-dark">
          <thead>
            <tr>
              <th scope="col">No</th>
              <th scope="col">Title</th>
              <th scope="col">Views</th>
              <th scope="col">Likes</th>
              <th scope="col">Stocks</th>
              <th scope="col">Like率</th>
              <th scope="col">Stock率</th>
            </tr>
          </thead>
          <tr v-for="(item, index) in items" :key="item.no">
            <th scope="row">{{ itemno - index }}</th>
            <td><a v-bind:href="item.url" target="_blank">{{ item.title }}</a></td>
            <td>{{ item.views }}</td>
            <td>{{ item.likes }}</td>
            <td>{{ item.stocks }}</td>
            <td>{{ item.per_like }}</td>
            <td>{{ item.per_stock }}</td>
          </tr>
        </table>
      </div>
    <div>

    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>

    <script>
      var app = new Vue({
        el: '#app',
        data: {
          items: [],
          getdate: '',
          itemno: 0
        },
        methods: {
          getitem() {

            if( this.getdate === ''){
              var date = new Date();
              var yyyy = date.getFullYear();
              var mm = ("0"+(date.getMonth()+1)).slice(-2);
              var dd = ("0"+date.getDate()).slice(-2);
              this.getdate = yyyy+'-'+mm+'-'+dd;
            }

            yyyy = this.getdate.substr(0,4)
            mm = this.getdate.substr(5,2)
            dd = this.getdate.substr(8,2)
            var setdate = yyyy + mm + dd
            var url = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/qiita_data_get.php?setdate=' + setdate

            axios.get(url)
            .then(function(response){
              this.items = response.data
              this.itemno = this.items.length
              if(this.itemno == 0){
                alert('データが取得できませんでした')
              }
              console.log(this.itemno)
            }.bind(this))
            .catch(function(error){
              console.log(error + 'が起きています')
            })
          }
        }
      })
    </script>
  </body>
</html>
~~~

##まとめ
・QiitaAPIを叩き、自分の投稿の「ページビュー」/「いいね」/「ストック」のすべての情報取得・表示するところを実装しました。
・どの方法でもよいと思いますが、GoogleScriptは定期実行も簡単に設定できるので、便利かなと思いました。
・次はたまってきた情報をいい感じに見せるグラフ表示とかをやってみたいと思います。→2020/02/12追記：[Chart.jsを使用して、複数軸のグラフを表示する(Javascript)](https://qiita.com/yoshi_yast/items/f4557cb48a13f198d42a)にて実施しました。


##その他の参考URL

- [Qiita API v2 ドキュメント](https://qiita.com/api/v2/docs)
- [Qiita API v2 の概要(非公式)](https://qiita.com/tag1216/items/b0b90e30c7e581aa2b00)
- [Qiita API v2を活用するときの参考記事まとめ](https://qiita.com/kai_kou/items/3cc89f94f4440546a995)
- [Vue.js + axios で Qiita API v2 を叩いて投稿を取得する](https://qiita.com/junsan50/items/af751532e6eeb1e2a987)