LoginSignup
4
1

More than 1 year has passed since last update.

QiitaAPIでVRタグが付いた記事の投稿推移を調べてみた。

Last updated at Posted at 2021-10-14

今回が初投稿になります!
つたない記事ではございますが、最後までお付き合いいただけると嬉しいです。。

さて、初めて投稿するにあたって何を書こうかとなったのですが、そもそも自分があまりQiitaを知らない!ということで、QiitaAPIで情報取得しながら遊んでみたいと思います。

何をするのか?

今回は、QiitaAPIで「VR」のタグが付いた記事の投稿日時を取得し、「VR」タグが付いた記事がどのような頻度で投稿されているのかを可視化します。また可視化したうえで、投稿頻度が高かった時期に「VR」関連のイベントはどんなことがあったのか、影響はあるのか考察していきたいと思います。

環境

環境については以下の通りです。

◆実行環境
Node.js 16.10.0

◆パッケージ
npm 7.24.0
axios 0.23.0

◆QiitaAPI
・GET /api/v2/tags
・GET /api/v2/tags/:tag_id
・GET /api/v2/tags/:tag_id/items

◆コーディング
・Visual Studio Code 1.61.0

いざ実施!

まずはタグに関する情報をQiitaAPIから引っ張ってみたい。。
ということで調べてみた結果、以下のコードを実行してみました。

tags.js
const axios = require('axios');

async function main() {
    let response = await axios.get("https://qiita.com/api/v2/tags");
    console.log(response);    
}

main();

結果はこんな感じになりました笑

