at backyard

Color my life with the chaos of trouble.

フォロー返ししてくれないアカウントをJavaScript(Node.js)を用いて特定する(Twitter API v2)

タイトルはネタであり、このあとに書く文章も適当な文章です。

まえがき

昔々、フォロー返しという文化がありました。Twitterで誰かをフォローするとお返しにフォロー返しをするというやつです。
その文化を利用してたくさんのアカウントがフォロワー数を増やし始めました。

勿論全てのアカウントがフォロー返しをしてくれるわけではありません。
フォロー返しをしてくれないアカウントをそのままにフォローし続けると、自分のアカウントのフォローとフォロワーの数の関係性が以下のようになってしまいます。

フォロー数 5000 / フォロワー数 2500

これではいけません。
なぜなら周りからはフォロー返しの文化を利用してアカウントのフォロワー数を増やそうとしていることがバレバレだからです。

さて、フォロー返しをしてくれないアカウントは フォロー返し文化を利用してフォロワー数を増やす という目的から見ると、完全に不要なアカウントとなります。

そういうアカウントはフォローを解除しないといけませんね。

さて、フォロー返しをしてくれないアカウント、というのはアカウントのフォロー画面から確認が可能です。 ですが、数千ものアカウントをフォローしている場合、これらをいちいち確認するのは面倒です。

退屈なことをはプログラムにやらせよう的な本があります。Pythonの本ですね。
(ちなみにこの本、今年の11月に第2版が出るそうです)

一歩先行くハイパフォーマンスなビジネスパーソンからの圧倒的な支持を獲得し、自作RPA本の草分けとして大ヒットしたベストセラー書の改訂版。劇的な「業務効率化」「コスト削減」「生産性向上」を達成するには、単純な繰り返し作業の自動化は必須です。

我々もフォロー返ししてくれないアカウントをプログラムを使って特定しましょう。

今回、言語はJavaScript(Node.js)を用います

Twitter API v2を使ってフォロー返ししてくれないアカウントを特定する

さて、本題です。
の前に、今回の内容、ならびに実行する環境について説明していきます。

Essentialプランを利用

まずTwitter API v2はEssentialプランを想定しています。
Essentialプランは登録が楽ですが、すぐに制限に到達してしまいます。
本当にすぐで、私が開発している最中にテストで動かしてみただけで制限に到達してしまうぐらいの天井の低さです。

※Essentialプランの登録については下記にも書いているのでよろしければ見てみてください。

shinshin86.hateblo.jp

本当は無料でもっと天井が高いプランもあるのですが、Twitterに申請用の文章(しかも英語)を書いて送るようなことをしなくてはなりません。
とっても面倒ですよね。楽したくてTwitter APIを使うのに英語の作文を考えるだけで疲れてしまうので、楽に作れるEssentialプランにしましょう。

もしあなたのアカウントのフォロー・フォロワー数が多い場合、制限に引っかかる可能性もあります。
その場合はスリープを挟んで上手く制限に引っかからないように感覚を空けてやりましょう。
この方法だと時間はかかってしまいますが、そもそも実行中はPCの前にいる必要もないので、コーヒータイムしたりApex Legendsで遊んでいたりすればよいのです。

ちなみにフォロワーなどのアカウントを取得するAPIの制限が具体的にどれぐらいなのかは、下記のドキュメントを見る限りはわかりませんでした。
(何れにせよ天井は低いです)

developer.twitter.com

フォロー返ししてくれないアカウントを特定していく

というわけで本題です。

今回の流れとしては、

  • フォローしているアカウントのリストを取得する
  • フォローされているアカウントリストを取得する
  • 上2つのリストを照らし合わせてフォロー返ししてくれていないアカウントを特定する

という非常に原始的な方法でフォロー返ししてくれないアカウントの数を特定していこうと思います。

これらの操作を一連のプログラムとして一度にやってもよいのですが、前述の通り、APIの制限に引っかかるので、一つ一つ実行していくという形でやっていきます。

フォローしているアカウントのリストを取得する

まずはコードを貼ります。

import { Client } from "twitter-api-sdk";
import { createObjectCsvWriter } from "csv-writer";

const sleep = msec => new Promise(resolve => setTimeout(resolve, msec));

const FOLLOWING_LIST_CSV_PATH = "following-list.csv";

const CSV_HEADER = [
  {id: "id", title: "id"},
  {id: "name", title: "name"},
  {id: "username", title: "username"},
  {id: "url", title: "url"},
  {id: "description", title: "description"},
];

const twitterParams = {
  max_results: 1000,
  "user.fields": ["id", "name", "username", "url", "description"]
}

const getUserId = async (client, userName) => {
  const { data } = await client.users.findUserByUsername(userName);
  return data.id;
}

const createFollowingList = async(userId, params) => {
  const followingListWriter = createObjectCsvWriter({
    path: FOLLOWING_LIST_CSV_PATH,
    header: CSV_HEADER
  })

  const followings = client.users.usersIdFollowing(userId, params);

  for await (const page of followings) {
    console.log(page.data);
    await followingListWriter.writeRecords(page.data);

    console.log('fetch following list...')
    await sleep(1000);
  }
}

// main
const client = new Client(process.env.BEARER_TOKEN);
const userName = "自分のアカウント名(@から始まるやつ)を入れてください";
const userId = await getUserId(client, userName);

await createFollowingList(userId, twitterParams);

このコードをコピペして、followingList.mjs というファイルに貼り付け、下記の要領で実行します。

BEARER_TOKEN=<取得したtoken> node followingList.mjs

すると、あなたのフォローしているアカウントのリストが following-list.csv というファイルに集計されていきます。
もしアカウントのフォロー数が多い場合、制限に到達してしまいます。

