🖼️

【.NET】Twitter APIを使って推しの画像に溺れたい②

2022/05/03に公開

今回はアプリの作成その1です。

いつも通りVisualStudioでC#コンソールアプリを立ち上げます。
私はまだ.NET 6.0に慣れていないので5.0で作りました。

いろいろ取得してみよう

パッケージ追加

「Tweetinvi」というパッケージを追加します。
最新の安定版で問題なく動作しました。

https://linvi.github.io/tweetinvi/dist/index.html

ユーザー取得

以前使われていたAPI v1.1ではアプリごとの認証だったらしいのですが、今回使うAPI v2はユーザーごとの認証になります。
つまり各種トークンを使って「どのアプリを」「どのユーザーが使うのか」という単位で認証をかけるようです。
なのでConsumerKeyとAccessTokenがあるのだと思います。

Main.cs
using System;
using System.Threading.Tasks;
using Tweetinvi;

namespace OshiPicCollector
{
    internal class Program
    {
        private static readonly string API_KEY = "aaa";
        private static readonly string API_SECRET = "bbb";
        private static readonly string ACCESS_TOKEN = "ccc";
        private static readonly string ACCESS_TOKEN_SECRET = "ddd";

        static async Task Main(string[] args)
        {
	    // 認証
            var userClient = new TwitterClient(API_KEY, API_SECRET, ACCESS_TOKEN, ACCESS_TOKEN_SECRET);
	    // ユーザー情報取得
            var user = await userClient.Users.GetAuthenticatedUserAsync();
            Console.WriteLine(user);
        }
    }
}
出力
moootoko_ojisan

よかったらフォローしてね🍙

https://twitter.com/moootoko_ojisan

ツイート取得

適当にツイートIDを取ってきます。

Main.cs(一部)
// 認証
var userClient = new TwitterClient(API_KEY, API_SECRET, ACCESS_TOKEN, ACCESS_TOKEN_SECRET);
// ツイートIDからツイート情報を取得
var publishedTweet = await userClient.TweetsV2.GetTweetAsync(1519876498676940801);
Console.WriteLine(publishedTweet.Tweet.Text);
出力
パンに驚くのがパンダ

米に驚くのがコメダ

ツイート情報から取得できるもの

かなり幅広く取得できるようです。

画像だけでなく動画、GIFアニメも取れるみたいです。
"possiblySensitive"はセンシティブ認定された場合表示させるか選択するアレですね。

もう少し中身を見ていけばいいね数やリツイート数も取れるみたいなので、活用していこうと思います。

試しにツイートしてみると

アクセスレベルを上げたときにAPI経由でpublish(ツイートすること)はしない設定にしたので、ツイートすると権限エラーになります。

Main.cs(一部)
var userClient = new TwitterClient(API_KEY, API_SECRET, ACCESS_TOKEN, ACCESS_TOKEN_SECRET);

 //ここで例外発生
var tweet = await userClient.Tweets.PublishTweetAsync("【テスト】Tweetinviを使って.NETからテストツイートしたよ!!");

Console.WriteLine("Published tweet: {0}", tweet.Text);

例外

理由

ツイートを検索しよう

普通に検索

Main.cs(一部)
var userClient = new TwitterClient(API_KEY, API_SECRET, ACCESS_TOKEN, ACCESS_TOKEN_SECRET);
var tweetResponse = await userClient.SearchV2.SearchTweetsAsync("絵かゆ");

Console.WriteLine(tweetResponse.Tweets.Length);
出力(タイミングによって値は変わります)
98

いつまで遡れるのか

Main.cs(一部)
var userClient = new TwitterClient(API_KEY, API_SECRET, ACCESS_TOKEN, ACCESS_TOKEN_SECRET);
var tweetResponse = await userClient.SearchV2.SearchTweetsAsync("絵かゆ"); //おがゆぅ~
// 取得した情報からツイートのリストを抜き出す
var tweetList = tweetResponse.Tweets;

// リストの先頭のツイートが一番古いとする
var oldestTweet = tweetList[0];

Console.WriteLine("実行日時: " + DateTime.Now.ToLocalTime().ToString("yyyy/MM/dd HH:mm:ss"));

for(int i = 1; i < tweetList.Length; i++)
{
    if(tweetList[i].CreatedAt.ToUnixTimeSeconds() < oldestTweet.CreatedAt.ToUnixTimeSeconds())
    {
        oldestTweet = tweetList[i];
    }
}

Console.WriteLine("取得したツイートで一番古いツイートの日時: " + oldestTweet.CreatedAt.DateTime.ToLocalTime().ToString("yyyy/MM/dd HH:mm:ss"));
出力(実行日時は2022/05/02)
実行日時: 2022/05/02 16:13:55
取得したツイートで一番古いツイートの日時: 2022/04/29 02:08:30