{
  status: 200,
  statusText: 'OK',
  headers: {
    date: 'Wed, 13 Oct 2021 12:09:20 GMT',
    'content-type': 'application/json; charset=utf-8',
    'transfer-encoding': 'chunked',
    connection: 'close',
    server: 'nginx',
    'x-frame-options': 'SAMEORIGIN',
    'x-xss-protection': '1; mode=block',
    'x-content-type-options': 'nosniff',
    'x-download-options': 'noopen',
    'x-permitted-cross-domain-policies': 'none',
    'referrer-policy': 'strict-origin-when-cross-origin',
    link: '<https://qiita.com/api/v2/tags?page=1>; rel="first", <https://qiita.com/api/v2/tags?page=2>; rel="next", <https://qiita.com/api/v2/tags?page=7684>; rel="last"',
    'total-count': '153676',
    etag: 'W/"1c4a8a4b35dda6ecd5e3273ab3291f4b"',
    'cache-control': 'max-age=0, private, must-revalidate',
    'rate-limit': '60',
    'rate-remaining': '57',
    'rate-reset': '1634129821',
    vary: 'Origin',
    'x-request-id': 'f7a5c892-64fa-4a1f-b664-f5b644d2c5fa',
    'x-runtime': '0.342109',
    'strict-transport-security': 'max-age=2592000'
  },
  config: {
    transitional: {
      silentJSONParsing: true,
      forcedJSONParsing: true,
      clarifyTimeoutError: false
    },
    adapter: [Function: httpAdapter],
    transformRequest: [ [Function: transformRequest] ],
    transformResponse: [ [Function: transformResponse] ],
    timeout: 0,
    xsrfCookieName: 'XSRF-TOKEN',
    xsrfHeaderName: 'X-XSRF-TOKEN',
    maxContentLength: -1,
    maxBodyLength: -1,
    validateStatus: [Function: validateStatus],
    headers: {
      Accept: 'application/json, text/plain, */*',
      'User-Agent': 'axios/0.22.0'
    },
    method: 'get',
    url: 'https://qiita.com/api/v2/tags',
    data: undefined
  },
  request: <ref *1> ClientRequest {
    _events: [Object: null prototype] {
      abort: [Function (anonymous)],
      aborted: [Function (anonymous)],
      connect: [Function (anonymous)],
      error: [Function (anonymous)],
      socket: [Function (anonymous)],
      timeout: [Function (anonymous)],
      prefinish: [Function: requestOnPrefinish]
    },
    _eventsCount: 7,
    _maxListeners: undefined,
    outputData: [],
    outputSize: 0,
    writable: true,
    destroyed: false,
    _last: true,
    chunkedEncoding: false,
    shouldKeepAlive: false,
    maxRequestsOnConnectionReached: false,
    _defaultKeepAlive: true,
    useChunkedEncodingByDefault: false,
    sendDate: false,
    _removedConnection: false,
    _removedContLen: false,
    _removedTE: false,
    _contentLength: 0,
    _hasBody: true,
    _trailer: '',
    finished: true,
    _headerSent: true,
    _closed: false,
    socket: TLSSocket {
      _tlsOptions: [Object],
      _secureEstablished: true,
      _securePending: false,
      _newSessionPending: false,
      _controlReleased: true,
      secureConnecting: false,
      _SNICallback: null,
      servername: 'qiita.com',
      alpnProtocol: false,
      authorized: true,
      authorizationError: null,
      encrypted: true,
      _events: [Object: null prototype],
      _eventsCount: 10,
      connecting: false,
      _hadError: false,
      _parent: null,
      _host: 'qiita.com',
      _readableState: [ReadableState],
      _maxListeners: undefined,
      _writableState: [WritableState],
      allowHalfOpen: false,
      _sockname: null,
      _pendingData: null,
      _pendingEncoding: '',
      server: undefined,
      _server: null,
      ssl: [TLSWrap],
      _requestCert: true,
      _rejectUnauthorized: true,
      parser: null,
      _httpMessage: [Circular *1],
      [Symbol(res)]: [TLSWrap],
      [Symbol(verified)]: true,
      [Symbol(pendingSession)]: null,
      [Symbol(async_id_symbol)]: 3,
      [Symbol(kHandle)]: [TLSWrap],
      [Symbol(kSetNoDelay)]: false,
      [Symbol(lastWriteQueueSize)]: 0,
      [Symbol(timeout)]: null,
      [Symbol(kBuffer)]: null,
      [Symbol(kBufferCb)]: null,
      [Symbol(kBufferGen)]: null,
      [Symbol(kCapture)]: false,
      [Symbol(kBytesRead)]: 0,
      [Symbol(kBytesWritten)]: 0,
      [Symbol(connect-options)]: [Object],
      [Symbol(RequestTimeout)]: undefined
    },
    _header: 'GET /api/v2/tags HTTP/1.1\r\n' +
      'Accept: application/json, text/plain, */*\r\n' +
      'User-Agent: axios/0.22.0\r\n' +
      'Host: qiita.com\r\n' +
      'Connection: close\r\n' +
      '\r\n',
    _keepAliveTimeout: 0,
    _onPendingData: [Function: nop],
    agent: Agent {
      _events: [Object: null prototype],
      _eventsCount: 2,
      _maxListeners: undefined,
      defaultPort: 443,
      protocol: 'https:',
      options: [Object: null prototype],
      requests: [Object: null prototype] {},
      sockets: [Object: null prototype],
      freeSockets: [Object: null prototype] {},
      keepAliveMsecs: 1000,
      keepAlive: false,
      maxSockets: Infinity,
      maxFreeSockets: 256,
      scheduling: 'lifo',
      maxTotalSockets: Infinity,
      totalSocketCount: 1,
      maxCachedSessions: 100,
      _sessionCache: [Object],
      [Symbol(kCapture)]: false
    },
    socketPath: undefined,
    method: 'GET',
    maxHeaderSize: undefined,
    insecureHTTPParser: undefined,
    path: '/api/v2/tags',
    _ended: true,
    res: IncomingMessage {
      _readableState: [ReadableState],
      _events: [Object: null prototype],
      _eventsCount: 3,
      _maxListeners: undefined,
      socket: [TLSSocket],
      httpVersionMajor: 1,
      httpVersionMinor: 1,
      httpVersion: '1.1',
      complete: true,
      rawHeaders: [Array],
      rawTrailers: [],
      aborted: false,
      upgrade: false,
      url: '',
      method: null,
      statusCode: 200,
      statusMessage: 'OK',
      client: [TLSSocket],
      _consuming: true,
      _dumped: false,
      req: [Circular *1],
      responseUrl: 'https://qiita.com/api/v2/tags',
      redirects: [],
      [Symbol(kCapture)]: false,
      [Symbol(kHeaders)]: [Object],
      [Symbol(kHeadersCount)]: 44,
      [Symbol(kTrailers)]: null,
      [Symbol(kTrailersCount)]: 0,
      [Symbol(RequestTimeout)]: undefined
    },
    aborted: false,
    timeoutCb: null,
    upgradeOrConnect: false,
    parser: null,
    maxHeadersCount: null,
    reusedSocket: false,
    host: 'qiita.com',
    protocol: 'https:',
    _redirectable: Writable {
      _writableState: [WritableState],
      _events: [Object: null prototype],
      _eventsCount: 2,
      _maxListeners: undefined,
      _options: [Object],
      _ended: true,
      _ending: true,
      _redirectCount: 0,
      _redirects: [],
      _requestBodyLength: 0,
      _requestBodyBuffers: [],
      _onNativeResponse: [Function (anonymous)],
      _currentRequest: [Circular *1],
      _currentUrl: 'https://qiita.com/api/v2/tags',
      [Symbol(kCapture)]: false
    },
    [Symbol(kCapture)]: false,
    [Symbol(kNeedDrain)]: false,
    [Symbol(corked)]: 0,
    [Symbol(kOutHeaders)]: [Object: null prototype] {
      accept: [Array],
      'user-agent': [Array],
      host: [Array]
    }
  },
  data: [
    {
      followers_count: 0,
      icon_url: null,
      id: '満足,柔軟性',
      items_count: 1
    },
    { followers_count: 0, icon_url: null, id: 'gfgs', items_count: 1 },
    { followers_count: 0, icon_url: null, id: 'chem', items_count: 0 },
    {
      followers_count: 0,
      icon_url: null,
      id: 'Shaquille',
      items_count: 0
    },
    {
      followers_count: 0,
      icon_url: null,
      id: 'レプリケーションファクタ',
      items_count: 1
    },
    {
      followers_count: 0,
      icon_url: null,
      id: 'suttonbankcashapp',
      items_count: 0
    },
    {
      followers_count: 0,
      icon_url: null,
      id: 'nhavuonto',
      items_count: 0
    },
    {
      followers_count: 0,
      icon_url: null,
      id: 'アカシックエンジン',
      items_count: 1
    },
    {
      followers_count: 0,
      icon_url: null,
      id: 'Akashic-Engine',
      items_count: 2
    },
    {
      followers_count: 0,
      icon_url: null,
      id: 'ニコ生ゲーム',
      items_count: 2
    },
    {
      followers_count: 0,
      icon_url: null,
      id: '二重ルーター',
      items_count: 1
    },
    {
      followers_count: 0,
      icon_url: null,
      id: 'ブリッジモード',
      items_count: 1
    },
    {
      followers_count: 0,
      icon_url: null,
      id: 'PetriNet',
      items_count: 0
    },
    {
      followers_count: 0,
      icon_url: null,
      id: 'フレキシブルサーバー',
      items_count: 0
    },
    {
      followers_count: 0,
      icon_url: null,
      id: 'Soundproofing',
      items_count: 0
    },
    {
      followers_count: 0,
      icon_url: null,
      id: 'Soundproofing,',
      items_count: 0
    },
    {
      followers_count: 0,
      icon_url: null,
      id: 'Chobiit',
      items_count: 0
    },
    {
      followers_count: 0,
      icon_url: null,
      id: '#QiitaAPI',
      items_count: 1
    },
    {
      followers_count: 0,
      icon_url: null,
      id: '#COBOL',
      items_count: 1
    }
  ]
}