その場合は、 await sleep(1000); となっている箇所を await sleep(60000); などとして1分おきにリストを取得するようにすることで制限に到達しないでリストを取得し続けることが可能かもしれません。
これについては未検証なのですが、twitterのEssentialプランの内容的にこれぐらいなら大丈夫そうかなという、かなりアバウトなノリで書いていますのでその点ご了承ください。
言うまでもなく時間はとてもかかるので、その間は別のことをしておくのがおすすめです。

フォローされているアカウントリストを取得する

次は同じ要領で自身をフォローしてくれているフォロワーのリストを取得します。

import { Client } from "twitter-api-sdk";
import { createObjectCsvWriter } from "csv-writer";

const FOLLOWER_LIST_CSV_PATH = "follower-list.csv";

const sleep = msec => new Promise(resolve => setTimeout(resolve, msec));

const CSV_HEADER = [
  {id: "id", title: "id"},
  {id: "name", title: "name"},
  {id: "username", title: "username"},
  {id: "url", title: "url"},
  {id: "description", title: "description"},
];

const twitterParams = {
  max_results: 1000,
  "user.fields": ["id", "name", "username", "url", "description"]
}

const getUserId = async (client, userName) => {
  const { data } = await client.users.findUserByUsername(userName);
  return data.id;
}

const createFollowersList = async(userId, params) => {
  const followerListWriter = createObjectCsvWriter({
    path: FOLLOWER_LIST_CSV_PATH,
    header: CSV_HEADER
  })

  const follows = client.users.usersIdFollowers(userId, params);
  for await (const page of follows) {
    console.log(page.data);
    await followerListWriter.writeRecords(page.data);

    console.log('fetch follower list...')

    await sleep(1000);
  }
}

// main
const client = new Client(process.env.BEARER_TOKEN);
const userName = "自分のアカウント名(@から始まるやつ)を入れてください";
const userId = await getUserId(client, userName);

await createFollowersList(userId, twitterParams);

このコードをコピペして、followerList.mjs というファイルに貼り付け、下記の要領で実行します。

BEARER_TOKEN=<取得したtoken> node followerList.mjs

すると、今度はあなたのフォロワーのリストが follower-list.csv というファイルに集計されていきます。

フォロワー数が多くて制限に到達するケースは、上に書いたのと同じなので割愛します。

リストを照らし合わせてフォロー返ししてくれていないアカウントを特定する

これでフォローとフォロワーのリストをCSV形式で作成することができました。
今度はこの2つのリストを照らし合わせて、フォローしているけど私のことをフォローしてくれていないアカウントを特定していきます。

コードは以下です。

import { createObjectCsvWriter } from "csv-writer";
import { parse } from "csv-parse/sync";
import { existsSync, readFileSync } from "node:fs";

const FOLLOWING_LIST_CSV_PATH = "following-list.csv";
const FOLLOWER_LIST_CSV_PATH = "follower-list.csv";
const UNFOLLOW_USER_LIST_CSV_PATH = "unfollow-user-list.csv";

const CSV_HEADER = [
  { id: "id", title: "id" },
  { id: "name", title: "name" },
  { id: "username", title: "username" },
  { id: "url", title: "url" },
  { id: "description", title: "description" },
];

const getUnfollowUserList = async () => {
  if (
    !existsSync(FOLLOWER_LIST_CSV_PATH) || !existsSync(FOLLOWING_LIST_CSV_PATH)
  ) {
    throw new Error(
      `Not found csv file(${FOLLOWER_LIST_CSV_PATH} or ${FOLLOWING_LIST_CSV_PATH})`,
    );
  }

  const followerCsvData = readFileSync(FOLLOWER_LIST_CSV_PATH, "utf8");
  const followerRecords = parse(followerCsvData, { columns: true });
  const followerIdList = followerRecords.map(({ id }) => id);

  const followingCsvData = readFileSync(FOLLOWING_LIST_CSV_PATH, "utf8");
  const followingRecords = parse(followingCsvData, { columns: true });
  const unfollowUserList = followingRecords.filter(({ id }) =>
    !followerIdList.includes(id)
  );

  const unfollowUserListWriter = createObjectCsvWriter({
    path: UNFOLLOW_USER_LIST_CSV_PATH,
    header: CSV_HEADER,
  });
  await unfollowUserListWriter.writeRecords(unfollowUserList);

  return;
};

// main
await getUnfollowUserList();

このコードを notFollowerList.mjs にコピペして実行しましょう。
(ちなみに今更ですが、かなりコードは雑です。どこかで整理してGitHubにあげようかと思っています)

なお今度の処理はTwitter APIは使わないため、今までとは違い実行時に BEARER_TOKEN の指定は不要です。

このコードを実行すると、unfollow-user-list.csv というファイルが作られます。

そこにはフォローしているけどフォローはされていないアカウントのリストが表示されています。

これで準備はバッチリです。あとは手作業でフォロー返ししてくれていないアカウントのフォローを外していきましょう。

アンフォローもTwitter APIで行えばいいのでは?

おっしゃるとおり twitter-api-sdk 内にもアンフォローに関する関数は用意されていました。
が今はどうかわかりませんが、以前は1日内に特定の数以上のアカウントをアンフォローしているとバンされる、という噂がありました。
現状TwitterAPIに対するリクエストの制限を見ていても、おそらくアンフォローを一度大量に行うと制限が入る、またはアカウント凍結のリスクもあるかもしれません。

危険な橋は渡らないに越したことはないため、アンフォロー処理は手作業で、と書きました。

TODO

上に書いたコードを整理してそのうちGitHubにあげようと思っています。

上げたらこちらに追記します。