twitterのリストからユーザ取得して投稿されているメディアを一括ダウンロードしてみた



#1. 何したの？
twitterAPIを用いて、リストから登録されているユーザを取得し、いいねが千件以上ついているツイートのメディアを取得できるようにしました。
ユーザごとにフォルダを分けて作ってくれる機能付き！

#2. なんで作ったの？
リストのユーザの数が多くなりすぎて、ツイートを追えなくなってきたので。。。
twitterのAPIを試してみたかったのと、SEになった記念でQiitaに何か投稿してみたかったので。

#3. 環境
- Python 3.9.6
- TwitterAPI
- tweepy

#4. メディアダウンロード
[Python+Tweepyでメディアタイムラインの画像や動画を取得](https://qiita.com/Jameson/items/184a065d96b50f9c5750)をかなり参考にしました。
変更点だけ解説します。

```
#ファイルをダウンロードする
import urllib.error
import urllib.request

def download_file(url, file_name, tweet_id):
   urllib.request.urlretrieve(url, FOLDER_PASS + file_name)
   print("-----{0}{1}-----".format(artName[count2 -1],count))
   print("https://twitter.com/poke_times/status/" + str(tweet_id))
   print(fav.text)
   time.sleep(1)
   #csvにツイートIDを書き込み
   with open("getTweetPass.csv", mode='a') as f:
      f.write('{0},'.format(tweet_id))
```

コードです。`print("https://twitter.com/poke_times/status/" + str(tweet_id))`では、後述する条件を満たしたツイートのリンクを表示できるようにしています。
//twiiter.com/の後に関係ないユーザidが入力されていますが、リンク先に飛んだ後、ツイートidに基づいたツイートに変換してくれるのでこのままで大丈夫です。

```
 #csvにツイートIDを書き込み
 with open("getTweetPass.csv", mode='a') as f:
      f.write('{0},'.format(tweet_id))
```
ここでは、一度取得したことのあるツイートを再取得しないようにcsvファイルにツイートidを記録しています。
これによって次回プログラムを実行した時に、余計なダウンロードを防ぎます。

#5. ユーザごとにフォルダを生成
```
import tweepy
import os

tweet_num = 検索するツイート数

lists = tweepy.Cursor(api.list_members,list_id = "取得したいリストID").items(tweet_num)

#ユーザごとにフォルダを生成する
for getList in lists:
   folderName = getList.name
   replace = folderName
   try:
      os.mkdir(folderName)
   except FileExistsError: #フォルダが作成済みの時
      continue
   except FileNotFoundError:#ユーザ名が対応していないとき
      replace = folderName.replace("/", " ")
      try:    
         os.mkdir("artist/" +replace)
      except FileExistsError:
         print("")
      finally:
         artists.append(getList.screen_name)
         artName.append(replace)
         continue
   except OSError:#おまじない
      continue     
   finally:
      artists.append(getList.screen_name)
      artName.append(replace)
```

ユーザが50人とかになってくるといちいちフォルダを作るのがめんどうになってくるので、ユーザごとにフォルダを作成する機能を作りました。
フォルダ名はtwiiterのユーザ名を引っ張ってくるようになっています。
たまにユーザ名を頻繁に変える人がいるので改善の余地があります。

```
except FileNotFoundError:#ユーザ名が対応していないとき
      replace = folderName.replace("/", " ")
```
ここでは、ユーザ名に/がある場合プログラムが相対パスの一部と誤認してしまうため追加しました。
ほとんどいないけどね。

#6. いざツイート取得
```
for art in artists:
   print(count2)#何人目のユーザかカウント
   print(art)
   favs = tweepy.Cursor(api.user_timeline, screen_name=art, exclude_replies=True, include_rts=False).items(tweet_num)
   FOLDER_PASS = 'artist/{0}/'.format(artName[count2]) #格納するフォルダ名
   count2 += 1
   count = 0 #何件目のダウンロードかカウント
    
   for fav in favs:
      tweet_id = fav.id
      with open("getTweetPass.csv") as f:
         if str(tweet_id) in f.read():
            f.close  
         elif hasattr(fav, 'extended_entities') : #favが'extended_entities'属性を持っているか判定
            ex_media = fav.extended_entities['media'] #ツイートの属性を取得
            if fav.favorite_count > 1000:
               count += 1
               if 'video_info' in ex_media[0]:
                  ex_media_video_variants = ex_media[0]['video_info']['variants']
                  media_name = '%s-%s.mp4' % (art, tweet_id)
                  if 'animated_gif' == ex_media[0]['type']:
                     #GIFファイル
                     gif_url = ex_media_video_variants[0]['url']
                     f.close
                     download_file(gif_url, media_name, tweet_id)
                  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']
                     f.close
                     download_file(movie_url, media_name, tweet_id)
               else:
                  #画像ファイル
                  for image in ex_media:
                     image_url = image['media_url']
                     count_str = str(count)
                     image_name = count_str.zfill(6) + str(tweet_id) + ".jpg"
                     count += 1
                     f.close
                     download_file(image_url + ':orig', image_name, tweet_id)
```

これも[Python+Tweepyでメディアタイムラインの画像や動画を取得](https://qiita.com/Jameson/items/184a065d96b50f9c5750)を参考にさせて頂きました。
`favs = tweepy.Cursor(api.user_timeline, screen_name=art, exclude_replies=True, include_rts=False, count=100).items(tweet_num)`では、artに格納されているユーザのTLを取得しています。
`exclude_replies=True, include_rts=False, `でリプライとリツイートを取得しないようにしています。
また、APIの仕様でTLで取得できるツイート数は3200件前後が上限らしいです。[(参考文献)](https://www.pytry3g.com/entry/python-twitter-timeline)
過去ツイの追い方は調べてもよく分からなかったです。助けてエロい人。

```
with open("getTweetPass.csv") as f:
   if str(tweet_id) in f.read():
      f.close  
```
ここで、csvに記録されたツイートidがあるかどうかを判定しています。同じツイートを取得したことがあった場合、この後の処理を行わず次のツイートの取得に移行します。

# 7. コード全体
```
import tweepy
import urllib.error
import urllib.request
import time
import os

# 認証に必要なキーとトークン
API_KEY = 'いい感じに'
API_SECRET = 'いい感じに'
ACCESS_TOKEN = 'いい感じに'
ACCESS_TOKEN_SECRET = 'いい感じに'

# APIの認証
auth = tweepy.OAuthHandler(API_KEY, API_SECRET)
auth.set_access_token(ACCESS_TOKEN, ACCESS_TOKEN_SECRET)

# キーワードからツイートを取得
api = tweepy.API(auth, wait_on_rate_limit=True, wait_on_rate_limit_notify=True, timeout= 120)
tweet_num = 3500 #検索するツイート数
count = 0 #ファイル名のカウンタ
count2 = 0 #ユーザ数のカウント

#ファイルをダウンロードする
def download_file(url, file_name, tweet_id):
   urllib.request.urlretrieve(url, FOLDER_PASS + file_name)
   print("-----{0}{1}-----".format(artName[count2 -1],count))
   print("https://twitter.com/poke_times/status/" + str(tweet_id))
   print(fav.text)
   time.sleep(1)
   #csvにツイートIDを書き込み
   with open("getTweetPass.csv", mode='a') as f:
      f.write('{0},'.format(tweet_id))
   
artists = []
artName = []
   
lists = tweepy.Cursor(api.list_members,list_id = "取得したいリストID").items(tweet_num)

#ユーザごとにフォルダを生成する
for art in artists:
   print(count2)#何人目のユーザかカウント
   print(art)
   favs = tweepy.Cursor(api.user_timeline, screen_name=art, exclude_replies=True, include_rts=False).items(tweet_num)
   FOLDER_PASS = 'artist/{0}/'.format(artName[count2]) #格納するフォルダ名
   count2 += 1
   count = 0 #何件目のダウンロードかカウント
    
   for fav in favs:
      tweet_id = fav.id
      with open("getTweetPass.csv") as f:
         if str(tweet_id) in f.read():
            f.close  
         elif hasattr(fav, 'extended_entities') : #favが'extended_entities'属性を持っているか判定
            ex_media = fav.extended_entities['media'] #ツイートの属性を取得
            if fav.favorite_count > 1000:
               count += 1
               if 'video_info' in ex_media[0]:
                  ex_media_video_variants = ex_media[0]['video_info']['variants']
                  media_name = '%s-%s.mp4' % (art, tweet_id)
                  if 'animated_gif' == ex_media[0]['type']:
                     #GIFファイル
                     gif_url = ex_media_video_variants[0]['url']
                     f.close
                     download_file(gif_url, media_name, tweet_id)
                  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']
                     f.close
                     download_file(movie_url, media_name, tweet_id)
               else:
                  #画像ファイル
                  for image in ex_media:
                     image_url = image['media_url']
                     count_str = str(count)
                     image_name = count_str.zfill(6) + str(tweet_id) + ".jpg"
                     count += 1
                     f.close
                     download_file(image_url + ':orig', image_name, tweet_id)
            
print('終了')        
```


#8. 実行結果
実行結果の画像は大人の事情で載せられませんでした察してください。

#9. 書いてみた感想
Qiitaに記事を書くのってすごい大変なんですね。というかなにか記事を書いてネットの大海に送りだすこと自体初めての経験でした。
記事をコンスタントに書ける人ってすごい。
少しでもなにかの参考になれば幸いです。
駄文失礼致しました。