ずらっと並んだ文字の羅列にびっくりしましたが、よくよく調べてみると叩いたAPIは「タグ一覧を作成日時の降順で返す。」とのこと。
確かに新しい記事についているタグ情報が返ってきているようです。
また、responseを出力するときに返ってきた情報をすべて指定したため、余計な情報も出力してしまいました。

気を取り直して、、「VR」タグに関する記事だけ取ってこれないか調べた結果
・GET /api/v2/tags/:tag_id
を見つけました。
「:tag_id」の部分は取得したいタグ名を記載します。

それを踏まえて先ほどのコードを修正したものがこちらです。

VR_tags.js
const axios = require('axios');

async function main() {
    let response = await axios.get("https://qiita.com/api/v2/tags/VR");
    console.log(response.data);    
}

main();

先ほどのコードと比べ、responseの出力部分も修正しています。

console.log(response.data);  

結果はこんな感じに。

{
  followers_count: 561,
  icon_url: 'https://s3-ap-northeast-1.amazonaws.com/qiita-tag-image/61c94f5abe866a439f9f3574153ceb1a3ea84d47/medium.jpg?1591884698',
  id: 'VR',
  items_count: 1035
}

だいぶすっきりしました笑

こちらは「VR」のフォロー人数とタグ付けされた記事数(item_count)が取得できるみたいです。
以下のURLに記載された情報と同じものになります。

さて、タグを絞り込んで情報を取得することはできましたが、これだけでは何もできません。。
当初の目的であった、「VR」タグが付いた記事の投稿頻度を調べるため試行錯誤いたしました。

最後はこちらのAPIを使用してみたいと思います。
・GET /api/v2/tags/:tag_id/items

