前回、Dropbox APIを使って、DropboxにあるExcelファイルを定期的にメールするツールを作った。前回は手軽に使えるアクセストークンを取得して通信を行う方法を紹介した。今回は、前回作ったプログラムを改良して長期間に渡って安定してAPIアクセスを行うためのリフレッシュトークンを利用する方法を紹介しよう。

  • 長期間安定してDropbox APIが利用できる

    長期間安定してDropbox APIが利用できる

短命のアクセストークン問題について

前回(連載135回目)では、Dropbox APIを使ってファイルをメールするスクリプトを作成した。この時、開発者用サイトで生成したアクセストークンを使って、ファイルの一覧を取得したり、ファイルをダウンロードしたりしてみた。専用アプリをインストールしたり、ブラウザを開くことなく作業ができるので、自動処理を行うのにとても便利だった。

ところが、翌日、同じプログラムを実行してみると気付くのだが、「AuthError('expired_access_token(アクセストークンの期限切れ)')」というエラーが出てしまうことだろう。実は、DropboxのAPIでは、アクセストークンを使って、APIアクセスを行う場合、有効期限が設定されており、その有効期限が過ぎると、使えなくなってしまうのだ。

  • Dropbox APIのアクセストークンは短命

    Dropbox APIのアクセストークンは短命

そのため、長期間に渡って自動処理を行う場合、更新用のリフレッシュトークンを取得し、そのトークンを利用して、アクセストークンを再取得する処理を組み込む必要がある。

リフレッシュトークンについて

それでは、アクセストークンとリフレッシュトークンはどう違うのか、まとめてみよう。

まず、アクセストークンとは、Dropbox APIアクセスを行うためのトークンであり、有効期限が設定されているものだ。

これに対して、リフレッシュトークンとは、アクセストークンを更新するためのトークンであり、有効期限がないものだ。リフレッシュトークンを利用して、アクセストークンを再取得することができる。ただし、リフレッシュトークンを利用して、APIアクセスを行うことはできない。

それで、Dropbox APIクライアントは、次の図のように、リフレッシュトークンを利用してアクセストークンを取得し、そのトークンを使ってDropboxのストレージからファイルの取得などを行う。ただし、アクセストークンは有効期限が切れると使えなくなってしまうため、アクセストークンの有効期限が切れた場合には、リフレッシュトークンを利用してアクセストークンを再取得してから、改めてファイルを取得するという流れになる。

  • Dropbox APIクライアントの流れ

    Dropbox APIクライアントの流れ

つまり、リフレッシュトークンを利用するなら、長期間に渡って安定してAPIアクセスを行うことができるようになるということだ。

なお、PythonのDropboxパッケージを使うと、上記の処理を隠蔽してくれるので、とても便利だ。ただし、この場合でも、リフレッシュトークン自体は、事前に取得しておく必要がある。

リフレッシュトークンを取得するための準備をしよう

それでは、リフレッシュトークンを取得するために必要な情報を、Dropboxの開発者用サイトから取得しよう。まずは、こちらのDropboxの開発者用サイトにアクセスし、前回作成したアプリを選択しよう。

そして、キー(App key)とシークレット(App secret)をコピーしよう。なお、シークレットは隠されているので、表示させてからコピーする必要がある。

  • キーとシークレットをコピーしよう

    キーとシークレットをコピーしよう

取得したキーとシークレットを、それぞれ、環境変数のDROPBOX_APP_KEYとDROPBOX_APP_SECRETに設定しておこう。

Windowsの場合は、コントロールパネルの「システムとセキュリティ」→「システム」→「システムの詳細設定」→「環境変数」から、ユーザー環境変数に新しい変数を追加して設定することができる。

macOSやLinuxの場合は、シェルの設定ファイル(例えば、macOSなら「~/.zshrc」、Linuxなら「~/.bashrc」)に以下の内容を記述することで環境変数を設定することができる。ファイルを変更したら、「source ~/.zshrc」あるいは「source ~/.bashrc」コマンドを実行して、変更を反映させておこう。

export DROPBOX_APP_KEY="ここにキー"
export DROPBOX_APP_SECRET="ここにシークレット"

リフレッシュトークンを取得するためのプログラムを作ろう

以上で、長期間の自動処理のために必要なリフレッシュトークンを取得するための準備が整った。次に、リフレッシュトークンを取得する簡単なプログラムを作ってみよう。次のプログラムを「dropbox_auth.py」という名前で保存しよう。

"""Dropboxのリフレッシュトークンを取得するためのスクリプト"""
import os
import requests
import sys

