0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

tweepyのsearch_tweetsを使いやすくしたい

Last updated at Posted at 2022-05-16

はじめに

クラスをきれいに書く練習がてら、tweepyを使いやすいようにカスタマイズしたい。
tweepyを使うための諸々の準備は、公式ドキュメントや記事が充実しているので割愛します。

クラス定義

APIキー等を環境変数から取得します。インスタンス生成時にも渡せるが基本やらない。

class TwitterClient:
    def __init__(self,
                 consumer_key: str = None,
                 consumer_secret: str = None,
                 access_token: str = None,
                 access_token_secret: str = None,
                 wait_on_rate_limit: bool = True):
        self._consumer_key = \
            consumer_key or os.getenv('CONSUMER_KEY')
        self._consumer_secret = \
            consumer_secret or os.getenv('CONSUMER_SECRET')
        self._access_token = \
            access_token or os.getenv('ACCESS_TOKEN')
        self._access_token_secret = \
            access_token_secret or os.getenv('ACCESS_TOKEN_SECRET')
        self._create_api(wait_on_rate_limit=wait_on_rate_limit)

    def _create_api(self, wait_on_rate_limit: bool = True):
        try:
            self._auth = \
                tweepy.OAuthHandler(consumer_key=self._consumer_key,
                                    consumer_secret=self._consumer_secret)
            self._auth.set_access_token(key=self._access_token,
                                        secret=self._access_token_secret)
            self._api = tweepy.API(self._auth,
                                   wait_on_rate_limit=wait_on_rate_limit)
        except Exception as e:
            print(e)
            raise

次に、tweepy.API.search_tweetsを使うためのメソッドを定義します。本体はget_searched_tweetsです。

    _DATETIME_FORMAT = '%Y-%m-%d %H:%M:%S'
    _TIMEZONE = 'Asia/Tokyo'
    _RESULT_RECENT = 'recent'
    _FULL_TEXT = 'extended'

    @staticmethod
    def _get_query(keyword: Union[str, List[str]],
                   min_faves: int,
                   min_retweets: int,
                   exclude_retweets: bool,
                   filter_media: bool):
        _exclude_retweets = 'exclude:retweets' if exclude_retweets else ''
        _filter_media = 'filter:media' if filter_media else ''

        if isinstance(keyword, str):
            _keyword = f'#{keyword} OR {keyword}'
        elif isinstance(keyword, list):
            _keyword = ' AND '.join([f'(#{k} OR {k})' for k in keyword])

        return f'{_keyword} {_exclude_retweets} {_filter_media}' \
               f'min_faves:{min_faves} min_retweets:{min_retweets}'

    @staticmethod
    def _datetime_to_jst_str(dt: datetime.datetime):
        return dt.astimezone(timezone(_TIMEZONE)).strftime(_DATETIME_FORMAT)

    @classmethod
    def _tweet_summarize(cls, tweets: tweepy.models.SearchResults):
        searched_at = datetime.datetime.now().strftime(_DATETIME_FORMAT)

        return [
            dict(
                tweet_id=tweet.id,
                tweet_time=cls._datetime_to_jst_str(dt=tweet.created_at),
                account_id=tweet.user.id,
                user_name=tweet.user.name,
                account_name=tweet.user.screen_name,
                text=tweet.text
                if hasattr(tweet, 'text') else tweet.full_text,
                favorite=tweet.favorite_count,
                retweet=tweet.retweet_count,
                searched_at=searched_at
            )
            for tweet in tweets
        ]

    def get_searched_tweets(self,
                            keyword: Union[str, List[str]],
                            min_faves: int = 0,
                            min_retweets: int = 0,
                            exclude_retweets: bool = False,
                            filter_media: bool = False,
                            count: int = 10):
        query = self._get_query(keyword=keyword,
                                min_faves=min_faves,
                                min_retweets=min_retweets,
                                exclude_retweets=exclude_retweets,
                                filter_media=filter_media)

        try:
            tweets = self._api.search_tweets(q=query,
                                             count=count,
                                             result_type=self._RESULT_RECENT,
                                             tweet_mode=self._FULL_TEXT,
                                             include_entities=True)
            return self._tweet_summarize(tweets=tweets)

        except Exception as e:
            print(e)
            raise

実現したかったポイントとしては以下です。

  • ファボ/リツイート数、リツイート除外等のフィルタリングは引数で指定
  • 検索結果は 欲しい情報だけに絞る & 検索時刻を追加 したい

これらを呼び出し側でやるとごちゃごちゃするので隠したかった。

雑な説明:

  • get_searched_tweets
    • どんなキーワードをどんな条件で検索したいかを指定
    • _get_queryにそれらを渡してクエリ文字列を取得
    • API.search_tweetsを実行
    • 検索結果を_tweet_summarizeで加工して返す
  • _get_query
    • 指定されたキーワードや検索条件を、頑張ってクエリ文字列にまとめて返す
    • 現状は、複数キーワードの場合はAND検索される
  • _tweet_summarize
    • 使いやすそうな情報+検索時刻を、1ツイート毎にdictでまとめてlistで返す
  • _datetime_to_jst_str
    • タイムゾーンがUTCなdatetimeオブジェクトを、Asia/Tokyoに変換しstringで返す

使い方

例:

twitter = TwitterClient()
tweets = twitter.get_searched_tweets(keyword=keyword, exclude_retweets=True)

from pprint import pprint
pprint(tweets)

結果:

[{'account_id': xxx,
  'account_name': 'xxx',
  'favorite': xxx,
  'retweet': xxx,
  'searched_at': 'YYYY-MM-DD HH:mm:SS',
  'text': 'xxx',
  'tweet_id': xxx,
  'tweet_time': 'YYYY-MM-DD HH:mm:SS',
  'user_name': 'xxx'},

(snip)

]

呼び出し側がシンプルになりました。

tweets = twitter.get_searched_tweets(keyword='テスト', exclude_retweets=True, min_faves=5)

とすると、リツイートを除いてファボが5以上の、テスト または #テスト の文字列が含まれるツイートを検索しています。

#テスト OR テスト exclude:retweets min_faves:5 min_retweets:0

最後に

使える場面は限定されているので、用途によって拡張していこうと思います。

ただし、Twitter APIのラッパーのラッパーみたいになっているので、tweepyは使わずにそもそも自分で書いた方が良かったり…

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?