Twitter API v2 でキーワードから直近１週間分ツイートを収集する

8月12日（米現地時間）、Twitter API v2 がアーリーアクセスで正式リリースされました。
https://developer.twitter.com/en/docs/twitter-api/early-access

そこで早速、直近１週間分のツイートのキーワード検索ができる `recent search API` でツイート集計・保存するコードを作ったので、一例として共有します。

### ソースコード

```node.js
'use strict'

const fs = require('fs');
const axios = require('axios');
const Qs = require('qs');
const sleep = msec => new Promise(resolve => setTimeout(resolve, msec));

// Set target query
const target_query = 'qiita.com';

// Format nested params correctly
axios.interceptors.request.use(config => {
  config.paramsSerializer = params => {
    // Qs is not included in the Axios package
    return Qs.stringify(params, {
      allowDots: true,
      arrayFormat: "brackets",
      encode: false
    });
  };
  return config;
});

// Set API config
const config_common = {
  method: 'get',
  baseURL: 'https://api.twitter.com/2/tweets',
  url: '/search/recent',
  headers: { 
    Authorization: 'Bearer <YOUR_BEARER_TOKEN>', 
  }
};
const init_config = Object.create(config_common);
const loop_config = Object.create(config_common);

// Set params
init_config.params = {
    query: target_query,
    start_time: '<STRAT_TIME>',  // ex. 2020-09-01T00:00:00.000Z
    end_time: '<END_TIME>',      // ex. 2020-09-03T00:00:00.000Z
    expansions: 'author_id',
    tweet: {
      fields: 'created_at',
    },
  };

let output = [];

const getReq = cfg => {
  return axios(cfg)
    .then(function (res) {
      output = output.concat(res.data.data);

      if (res.data.meta.result_count === 10 && Date.parse(res.data.data[res.data.meta.result_count-1].created_at) > Date.parse(init_config.params.start_time)) {
        // Update params
        loop_config.params = {
          query: target_query,
          until_id: res.data.meta.oldest_id,
          expansions: 'author_id',
          tweet: {
            fields: 'created_at',
          },
        }
        return true;
      }
      else {
        return false;
      }
    })
    .catch(function (error) {
      console.log(error);
    });
}

const loop = async(result) => {
  await sleep(3000);
  if(result) {
    const result = await getReq(loop_config);
    await loop(result);
  }
  else await fs.writeFile('tweetList.json', JSON.stringify(output, null, '    '), (err)=>{
    if(err) console.log(`error: ${err}`);
  });
}

// Run
(async () => {
  const result = await getReq(init_config);
  await loop(result);
})();
```


### 実行後、保存したデータ（`qiita.com`をクエリにした例）

```json
[
    {
        "id": "1300598977407016961",
        "text": "RT @omiita_atiimo: 畳み込みよ、さようなら。畳み込み層を全てSelf-Attentionに取り替えた新時代のモデル「SAN」の解説記事を書きました！\n\nhttps://t.co/BXydQsNpf5\n\nSAだけで従来のCNNよりも優れた精度/ロバスト性を示して…",
        "created_at": "2020-09-01T00:58:54.000Z",
        "author_id": "1123585826623868934"
    },
    {
        "id": "1300598327080177670",
        "text": "RT @ErdLaravel: Laravel 爆速プロトタイプ作成ツール「 https://t.co/4QQvMWoqiJ 」\nER図テーブル設計で『Migrationコード』を自動生成！！\n世界 900人以上を超える開発者に使用してもらっています。\nDB設計の勉強にも使って…",
        "created_at": "2020-09-01T00:56:19.000Z",
        "author_id": "1042801642460987392"
    },
    {
        "id": "1300598272239652866",
        "text": "RT @ErdLaravel: Laravel 爆速プロトタイプ作成ツール「 https://t.co/4QQvMWoqiJ 」\nER図テーブル設計で『Migrationコード』を自動生成！！\n世界 900人以上を超える開発者に使用してもらっています。\nDB設計の勉強にも使って…",
        "created_at": "2020-09-01T00:56:06.000Z",
        "author_id": "712515194925219840"
    },
    ...
}
```