ハッシュタグにもよりますが、そこまで過去には遡れないようです。上記だと3日前ですね。

正解ははこちらの記事にかかれています。
https://zenn.dev/mochi_gu_ma/articles/24eb0a241fa783#v2について

Elevatedレベルではアクセス日時より7日前までが取得対象になるようです。
マジで全データ欲しい場合はスクレイピングのRPA組むことになるんですかね(白目)

実際に取得したツイートはどんなものが含まれているのか

Main.cs(一部)
var userClient = new TwitterClient(API_KEY, API_SECRET, ACCESS_TOKEN, ACCESS_TOKEN_SECRET);
var tweetResponse = await userClient.SearchV2.SearchTweetsAsync("絵かゆ");
var tweetList = tweetResponse.Tweets;

List<string> csvLines = new List<string>();
foreach (var tweet in tweetList)
{
    // 本文の改行は無視して1行で表示
    csvLines.Add(String.Format(
        "{0}, {1}",
        tweet.CreatedAt.ToLocalTime().ToString("yyyy/MM/dd HH:mm:ss"),
        tweet.Text.Replace("\n", "")
    ));
}

Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
File.WriteAllLines(@"C:\Users\XXX\Downloads\TweetSeatchTest.csv", csvLines, Encoding.GetEncoding("Shift_JIS"));

結果

ハッシュタグだけでなく、単純な「絵かゆ」もヒットしています。
また画像がないツイートもあります。
このあたりのチューニングは検索ワードに"#"をつけたり、ツイート情報の「画像があるか」のフラグを見て判別させたりする必要があります。

何件取得できるのか

Main.cs(一部)
// 検索ワードを"ほしまちぎゃらりー"に変更
var tweetResponse = await userClient.SearchV2.SearchTweetsAsync("ほしまちぎゃらりー");

見た感じ100件で打ち切りのようです。
これはTwitterAPIが1回のリクエストで100件までしか返さないためです。
100件だけでは推しのイラストに溺れることはできないので、Iteratorという機能が必要になります。

What is Iterator?

たくさんある結果を100件ずつ繰り返し取得することで、ほしいツイートを全部取得するというスグレモノです。
APIからのレスポンスに次の100件を返すためのURLが入っているらしく、それに対して繰り返しリクエストを送ってくれるという仕組みのようです。
https://linvi.github.io/tweetinvi/dist/twitter-api-v1.1/iterators.html

イメージとしては以下のような感じです。

全部でツイートが234件あるとして
1.最初の100件を取得 → 取得済み:100 残り:134
2.まだ残っているか判定
3.まだ残っているので100件取得 → 取得済み:200 残り:34
4.まだ残っているか判定
5.まだ残っているので100件取得 → 取得済み:234 残り:0
6.まだ残っているか判定
7.残っていないので終了

基本的な使い方は下記参照。
https://linvi.github.io/tweetinvi/dist/twitter-api-v2/search.html

Main.cs(一部)
var userClient = new TwitterClient(COMSUMER_KEY, CONSUMER_KEY_SECRET, ACCESS_TOKEN, ACCESS_TOKEN_SECRET);
var searchIterator = userClient.SearchV2.GetSearchTweetsV2Iterator("ほしまちぎゃらりー"); // キョウモチイサーイ

List<string> csvLines = new List<string>();

while (!searchIterator.Completed)
{
    // 次の100件を取得
    var searchPage = await searchIterator.NextPageAsync();
    // レスポンス抜き出し
    var searchResponse = searchPage.Content;
    // ツイート情報抜き出し
    var tweets = searchResponse.Tweets;
    
    foreach(var tweet in tweets)
    {
        // 本文の改行は無視して1行で表示
        csvLines.Add(String.Format(
            "{0}, {1}",
	    tweet.CreatedAt.ToLocalTime().ToString("yyyy/MM/dd HH:mm:ss"),
            tweet.Text.Replace("\n", "")
	));
    }
}

Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
File.WriteAllLines(@"C:\Users\XXX\Downloads\TweetSeatchTest.csv", csvLines, Encoding.GetEncoding("Shift_JIS"));

1998件取れました。
とはいえ無駄なツイートがたくさん含まれるので、やはりGetSearchTweetsV2Iteratorに渡すパラメータを工夫しなければなりません。
これについては次回の内容とします。

検索しすぎると...

Elevatedクラスでは月に200万ツイートの取得までという制限があります。
あんまりガツガツ検索していると、余裕があるように見えて制限ギリギリになりかねません。

次回

検索条件をチューニングして、なるべく取得件数をへらす工夫をします。

Discussion