# 環境変数からキーを取得する --- (※1)
DROPBOX_APP_KEY = os.getenv("DROPBOX_APP_KEY", "")
DROPBOX_APP_SECRET = os.getenv("DROPBOX_APP_SECRET", "")
if not (DROPBOX_APP_KEY and DROPBOX_APP_SECRET):
    print("環境変数 DROPBOX_APP_KEY と DROPBOX_APP_SECRET を設定してください。")
    sys.exit(1)

def get_refresh_token() -> str:
    """(1) Dropbox の認可コード取得用URLを表示する"""  # --- (※2)
    url_reftoken = f"https://www.dropbox.com/oauth2/authorize?client_id={DROPBOX_APP_KEY}&response_type=code&token_access_type=offline"
    print("以下のURLにアクセスして認可コードを取得してください:", url_reftoken)
    code = input("認可コードを入力してください: ").strip()
    return code

def get_dropbox_token(app_key: str, app_secret: str, auth_code: str) -> dict:
    """(2) 認可コードからリフレッシュトークンを取得する"""  # --- (※3)
    # URLとパラメータを準備
    url = "https://api.dropbox.com/oauth2/token"
    data = {
        "code": auth_code,
        "grant_type": "authorization_code",
        "client_id": app_key,
        "client_secret": app_secret,
    }
    # POSTリクエストを送信してトークンを取得 --- (※4)
    response = requests.post(url, data=data, timeout=30)
    print("status =", response.status_code)
    print("body   =", response.text)
    response.raise_for_status()
    obj = response.json()
    return {
        "access_token": obj.get("access_token", ""),
        "refresh_token": obj.get("refresh_token", ""),
    }

def dropbox_auth_flow():
    """Dropboxの認可コード取得からトークン取得までのフロー""" # --- (※5)
    code = get_refresh_token()
    token_data = get_dropbox_token(DROPBOX_APP_KEY, DROPBOX_APP_SECRET, code)
    print("Dropboxのアクセストークンとリフレッシュトークンを取得しました。")
    print("この値を環境変数(DROPBOX_REFRESH_TOKEN)に設定してからプログラムを実行してください。")
    print("refresh_token:", token_data.get("refresh_token", ""))

if __name__ == "__main__":
    dropbox_auth_flow()

最初に、プログラムを確認しよう。(※1)では、環境変数からキーを取得している。これにより、コードにキーを直接書かなくても、環境変数を設定するだけで動作するようになる。(※2)では、認可コードを取得するためのURLを表示している。ユーザーはこのURLにアクセスして、Dropboxの認可画面で許可を与えると、認可コードが発行されるので、それを入力する必要がある。(※3)では、認可コードからリフレッシュトークンを取得するための関数を定義している。(※4)では、POSTリクエストを送信してトークンを取得している。(※5)では、認可コードの取得からトークンの取得までのフローをまとめている。

それでは、プログラムを実行しよう。ターミナル(WindowsならPowerShell、macOS/Linuxならターミナル)で下記のコマンドを実行しよう。

# 依存ライブラリのrequestsをインストール
python -m pip install requests
# プログラムを実行
python dropbox_auth.py

このプログラムを実行すると、認可コードを取得するためのURLが表示されるので、ブラウザを起動して、そのURLにアクセスしよう。するとDropboxのログイン画面が出るのでログインしよう。すると、次の画面が表示されるので「続行」ボタンを押そう。すると、認可コードが表示される。なお、気をつけたい点だが、ここで表示されるコードは、リフレッシュトークンを得るためだけの認可コードであり、リフレッシュトークンではないので注意しよう。

  • プログラムを実行すると表示されるURLにブラウザでアクセスして認可コードを取得しよう

    プログラムを実行すると表示されるURLにブラウザでアクセスして認可コードを取得しよう

そして、この認可コードをターミナルに貼り付けて[Enter]キーを押そう。すると、PythonのプログラムからDropbox APIにアクセスが行われ、ここではじめてリフレッシュトークンを取得することができる。

ここで取得したリフレッシュトークンは、環境変数「DROPBOX_REFRESH_TOKEN」に設定しよう。これにより、次回以降のプログラムで利用することができるようになる。

先ほどと同じように、Windowsであれば、コントロールパネルの「システムとセキュリティ」→「システム」→「システムの詳細設定」→「環境変数」から、macOS/Linuxであればシェルの設定ファイルに以下の内容を追加しよう。

export DROPBOX_REFRESH_TOKEN="ここにリフレッシュトークン"

ここまで、いろいろなコードやトークンが出てきたので、表にまとめてみよう。