VR_tags.js
const axios = require('axios');
const fs    = require("fs");

async function main() {
    var csv = "";

    let response1 = await axios.get("https://qiita.com/api/v2/tags/VR/items?page=1&per_page=100");

    for (var dataA of response1.data){
        csv += dataA.created_at;
        csv += ",";
    }

    let response2 = await axios.get("https://qiita.com/api/v2/tags/VR/items?page=2&per_page=100");

    for (var dataB of response2.data){
        csv += dataB.created_at;
        csv += ",";
    }

    let response3 = await axios.get("https://qiita.com/api/v2/tags/VR/items?page=3&per_page=100");

    for (var dataC of response3.data){
        csv += dataC.created_at;
        csv += ",";
    }

    let response4 = await axios.get("https://qiita.com/api/v2/tags/VR/items?page=4&per_page=100");

    for (var dataD of response4.data){
        csv += dataD.created_at;
        csv += ",";
    }

    let response5 = await axios.get("https://qiita.com/api/v2/tags/VR/items?page=5&per_page=100");

    for (var dataE of response5.data){
        csv += dataE.created_at;
        csv += ",";
    }

    let response6 = await axios.get("https://qiita.com/api/v2/tags/VR/items?page=6&per_page=100");

    for (var dataF of response6.data){
        csv += dataF.created_at;
        csv += ",";
    }

    let response7 = await axios.get("https://qiita.com/api/v2/tags/VR/items?page=7&per_page=100");

    for (var dataG of response7.data){
        csv += dataG.created_at;
        csv += ",";
    }

    let response8 = await axios.get("https://qiita.com/api/v2/tags/VR/items?page=8&per_page=100");

    for (var dataH of response8.data){
        csv += dataH.created_at;
        csv += ",";
    }

    let response9 = await axios.get("https://qiita.com/api/v2/tags/VR/items?page=9&per_page=100");

    for (var dataI of response9.data){
        csv += dataI.created_at;
        csv += ",";
    }

    let response10 = await axios.get("https://qiita.com/api/v2/tags/VR/items?page=10&per_page=100");

    for (var dataJ of response10.data){
        csv += dataJ.created_at;
        csv += ",";
    }

    fs.writeFile('out.csv', csv, (err, data) => {
        if(err) console.log(err);
        else console.log('write end');
         }
    );

}

main();

だいぶ変わりましたがざっと変更点についてお伝えします。

let response1 = await axios.get("https://qiita.com/api/v2/tags/VR/items?page=1&per_page=100");

こちらは「GET /api/v2/tags/:tag_id/items」のAPIをたたくためのコードになります。
APIの機能として「指定されたタグが付けられた記事一覧を、タグを付けた日時の降順で返します。」とのことであり、まさしく私が求めていたAPIでした!
また「?page=1&per_page=100」の部分ですがオプション扱いになります。
Qiita APIでは結果に対して、ページという単位で分割して返すようになっています。
この時に使うオプションが「per_page」と「page」で、「per_page」では1ページ辺りにいくつの結果を含むかを最大100まで指定できます。
「page」ではその中の何ページ目を取得するかを指定します。
今回は1000件分の結果を取得するために10回繰り返しています。

fs.writeFile('out.csv', csv, (err, data) => {
        if(err) console.log(err);
        else console.log('write end');
         }
    );

こちらは結果出力用のコードになります。
「out.csv」というファイルに結果を出力します。
1000件分の結果を取得するのと同時に結果をグラフ化したかったのでファイル出力できるように対応してみました!

結果&考察

最後のコードを実行した結果は無事「out.csv」ファイルに出力されました!
結果をグラフ化し、私が調べたVR関連の主要イベントを記載したものが以下の画像になります。

グラフ.png

こうしてみると、極端に投稿数が増えている月があるのが分かります。
しかし主要なVR関連のイベントの時期とはズレていたので、投稿数と主要イベントとの間には相関関係は見られませんでした。

まとめ

今回、初めてJavaScriptやAPIといったものに触ってみました。
APIという言葉はよく耳にしていましたが、それがどういったものなのかわからず今まで来てしまいました。ただ実際に触ってみるととても実感がわきました。各サービスが提供しているAPIを上手に使うことで、さまざまなデータが容易に手に入るこの機能は汎用性や発展性が高く、話題になるのも納得です。
またQiitaでの記事投稿は初めてでしたが、Markdown記法による執筆も初めてでとても新鮮でした。
今後も記事投稿を行っていきたいと思いますので、このMarkdown記法にも慣れていきたいと思います。

4
1
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1