僕が「いいね」したものをPythonで記録する

#何をするの？
Twitterで僕が「いいね」したツイート内容をExcelに出力します。
また、「いいね」したツイートに画像がある場合、PCに画像を保存します。

**なんでしようと思ったの？**
僕の記憶力が乏しいからです…！！！
僕はスマートフォンアプリでTwitterを活用しています。
良いと感じたもの・印象に残ったものは「いいね」をして、後で見返せるような使い方をしています。
ツイッター(アプリ)の「いいね」のデータ保持数には上限があり、過去「いいね」したツイートをすべて表示できるわけではありません。
また遡るときに、過去のものほど、いちいちスクロールする面倒なものはないはずです。

記憶力が良ければ遡る必要もありませんが、僕には必要です。
なので今回Excelに記録しておいて、なるべく早く遡れるようにしたいと思い、作りました。

<details><summary> 参考サイト1　　[Twitterいいね/お気に入りを全て確認するふぁぼろぐ(favolog)の使い方](https://appli-world.jp/posts/809)</summary>
【引用文】
Twitterアプリではこのお気に入りに入れたツイートを確認することはもちろん可能なのですが、
実際は数十件ほどしか過去のお気に入りツイートを見ることができません。


**既存サービスあるよね？**
アカウント連携は極力避けてます。
アカウント連携によるリスクの方が大きいと考えてるため、既存サービスの利用はしていません。

#環境

* Python 3.8.3
* Anaconda 1.9.12
* Spyder 4.1.4 

「この情報も必要じゃい(# ﾟДﾟ)」などありましたら、ご教授ください。

#インプット

* Twitter Developerで用いられる4つの個人データ (必須)
* 自分のTwitterアカウントID (必須)
* 前回取得最後のツイートID  (任意)

#アウトプット

* 「いいね」したツイートの情報を記録したExcelファイル (16要素/データ)
##
| action_date | last_action_tweet_id | favorite_tweet_no | tweet_created_time | tweet_sentence | tweet_image_filename1 | tweet_image_filename2 | tweet_image_filename3 | tweet_image_filename4 | tweet_image_url1 | tweet_iamge_url2 | tweet_iamge_url3 | tweet_iamge_url4 | tweet_video_url | tweet_url | save_folder |
|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
| 実行時刻 | 前回取得最後のツイートID | 「いいね」取得番号(降順) | ツイート作成時間 | ツイート文 | 画像ファイル名1 | 画像ファイル名2 | 画像ファイル名3 | 画像ファイル名4 | 画像URL1 | 画像URL2 |  画像URL3 |  画像URL4 | 映像URL | ツイートURL | 画像保存フォルダ名 |

* 「いいね」したツイートの画像


#事前準備
処理を実行するにあたって、下記2つの準備を行ないます。

1. Twitter Developersに登録し、下記4つの情報を取得しておきましょう。

> * CONSUMER_KEY
> * CONSUMER_SECRET
> * ACCESS_TOKEN
> * ACCESS_TOKEN_SECRET

<details><summary>登録方法＆情報の取得方法は下記サイトの記事をご参照ください。</summary>
参考サイト2　[TwitterAPI登録を記入時間30分で超簡単にパスする裏技(日本語訳付き) Qiita記事](https://qiita.com/newt0/items/66cb76b1c8016e9d0339)
参考サイト3　[TwitterAPIを申請して一発で承認されるまでの手順まとめ（例文あり）＋APIキー、アクセストークン取得方法](https://dev.classmethod.jp/articles/twitter-api-approved-way/)
参考サイト4　[2021年度版 Twitter API利用申請の例文からAPIキーの取得まで詳しく解説](https://www.itti.jp/web-direction/how-to-apply-for-twitter-api/)</details>
2. 出力するExcelを作成
　初回のみ作業です。2回目以降の実行ではExcelファイルファイルの作成は不要です。
　こちらは出力するExcelのもとになるものを作成しておきます。
　と言っても、[アウトプット](https://qiita.com/ttttttt/items/f8c310ab9c3d70e23334#%E3%82%A2%E3%82%A6%E3%83%88%E3%83%97%E3%83%83%E3%83%88)のキーを記載するくらいです。
![preparing_excel.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344537/d388ff28-f842-0166-e583-a51e5785fb39.png "お好みでウィンドウ固定とかもどうぞ")

 
#処理内容
概要フローを記載します。

1. 前回実行時の情報を取得する
1.1. 出力Excelより前回実行時に出力した最後のツイートID(last_action_tweet_id)を取得する

2. TwitterのAPIで「いいね」したツイートを取得し、Excelに出力する。
2.1.　"1.1."で取得したlast_action_tweet_idをもとに、自分が「いいね」したツイートを最大200ツイート取得する
2.2.　"2.1."で取得したツイートを1つずつ見ていく
　　2.2.1.　1つのツイートから必要な情報を取得する
　　2.2.2 　"2.2.1."で取得した情報をExcelに出力する
2.3. 画像があるツイート情報を返す

3. Excelに出力した内容を保存する
4. 画像のDL
4.1. 画像のあるツイートを1つずつ見る
　　4.1.1.　画像をDL
　　4.1.2.　DLした画像をフォルダ保存 
<br>
<details><summary>APIの使い方は以下のサイトをご参照ください</summary>
参考サイト5　 [Twitter Developers](https://developer.twitter.com/en)
参考サイト6 　[Twitter API (GET favorites/list)](https://developer.twitter.com/en/docs/twitter-api/v1/tweets/post-and-engage/api-reference/get-favorites-list)
</details>

#コード

* [事前準備](https://qiita.com/ttttttt/items/f8c310ab9c3d70e23334#%E4%BA%8B%E5%89%8D%E6%BA%96%E5%82%99)の1で取得したキーやトークンの情報を記載します。


```env.py
CONSUMER_KEY = "*****************"
CONSUMER_SECRET = "*****************"
ACCESS_TOKEN = "*****************"
ACCESS_TOKEN_SECRET = "*****************"
# 自分のユーザーID
USER_ID = "*****************"
```

<br>

* 全体像のコードです。<br>
それぞれのメソッドの詳細は、これ以降に記載しています。

```getFavoriteTweetList.py
from requests_oauthlib import OAuth1Session
from datetime import datetime
import time
import os.path
import openpyxl
import urllib.request
import json
import env

user_id = env.USER_ID
CONSUMER_KEY = env.CONSUMER_KEY
CONSUMER_SECRET = env.CONSUMER_SECRET
ACCESS_TOKEN = env.ACCESS_TOKEN
ACCESS_TOKEN_SECRET = env.ACCESS_TOKEN_SECRET

twitter = OAuth1Session(CONSUMER_KEY, CONSUMER_SECRET, ACCESS_TOKEN, ACCESS_TOKEN_SECRET)

def main():
    ##Excel初期設定##################################
    #ファイルの読込み
    folder_pass = "D:\\anaconda\\Spyder\\twitter_API"
    file_name = "twitter_favorite_list.xlsx"
    wb = openpyxl.load_workbook(folder_pass + "\\" + file_name)
    #シートの取得
    sheet = wb['Sheet1']
    
    #実行時の日時を取得し、画像保存するフォルダ名を生成する
    save_folder = datetime.now().strftime('%Y年%m月%d日 %H：%M：%S')
    
    #前回実施で最後に出力したツイートのIDを取得する
    since_id = sheet.cell(row = 2, column = 2).value
    
    #200個分のツイートリストをExcelに出力し、画像URLを取得する
    image_url_array = getFavoriteList(sheet, since_id, save_folder)
    
    #Excelの保存
    wb.save(folder_pass + "\\" + file_name)
    
    #画像の取得
    DLImageFromUrl(image_url_array, save_folder)
    
    print("completed")


if __name__ == '__main__':
    main()
    
```

<br>

* 200個分のツイートリストをExcelに出力し、画像URLを取得するメソッド

```getFavoriteTweetList.py
def getFavoriteList(sheet, since_id, save_folder):
    #ダウンロード用の画像URLを格納した配列(返り値)
    image_url_arra= []
    url = "https://api.twitter.com/1.1/favorites/list.json"
    #since_idの確認によるパラメータの設定
    if len(since_id) == 0:
        params = {'user_id':user_id, 'count':200}
    else:
        params = {'user_id':user_id, 'count':200, 'since_id':since_id}
    
    response = twitter.get(url, params=params)
    if response.status_code == 200:
        r = json.loads(response.text)
        
        ###########################################
        #200ツイートを1つずつみていく
        for i, tweet in enumerate(reversed(r)):
            #ユーザーの情報を保持
            user_object = tweet["user"]
            #ツイート文を保持
            text_object = tweet["text"]
            
            #動画URLの初期設定
            video_url = ""
            #画像URLの初期設定
            image_urls = []
            #ツイートurlの初期設定
            tweet_url = ""
            
            #ツイートに画像or動画がある場合
            if 'extended_entities' in tweet:
                media_object = tweet["extended_entities"]["media"]
                #画像か動画かの判定
                try:
                    #動画URLの取得
                    video_object = media_object[0]["video_info"]
                    video_url = video_object["variants"][0]["url"]
                    #tweet_urlの取得
                    tweet_url = media_object[0]["url"]
                except KeyError:
                    try:
                        #画像URLの取得
                        for image_object in media_object:
                            image_urls.append(image_object["media_url_https"])
                            #返り値に追加
                            image_url_arra.append(image_object["media_url_https"])
                        #tweet_urlの取得
                        tweet_url = media_object[0]["url"]
                    except KeyError:
                        return False
            else:
                media_object = ""
                tweet_url = "https://twitter.com/" + user_object["screen_name"] + "/status/" + tweet["id_str"]
            
            #Excelへの出力
            if i + 1 == len(r):
                #取得した中で最後のツイートの場合
                outputDocumetation(sheet, user_object, tweet, image_urls, video_url, tweet_url, save_folder, tweet["id_str"], save_folder)
            else:
                #最後のツイートではない場合
                outputDocumetation(sheet, user_object, tweet, image_urls, video_url, tweet_url, save_folder, "", "")
        ###########################################
        
        return image_url_arra

```


<br>

* 1レコード分のExcelへの出力するメソッド

```getFavoriteTweetList.py
def outputDocumetation(sheet, user_object, tweet, image_urls, video_url, tweet_url, save_folder, tweet_id, last_action_date):
    #行挿入
    sheet.insert_rows(2)
    
    #前回実行時の最終favorite_tweet_noを取得
    #最後のツイート番号から次のツイート番号を設定する
    check_str = sheet.cell(row = 3, column = 3).value
    if check_str is None:
        tweet_no = 1
    else:
        tweet_no = int(check_str) + 1
    
    ##Excelへの出力##############################
    #取得時刻の出力
    sheet.cell(row = 2, column = 1, value = save_folder)
    #取得ツイートIDの出力
    sheet.cell(row = 2, column = 2, value = tweet_id)
    #favorite_tweet_noの出力
    sheet.cell(row = 2, column = 3, value = tweet_no)
    #favorite_tweet_created_timeの出力
    sheet.cell(row = 2, column = 4, value = user_object["created_at"])
    #tweet_sentenceの出力
    sheet.cell(row = 2, column = 5, value = tweet["text"])
    #tweet_iamge_filenamesの出力
    image_count = 0
    for image_url in image_urls:
        image_file_name = image_url.split("/")
        sheet.cell(row = 2, column = 6 + image_count, value = image_file_name[-1])
        image_count += 1
    #tweet_image_urlの出力
    image_count = 0
    for image_url in image_urls:
        sheet.cell(row = 2, column = 10 + image_count, value = image_url)
        image_count += 1
    #tweet_video_urlの出力
    sheet.cell(row = 2, column = 14, value = video_url)
    #tweet_urlの出力
    sheet.cell(row = 2, column = 15, value = tweet_url)
    #画像保存フォルダの出力
    sheet.cell(row = 2, column = 16, value = save_folder)
    
    ##次ツイートの準備#############################
    tweet_no = + 1

```

<br>

* 画像を取得するメソッド

```getFavoriteTweetList.py
def DLImageFromUrl(image_url_array, save_folder):
    #画像を保存するフォルダの作成
    os.makedirs(save_folder,exist_ok=True)
    
    for url in image_url_array:
        urlName= url.split("/")
        #保存するフォルダパス
        file_Name = urlName[-1]
        print(file_Name)
        #ローカルフォルダに画像を保存
        urllib.request.urlretrieve(url, save_folder + "\\" + file_Name)
        time.sleep(1)

```


#出力内容

####実行後のフォルダはこんな感じです
![folder.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344537/74f14908-2379-9198-8089-07dc133cb6c1.png "実行後のフォルダ")


<br>
<br>

####画像を保存したフォルダはこんな感じになってます
(僕の趣味があられもない形で暴露してしましました…)
![image-folder.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344537/0d055886-b94f-222e-2188-6058bae11078.png)

<br>
<br>

####初回実行時はこんな感じになります　(見やすいようデータのグループ化をしています)
![excel3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344537/795fbd7c-73c5-fb2a-3b0b-d5019d1fd23b.png)

![excel4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344537/fe5d6479-cd1c-40ba-9648-36643fa1174b.png)



<br>
<br>

####複数回行うとこんな感じになります
![excel1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344537/6faa4e65-902e-0a43-0f68-db61e89922b4.png)
![excel2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344537/e3f98708-3b89-413f-c38f-bd918106fe62.png)


#あとがき
今回は、以下2つの理由で記事にしました。

* 私自身の覚書きとして残す　(どうやったんだっけ？となるので...)
* 誰かの参考になれば嬉しい

本当は実行するもの面倒なので定時バッチ処理にしてやろうかとも思いましたが、
そもそも日中PCを起動させておくのも面倒なのでもう一人の僕が却下しました。
(「クラウド使えよ！！」は、なしで)

さっそく活用する機会がありました。
物忘れが激しいのか、有効活用ができています！
(認めたくないものだな…)

####活用パターン１
保存した画像を投稿した人が誰なのか知りたくなりました。
そこで、保存したExcelで画像ファイル名を検索すると、対象の行がヒットしました。
その行には、ツイートのURLを保存しています。
そして、このURLにアクセスして、投稿者が誰かなのかを知ることができました。

####活用パターン２
趣味の1つに絵を描くことがあります。
ふと、
「影を描く際のコツが書いてあるツイートをいいねしたよなぁ。探そっ」
となりました。
そこで、保存したExcelのうち、ツイート内容に「影」「コツ」が含まれている行で絞りました。
みごとそのツイートを見つけ出すことができ、ツイート内容のすべてを確認することができました。
おかげで満足のいく絵が描けました(｀・ω・´)

####あるべき処理手順
実務では[処理内容](https://qiita.com/ttttttt/items/f8c310ab9c3d70e23334#%E5%87%A6%E7%90%86%E5%86%85%E5%AE%B9)にある手順は好ましくありません。
特に、手順2.2.2は手順2とは別の手順で処理すべきです。
本来、1つのメソッド内で、関連のない処理はできるだけ組み込まないことが理想です。
複雑性を強め、可変性を小さくしてしまい、扱いにくくなります。

しかし、今回は以下の理由で同じ処理内に施しました。

* 趣味の範囲であること
* どうせ同じ処理をするなら、別けるより同時に行なった方が効率が良い


####あれっ？と思ったこと
初回実行時に取得するツイート数は200で指定したはずなのに、実際取得した数は197でした。
この動作の原因は不明です。
ご存知の方がいましたら、ご教授頂けると幸いです。


#参考サイト
1. [Twitterいいね/お気に入りを全て確認するふぁぼろぐ(favolog)の使い方](https://appli-world.jp/posts/809)
2. [TwitterAPI登録を記入時間30分で超簡単にパスする裏技(日本語訳付き) Qiita記事](https://qiita.com/newt0/items/66cb76b1c8016e9d0339)
3. [TwitterAPIを申請して一発で承認されるまでの手順まとめ（例文あり）＋APIキー、アクセストークン取得方法](https://dev.classmethod.jp/articles/twitter-api-approved-way/)
4. [2021年度版 Twitter API利用申請の例文からAPIキーの取得まで詳しく解説](https://www.itti.jp/web-direction/how-to-apply-for-twitter-api/)
5. [Twitter Developers](https://developer.twitter.com/en)
6. [Twitter API (GET favorites/list)](https://developer.twitter.com/en/docs/twitter-api/v1/tweets/post-and-engage/api-reference/get-favorites-list)
7. [Twitterでいいね(かRT)した画像をひたすら保存する](https://qiita.com/kazuya-n/items/ee50eaad1949e81e95dc)



#自己紹介
プログラミングできることを志望してましたが、就職時は願い叶わず、ネットワークエンジニアをしています。
ただ、1年目はプログラマーをメインとして、働いていました。

* Webアプリケーション/iOSアプリケーションサービスの設計・コーディング・テスト・運用
* 設計は、機能設計と詳細設計の経験あり
* フロント側・クライアント側の処理をコーディング
* 開発環境でDBを壊した経験あり！　Σ＼(￣ｰ￣;)ｵｲｯ

今は趣味でプログラミングしています。
他にもいろいろやっているので、DLした画像や「いいね」したツイート内容に興味を持たれたら
ぜひ[僕のTwitterアカウント](https://twitter.com/nico_ac_chi)を見に来てください。