5
4

More than 1 year has passed since last update.

Twitterの特定のユーザーの画像を全てDLするだけ

Last updated at Posted at 2022-08-21

何を作ったか

友達に依頼されてTwitterの特定ユーザーのアップロードしている画像をまとめてダウンロードするプログラムを書いてみた。

流行りに乗ってdenoを使ってみた。

できたもの

クリックして表示

汚いコードでごめんなさい

import { download } from 'https://deno.land/x/download@v1.0.1/mod.ts';

const YOUR_BEARER_TOKEN = 'YOUR_BEARER_TOKEN';

const excludeNullFromKey = (obj: Record<string, string | undefined>) => {
  const newObj: Record<string, string> = {};
  Object.keys(obj).forEach((key) => {
    const value = obj[key];
    if (value != undefined) {
      newObj[key] = value;
    }
  });
  return newObj;
};

const getUserIdFromScreenName = async (screen_name: string) => {
  const res = await fetch(
    `https://api.twitter.com/2/users/by/username/${screen_name}`,
    {
      headers: {
        Authorization: 'Bearer ' + YOUR_BEARER_TOKEN,
      },
    }
  );
  const json = await res.json();
  return json.data.id;
};

const getImagesFromId = async (
  id: string,
  next?: string
): Promise<string[]> => {
  const params = excludeNullFromKey({
    max_results: '100',
    exclude: 'retweets',
    expansions: 'attachments.media_keys',
    pagination_token: next,
    'media.fields': 'url',
    'tweet.fields': 'attachments',
  });

  const raw_response = await fetch(
    `https://api.twitter.com/2/users/${id}/tweets?` +
      new URLSearchParams(params),
    {
      headers: {
        Authorization: 'Bearer ' + YOUR_BEARER_TOKEN,
      },
    }
  );
  const json_response = await raw_response.json();

  const images: string[] = json_response.includes.media.map(
    (item: { url: string }) => item.url
  );

  if (json_response.meta.next_token !== undefined) {
    const next_images = await getImagesFromId(
      id,
      json_response.meta.next_token
    );
    return [...images, ...next_images];
  }

  return images;
};

const downloadImages = async (images: string[]) => {
  for (let i = 0; i < images.length; i++) {
    const image = images[i];
    if (image !== undefined) {
      const file = image.split('/').pop();
      await download(image, { file, dir: './images/' });

      const progress = ((i + 1) / images.length) * 100;
      const progressCount = 20;
      Deno.stdout.write(
        new TextEncoder().encode(
          `${
            '#'.repeat(progress / 100 * progressCount) +
            ' '.repeat(progressCount - progress / 100 * progressCount)
          } ${Math.floor(progress)}%\r`
        )
      );
    }
  }
};

const userId = await getUserIdFromScreenName('SCREEN_NAME');

const userImages = await getImagesFromId(userId);

console.log(`${userImages.length} images found`);

await downloadImages(userImages);

console.log('all images downloaded');

ハマったポイント

同じようなことをやろうとしている人のお役に立てればと

エンドポイント探し

まずTwitterAPIのどのエンドポイントを使うのかを探すのがちょい大変だった。公式リファレンスが微妙に探しづらいので、ググってでてきたプログラムから拾ってきた。

  • スクリーンネーム → ユーザーID /2/users/by/username/:SCREEN_NAME
  • ユーザーID → ツイート一覧 /2/users/:id/tweets

この2つで実現できた。

画像のURLの取得

ツイートの取得は出来てもデフォルトだと画像がついてこないので若干書き換える必要がある。

/2/users/:id/tweetsでツイートを取得するときのクエリパラメータに以下の2つを追加する。

  • expansions=attachments.media_keys
  • tweet.fields=attachments
  • media.fields=url

そうするとレスポンスがこんな感じになる。

{
    "data": [
        {
            "text": "ツイートの内容",
            "id": "ツイートのID",
            "attachments": {
                "media_keys": ["メディアキー"]
            }
        },
        ...
    ],
    "meta": {...},
    "includes": {
        "media": [
            {
                media_key: "メディアキー",
                type: "...",
                url: "画像のURL"
            },
            ...
        ]
    }
}

今回はツイートと紐づける必要がなかったのでincludesの中のURLを取得してダウンロードしたが、ツイートと紐づけたい場合はツイートからメディアキーを取得してくる必要がある。

ツイートの取得を連続的にやる

レスポンスの中のnext_tokenという部分を取り出して、次回のリクエスト時にpagination_token=next_tokenというクエリパラメータを設定することで連続的にツイートを取得できる。

{
    "data": [...],
    ...
    "meta": {
        "next_token": "...",
        ...
    }
}

画像のダウンロード

denoのdownloadというライブラリを使った。詳しくは公式リファレンスを見ればわかると思うが、downloadというメソッドを使えばできる。

感想

TwitterAPIは微妙に使いづらい。

Denoは使いやすそうな感じはする。ただまだ環境が整ってない感じがあるので今後に期待。

5
4
0

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
5
4