トークンの種類 有効期限 用途
認可コード (auth_code) 数分間(1回限り) リフレッシュトークンと交換するために使用
リフレッシュトークン 無期限(失効させるまで) 新しいアクセストークンを発行するために使用
アクセストークン 短い(数時間程度) 実際のファイル操作(API呼び出し)に使用

リフレッシュトークンを利用したファイル一覧の取得プログラム

これで、リフレッシュトークンを利用したプログラムを作成する準備が整った。前回作成したファイル一覧を取得するプログラムを、リフレッシュトークンを利用するように改良してみよう。次のコードを「dropbox_list_files.py」という名前で保存しよう。

"""ファイル一覧を取得するスクリプト(リフレッシュトークン対応版)"""
import os
import sys
import dropbox

# 環境変数からキーとリフレッシュトークンを取得する --- (※1)
DROPBOX_APP_KEY = os.getenv("DROPBOX_APP_KEY", "")
DROPBOX_APP_SECRET = os.getenv("DROPBOX_APP_SECRET", "")
DROPBOX_REFRESH_TOKEN = os.getenv("DROPBOX_REFRESH_TOKEN", "")
if not (DROPBOX_APP_KEY and DROPBOX_APP_SECRET and DROPBOX_REFRESH_TOKEN):
    print("環境変数 DROPBOX_APP_KEY, DROPBOX_APP_SECRET, DROPBOX_REFRESH_TOKEN を設定してください。")
    sys.exit(1)

# Dropboxクライアントを初期化する --- (※2)
dbx = dropbox.Dropbox(
    app_key=DROPBOX_APP_KEY,
    app_secret=DROPBOX_APP_SECRET,
    oauth2_refresh_token=DROPBOX_REFRESH_TOKEN,
)

# ルートのファイル一覧を得る --- (※3)
for f in dbx.files_list_folder(path="").entries:
    if isinstance(f, dropbox.files.FileMetadata):
        print("- ファイル:", f.name)
    elif isinstance(f, dropbox.files.FolderMetadata):
        print("- フォルダ:", f.name)

プログラムを確認してみよう。(※1)では、環境変数からキーとリフレッシュトークンを取得している。(※2)では、Dropboxクライアントを初期化している。ここで、リフレッシュトークンを渡すことで、アクセストークンの有効期限が切れた場合に、自動的にリフレッシュトークンを使ってアクセストークンを再取得する機能が有効になる。(※3)では、ルートのファイル一覧を取得して表示している。

プログラムを実行するには、ターミナルで下記のコマンドを実行しよう。

# 依存ライブラリのインストール
python -m pip install dropbox
# プログラムを実行
python dropbox_list_files.py

このプログラムを実行すると、Dropboxのルートにあるファイルとフォルダの一覧が表示されるはずだ。これで、リフレッシュトークンを利用して、アクセストークンを自動的に更新するように改良したファイル一覧取得プログラムが完成した。

前回作成したプログラムと見比べてみると分かるが、Dropboxクライアントの初期化の部分が少し変わっているだけで、あとはほとんど同じコードでファイル一覧を取得できていることが分かるだろう。これで、アクセストークンの有効期限を気にせずに、長期間に渡って安定してAPIアクセスを行うことができるようになった。

メール送信スクリプト

それでは、前回のプログラムを改造して、リフレッシュトークンに対応したスクリプトを作ってみよう。「dropbox_send.py」という名前で保存しよう。なお、以下と同じものをこちらにもアップロードしている。

"""DropboxからファイルをダウンロードしてGmailで送信"""
import os
import dropbox
import smtplib
from email.message import EmailMessage
import sys

# 定数の設定 --- (※1)
MAIL_TO = "mail@example.com"  # ★要変更 - メールを送信する宛先を指定
DROPBOX_FILE = "/売上データ.xlsx"  # ★要変更 - 送信したいファイル
DIR_ROOT = os.path.dirname(os.path.abspath(__file__))
LOCAL_FILE = os.path.join(DIR_ROOT, "売上データ.xlsx")

# 環境変数からトークンなどを読み取る --- (※2)
DROPBOX_APP_KEY = os.getenv("DROPBOX_APP_KEY", "")
DROPBOX_APP_SECRET = os.getenv("DROPBOX_APP_SECRET", "")
DROPBOX_REFRESH_TOKEN = os.getenv("DROPBOX_REFRESH_TOKEN", "")
GMAIL_ACCOUNT = os.getenv("GMAIL_ACCOUNT", "")
GMAIL_PASSWORD = os.getenv("GMAIL_PASSWORD", "")
if not (DROPBOX_APP_KEY and DROPBOX_APP_SECRET and DROPBOX_REFRESH_TOKEN):
    print("環境変数 DROPBOX_APP_KEY, DROPBOX_APP_SECRET, DROPBOX_REFRESH_TOKEN を設定してください。")
    sys.exit(1)
