Python+Tweepyでメディアタイムラインの画像や動画を取得

# 概要
メディアタイムライン（ユーザーページの「メディア」を押した列）を取得したかったので、備忘録的に記事を書きます。

### 使用するもの
- Python 3.8.3
- TwitterAPI
- tweepy

(ちなみに初Python)

# 0.諸準備
TwitterAPI接続に必要なトークンとかPythonの準備は他の人の記事を参考にしました（丸投げ）

# 1.まずはTweepyを使ってみる
[こちらの記事を参考](https://note.com/mitsuru_h_cc/n/n6a4e0b2134a8)に、自分のタイムラインに対してお試し実行します。

取得できたツイートのtextをprintしてみると、結果が見えます。

![キャプチャ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/311940/725a1f1e-f431-6c59-a13e-4d1205279b94.png)

ツイートが取得できています。画像もソースコードのフォルダのimgフォルダに保存されてました。
しかし、RTしたツイートも混じっています。
ツイッターでメディアを見るとき、その人がRTしたものは表示されないはず、今回はそれと同じようにRTは除外したい。

# 2.RTを除外するため、include_rts=Falseを設定
[公式ドキュメント、user_timelineのAPI仕様](https://developer.twitter.com/en/docs/tweets/timelines/api-reference/get-statuses-user_timeline#)を見ると、APIのparamに、**include_rts**なるそのものずばりのパラメータがあります、これを**False**にします。
tweepyの場合、以下の通りに記述すれば設定できるようです

```tweetpytest.py
search_results = tweepy.Cursor(api.user_timeline, screen_name=key_account, include_rts=False).items(count_no)
```

これでRTが結果から除外されます。
注意点として、countパラメータと実際に取得できたツイート数が合いませんが、公式ドキュメントをもう一度見ると、どうやらRTも含めてタイムラインを取得した後に、paramの条件でツイートを選別してる模様。


# 3.tweepyの返却結果を解析
あとは返却されたtweepyの返却値をfor in でぶん回して画像や動画URLを取得し、保存メソッドに投げます。
手順は以下の通り

#### 1.**'extended_entities'**の存在を確認、なければメディアツイートではないので対象外

tweepyの返却値はItemIterator型で、子要素はStatus型、みたいです（デバッグで確認）。
list型ならlist.get('要素名')等で判定できますが、今回はクラスのプロパティになっているのでこのメソッドが使えません。
今回は**hasattrメソッド**で判定します。

```tweetpytest.py
if hasattr(result, 'extended_entities'):
```

これがなければメディアツイートではないので無視して次行に行きます。


#### 2.result['extended_entities']['media'][0] に 'video_info' があるかを確認、あればそれは動画かGIF、なければ画像確定

画像ツイートにはvideo_infoがないので、これで判定できます。

#### 3.result['extended_entities']['media'][0]['type'] = animated_gif であればGIF。[0]をmp4で保存

GIFも一応mp4で保存します。

#### 4.result['extended_entities']['media'][0]['video_info']['variants']をループ、binrateの一番大きいものをmp4を保存

```tweetpytest.py
                bitrate_array = []
                for movie in ex_media_video_variants:
                    bitrate_array.append(movie.get('bitrate',0))
                max_index = bitrate_array.index(max(bitrate_array))
                movie_url = ex_media_video_variants[max_index]['url']
```

サイズ、形式違いで要素が格納されており、どのインデックスに何が入っているかは保証されていないようです。
決め打ちだとサイズが小さかったり、形式がmp4でない動画URLを取得する場合もあります（ありました）。
そこで、for文で中身を解析し、一番binrateがもっとも大きいmp4の動画を落とせるようにします。

# 4.ソース
上記をまとめて、以下のようなソースになりました。

```tweetpytest.py
# coding: UTF-8
#!/usr/bin/python3

import json, config #標準のjsonモジュールとconfig.pyの読み込み
from requests_oauthlib import OAuth1Session #OAuthのライブラリの読み込み
import tweepy
import time
import datetime
import urllib.error
import urllib.request
import re
import sys, calendar
import update_tweetinfo_csv

CONSUMER_KEY = config.CONSUMER_KEY
CONSUMER_SECRET = config.CONSUMER_SECRET
ACCESS_TOKEN = config.ACCESS_TOKEN
ACCESS_SECRET = config.ACCESS_TOKEN_SECRET

FOLDER_PASS = 'img/'

# 認証
auth = tweepy.OAuthHandler(CONSUMER_KEY, CONSUMER_SECRET)
auth.set_access_token(ACCESS_TOKEN, ACCESS_SECRET)
api = tweepy.API(auth)

def download_file(url, file_name):
   urllib.request.urlretrieve(url, FOLDER_PASS + file_name)

key_account = input('Enter account name:')
count_no = int(input('Set search count:'))
search_results = tweepy.Cursor(api.user_timeline, screen_name=key_account, include_rts=False).items(count_no)

for result in search_results:
    if hasattr(result, 'extended_entities'): #resultが'extended_entities'属性を持っているか判定
        ex_media = result.extended_entities['media']
        tweet_id = result.id
        if 'video_info' in ex_media[0]:
            ex_media_video_variants = ex_media[0]['video_info']['variants']
            media_name = '%s-%s.mp4' % (key_account, tweet_id)
            if 'animated_gif' == ex_media[0]['type']:
                #GIFファイル
                gif_url = ex_media_video_variants[0]['url']
                download_file(gif_url, media_name)
            else:
                #動画ファイル
                bitrate_array = []
                for movie in ex_media_video_variants:
                    bitrate_array.append(movie.get('bitrate',0))
                max_index = bitrate_array.index(max(bitrate_array))
                movie_url = ex_media_video_variants[max_index]['url']
                download_file(movie_url, media_name)
        else:
            #画像ファイル
            for image in ex_media:
                image_url = image['media_url']
                image_name = image_url.split("/")[len(image_url.split("/"))-1]
                download_file(image_url + ':orig', image_name)

print('終了')
```

# 実行イメージ
ソースのあるフォルダの下にimgフォルダができ、その下に画像や動画が保存されています。
とりあえず今回の目的は達成です。
![キャプチャ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/311940/94fb5718-97f9-02ed-def3-681908e6ab02.png)

# 5.（おまけ）since_idについて
一応上記で完成なのですが、これだと毎回すべてのツイートが検索対象になります。
次回起動した時、今回よりも新しいツイートだけを検索したいという場合、下記paramを設定します。

```Python
search_results = tweepy.Cursor(api.user_timeline, screen_name=key_account, include_rts=False, since_id='前回最終ツイートID').items(count_no)
```

これで、「since_id<検索対象ID」の条件が追加されます。

先ほどのソースの最後に、最後のidをprintsearch_results = tweepy.Cursor(api.user_timeline, screen_name=key_account, include_rts=False, since_id=pre_last_tweet_id-1).items(count_no)

ループの終端で最終ツイートIDを保持しておき、ユーザーIDと紐づけてテキストファイルか何かに保存しておけば、検索総数が減らせそうです。

# 6.余談
初めはtweepyを使わずにAPIを叩くソースにしてましたが、一度のリクエストで200件までしか取得できない制限に引っかかりました。
since_idとmax_idを取得しつつアクセスするループを組もうとしましたが、tweepyなら普通に取れるので、途中でtweepyを使用するソースに変更しました。

このソースをベースに、もう少し便利に画像保存するアプリケーションを組んでいくつもりです。

# 参考URL
[pythonでTwitter画像「一括」取得してみた](https://note.com/mitsuru_h_cc/n/n6a4e0b2134a8)
[公式ドキュメント、user_timelineのAPI仕様](https://developer.twitter.com/en/docs/tweets/timelines/api-reference/get-statuses-user_timeline#)