if not (GMAIL_ACCOUNT and GMAIL_PASSWORD):
    print("環境変数 GMAIL_ACCOUNT, GMAIL_PASSWORD を設定してください。")
    sys.exit(1)

def main():
    """メイン処理""" # --- (※3)
    if download_file(LOCAL_FILE, DROPBOX_FILE):
        send_mail()
    else:
        print("ファイルのダウンロードに失敗したため、メールは送信しませんでした。")

def download_file(local_path, dropbox_path) -> bool:
    """Dropboxからファイルをダウンロードする""" # --- (※4)
    dbx = dropbox.Dropbox(
        app_key=DROPBOX_APP_KEY,
        app_secret=DROPBOX_APP_SECRET,
        oauth2_refresh_token=DROPBOX_REFRESH_TOKEN,
    )
    try:
        dbx.files_download_to_file(local_path, dropbox_path)
        print("ファイルをダウンロードしました:", dropbox_path)
        return True
    except dropbox.exceptions.ApiError as err:
        print(f"APIエラーが発生しました: {err}")
        return False

def send_mail():
    """GMAILでメール送信""" # --- (※5)
    msg = EmailMessage()
    msg["From"] = GMAIL_ACCOUNT  # 送り主
    msg["To"] = MAIL_TO  # 送り先
    # 件名の指定
    msg["Subject"] = "Dropboxからダウンロードしたファイル"
    # 本文の指定
    msg.set_content("Dropboxからダウンロードしたファイルを添付します。")
    with open(LOCAL_FILE, "rb") as f:
        msg.add_attachment(f.read(),
            maintype="application",
            subtype="octet-stream",
            filename=os.path.basename(LOCAL_FILE))
    try:
        # ログインして送信 --- (※6)
        with smtplib.SMTP_SSL("smtp.gmail.com", 465) as smtp:
            smtp.login(GMAIL_ACCOUNT, GMAIL_PASSWORD)
            smtp.send_message(msg)
            print("メールを送信しました!")
    except Exception as e:
        print(f"メール送信中にエラーが発生しました: {e}")

if __name__ == "__main__":
    main()

このプログラムを実行するには、ターミナルで下記のコマンドを実行しよう。なお、前回の内容を元に、Gmailのメールアドレスとアプリパスワードを環境変数に設定しておく必要がある。また、メールの送信先(※1)やDropboxのファイルパスの定数を変更するのを忘れないようにしよう。

python dropbox_send.py

プログラムを実行すると、次の画面のように、Dropboxからファイルがダウンロードされて、Gmailでメールが送信される。

  • プログラムを実行するとDropboxからファイルをダウンロードしてメールする

    プログラムを実行するとDropboxからファイルをダウンロードしてメールする

簡単にプログラムを確認してみよう。(※1)では、メールの宛先や、Dropboxからダウンロードするファイルのパスなどの定数を設定している。(※2)では、環境変数からキーやリフレッシュトークン、Gmailのアカウント情報を取得している。(※3)では、メイン処理を定義している。(※4)では、Dropboxからファイルをダウンロードする関数を定義している。(※5)では、Gmailでメール送信する関数を定義している。(※6)では、SMTPサーバーにログインしてメールを送信している。

まとめ

以上、リフレッシュトークンを利用して、Dropboxからファイルをダウンロードし、Gmailで送信するスクリプトが完成した。これで、アクセストークンの有効期限を気にせずに、長期間に渡って安定してAPIアクセスを行うことができるようになった。これで、より実践的な自動処理を行うための基盤ができた。これをベースにして、スクリプトを改良して、自分のニーズに合った自動処理を作ってみよう。

自由型プログラマー。くじらはんどにて、プログラミングの楽しさを伝える活動をしている。代表作に、日本語プログラミング言語「なでしこ」 、テキスト音楽「サクラ」など。2001年オンラインソフト大賞入賞、2004年度未踏ユース スーパークリエータ認定、2010年 OSS貢献者章受賞。これまで50冊以上の技術書を執筆した。直近では、「大規模言語モデルを使いこなすためのプロンプトエンジニアリングの教科書(マイナビ出版)」「Pythonでつくるデスクトップアプリ(ソシム)」「実践力を身につける Pythonの教科書 第2版」「シゴトがはかどる Python自動処理の教科書(マイナビ出版)」など。