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?

pythonで自分のグーグルドライブにファイル(test.txt)をアップロードする

Last updated at Posted at 2025-10-02

はじめに

業務でpython使っているので、試してみる
結果、AI使ったら結構簡単にできた。が意味は分かってない

会社でwebブラウザを使った何かのオペレーションをpythonで自動化する場合、AIがあればなんとかなりそうだなと思うのであった。
しかし、会社ではいろんなセキュリティ要素で障害が多そうではある。

今回GCPが必要になりますが、無課金で行えます。GCP使ってない人は使える状態にしましょう

Google Cloud プロジェクトを作成

Google Cloudにログインし、drive-upload-sample という新しいプロジェクトを作成

なぜ Google Cloud が必要か?

Google Drive は API 経由で操作するしかない
→ ログイン画面を自動化(Selenium 等)するのは Google のセキュリティ(2段階認証・CAPTCHA)でほぼブロックされる。

API を使うには Google が発行する「認証情報」 が必要
→ その認証情報を発行・管理する場所が Google Cloud Console。

つまり Google Cloud を経由しないと、Google公式に認められた「安全な入り口」が手に入らない。

Drive API を有効化

Google Drive API ページ
を開く。

「有効にする」ボタンをクリック。
→ これでこのプロジェクトで Drive API が使えるようになります。

これを使わないのとグーグルドライブにアクセスできない
Google Cloud = API鍵やOAuth認証情報を発行する場所

Drive API = 実際にファイルをアップロードするための窓口

OAuth 同意画面を設定

  1. 左メニュー「APIとサービス」→「OAuth同意画面」へ。

  2. 「外部」を選択(社内だけでなく自分用でもOK)。※内部は選択不可だった。ワークスペースがいるらしい

  3. アプリ名を入力(例: DriveUploader)。

  4. ユーザーサポートメール: 自分の Gmail アドレス。

  5. 「スコープの追加」で .../auth/drive.file を選択(最小権限でOK)。

  6. 「保存して次へ」を押して最後まで完了。

役割:「このアプリが Drive にアクセスしてもいいですか?」をユーザーに説明する画面を作る

内容:

アプリ名(例: DriveUploader)

サポート用メール

どの権限(スコープ)を要求するか(例: drive.file)

意味:ユーザーが「このアプリを信じて権限を渡す」ための同意プロセス。

👉 要するに ユーザーに見せる説明書きです。

OAuth クライアントIDを作成

  1. 左メニュー「APIとサービス」→「認証情報」。

  2. 「認証情報を作成」→「OAuthクライアントID」。

  3. アプリケーションの種類で「デスクトップアプリ」を選択。

  4. 名前は何でもOK(例: DriveUploaderClient)。

  5. 作成すると「JSONをダウンロード」できる。

  6. このファイルが credentials.json です。ダウンロードするファイルはcredentials.jsonにリネームする

image.png

役割:アプリを識別するIDと秘密鍵(credentials.json)を発行すること

内容:

クライアントID / クライアントシークレット

リダイレクトURL(今回なら http://localhost) ※今回はPCから実行するため

意味:Python スクリプトが Google に「私はこのアプリです」と名乗るための鍵。

👉 要するに アプリ本体の身分証明書です。

credentials.jsonとtest.txtをプログラムのあるディレクトリに配置

credentials.jsonは公開NG

依存インストール

pip install google-api-python-client google-auth-oauthlib google-auth-httplib2

google-api-python-client → 実際にDrive APIを叩く「手」

google-auth-oauthlib → 最初のログイン/同意フローをやる「玄関」

google-auth-httplib2 → 認証つきで安全に通信する「配達員」

コード作成

main.py
# main.py
# Google Drive に任意ファイルをアップロードする最小スクリプト
#
# 目的:
#   - ローカルファイル (デフォルト: test.txt) を Google Drive のマイドライブ直下にアップロードする
#   - 初回のみ OAuth2 認証フローでブラウザが開き、Googleアカウントの同意が必要
#   - 同意後に token.json が保存され、2回目以降は無人でアップロード可能
#
# 想定ユースケース:
#   - 定期的に生成されるレポート/ログを Drive に送る
#   - Selenium 等で取得した成果物を自動保存する
#
# 実行例:
#   python main.py                 # デフォルト: test.txt をアップロード
#   python main.py --file data.csv # 任意ファイルをアップロード
#
# 注意:
#   - credentials.json / token.json は機密情報なので絶対に共有しないこと
#   - API スコープは最小権限 (drive.file) にしてあり、安全性を確保
#
# 必要ライブラリ:
#   pip install google-api-python-client google-auth-oauthlib google-auth-httplib2

import argparse
import os
import sys
from pathlib import Path
from typing import Optional

# ---- Google Drive API 関連 ----
from googleapiclient.discovery import build
from googleapiclient.http import MediaFileUpload
from googleapiclient.errors import HttpError

# ---- Google 認証関連 ----
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request

# スコープ設定
# drive.file: 「このアプリが作成/開いたファイル」にのみアクセス可能 (最小権限で安全)
SCOPES = ["https://www.googleapis.com/auth/drive.file"]

# ファイル名定義
CREDENTIALS_FILE = "credentials.json"  # Google Cloud Console からダウンロードしたOAuthクライアント情報
TOKEN_FILE = "token.json"              # 初回認証後に自動生成されるアクセストークン


def get_drive_service() -> "googleapiclient.discovery.Resource":
    """
    Google Drive API サービスオブジェクトを返す。
    - token.json があればそれを使う
    - 期限切れなら refresh_token で更新
    - 初回 or エラー時は credentials.json から認証フローを開始
    """
    creds: Optional[Credentials] = None

    # 既存トークンの読み込み
    if os.path.exists(TOKEN_FILE):
        creds = Credentials.from_authorized_user_file(TOKEN_FILE, SCOPES)

    # トークンが無効 or 存在しない場合
    if not creds or not creds.valid:
        if creds and creds.expired and creds.refresh_token:
            try:
                # 有効期限切れ → refresh_token で再取得
                creds.refresh(Request())
            except Exception as e:
                print(f"[WARN] トークンリフレッシュ失敗。再同意が必要です: {e}")
                creds = None

        if not creds:
            # credentials.json が存在しない場合は即エラー
            if not os.path.exists(CREDENTIALS_FILE):
                print(f"[ERROR] {CREDENTIALS_FILE} が見つかりません。"
                      f"Google Cloud で OAuth クライアント(デスクトップ)を作成し、"
                      f"このファイルを同じフォルダに配置してください。")
                sys.exit(1)

            # 初回認証フロー開始(ブラウザが開き、同意画面+2FA)
            flow = InstalledAppFlow.from_client_secrets_file(CREDENTIALS_FILE, SCOPES)
            creds = flow.run_local_server(port=0)

        # 新しいトークンを保存(次回以降は無人で利用可能)
        with open(TOKEN_FILE, "w", encoding="utf-8") as token:
            token.write(creds.to_json())

    # 認証済みクライアントを返す
    return build("drive", "v3", credentials=creds)


def upload_file(service, filepath: Path, mime: Optional[str] = None) -> str:
    """
    Google Drive にファイルをアップロードする。
    - マイドライブ直下に保存 (parents=["root"] で明示指定)
    - 成功すると fileId を返す
    """
    file_metadata = {
        "name": filepath.name,
        "parents": ["root"],  # マイドライブ直下
    }
    media = MediaFileUpload(filepath.as_posix(), mimetype=mime, resumable=True)
    created = service.files().create(
        body=file_metadata,
        media_body=media,
        fields="id, name"
    ).execute()
    return created["id"]


def guess_mime_from_suffix(path: Path) -> Optional[str]:
    """
    簡易 MIME タイプ推定。
    - 今回は .txt のみ text/plain と判定
    - その他は None として Drive 側に推定を任せる
    """
    if path.suffix.lower() == ".txt":
        return "text/plain"
    return None


def main():
    # ---- 引数処理 ----
    parser = argparse.ArgumentParser(description="Upload a file to Google Drive (My Drive root).")
    parser.add_argument("--file", "-f", default="test.txt",
                        help="アップロードするファイルのパス (デフォルト: test.txt)")
    args = parser.parse_args()

    filepath = Path(args.file).resolve()

    # ファイル存在確認
    if not filepath.exists() or not filepath.is_file():
        print(f"[ERROR] アップロード元ファイルが見つかりません: {filepath}")
        sys.exit(1)

    try:
        # Drive API サービス初期化
        service = get_drive_service()

        # MIME タイプ判定
        mime = guess_mime_from_suffix(filepath)

        # アップロード実行
        file_id = upload_file(service, filepath, mime=mime)
        print(f"[OK] アップロード完了: name={filepath.name}, fileId={file_id}")

    except HttpError as e:
        # Google API 側のエラー
        print(f"[ERROR] Google API error: {e}")
        sys.exit(2)
    except Exception as e:
        # その他想定外エラー
        print(f"[ERROR] 想定外のエラー: {e}")
        sys.exit(3)


if __name__ == "__main__":
    main()

実行テスト

python main.py --file test.txt

エラー&修正&再実行

image.png
これは Google OAuth の「審査プロセス未完了」エラーですね。
ただし、あなたが自分のアカウントで使うだけなら 審査は不要 で回避できます。

自分のDrive用(個人利用) → 審査不要。テストユーザーに自分を追加すればOK。

他人のDrive用(配布・顧客利用) → 審査必要。Googleに申請し、承認されるまで本番公開できない。

image.png
テストユーザに自分のメアド追加

image.png

image.png

image.png

image.png

設定を直しました。コードは直してません

アップロード成功しました

credentials.json

中身の意味

client_id
アプリ(今回の Python スクリプト)を識別するための公開ID。
「Googleさん、私はこのアプリです」と名乗るためのもの。

project_id
Google Cloud 上のプロジェクト名。どのプロジェクトで発行された認証情報かを示す。

auth_uri
認証リクエストを投げるURL(Googleのログイン画面)。

token_uri
アクセストークン(短期利用の鍵)をもらうAPIのURL。

auth_provider_x509_cert_url
Googleの認証サーバーの公開鍵を取得するためのURL。署名検証に使う。

client_secret
アプリが Google に「正規のアプリです」と証明する秘密鍵。
(他人に渡してはいけない重要情報)

redirect_uris
認証後に Google が「ログインOK! このコードどうぞ」と結果を返す場所。
今回は http://localhost → ローカルPCのスクリプトに直接戻す。

実行するとtoken.jsonが生成される。

これは「初回ログイン(OAuth認証)完了後に自動生成される、アクセス用の鍵情報」です。
それぞれのフィールドの意味を簡潔に説明します👇

中身の意味

token
今まさに有効な アクセストークン。
有効期限が短く(通常1時間)、このトークンを使って Drive API にアクセスします。

refresh_token
アクセストークンが切れたときに、新しいトークンをもらうための「更新用の鍵」。
これがあるから、2回目以降は再ログイン不要で無人実行できます。
(これが一番重要。なくすと再認証が必要になります)

token_uri
アクセストークンを更新するときに叩く Google のAPIエンドポイント。

client_id / client_secret
最初に作った credentials.json の中身と同じ。
どのアプリが使っているかを示す ID と秘密鍵。

scopes
このトークンで許可されている権限の範囲。
今回は https://www.googleapis.com/auth/drive.file
「このアプリが作った/開いたファイルに限定して操作可能」という最小権限。

universe_domain
認証を提供している Google のドメイン。通常は googleapis.com。

account
認証したアカウントを表すフィールド(空欄でも問題なし)。

expiry
アクセストークンの有効期限。
この時間を過ぎると token は無効になるが、refresh_token を使って更新されます。

まとめると

token.json は 「Drive にアクセスするための通行証」

token → 今回のチケット(すぐ期限切れになる)

refresh_token → 自動で新しいチケットをもらうための鍵(ずっと大事)

これのおかげで、次回以降はログイン画面を開かずに自動アップロードできる

次は何も操作なくアップロードできた

PS C:\Users\tmori\python-repo> python rensyu1.py --file test2.txt
[OK] アップロード完了: name=test2.txt, fileId=14wVGmi7OtTXpDfZNewyIvjEJxVoT0YAe
PS C:\Users\tmori\python-repo> 

思ったより簡単にできた。会社の場合、どうするんだろうねと疑問は沸いた。自分のドライブじゃないから

次に、Yahoo! JAPAN を開いてスクリーンショットを撮り、Google Drive(マイドライブ直下)にアップロードする

コード作成

yahoo_screenshot_to_drive.py
# yahoo_screenshot_to_drive.py
#
# 処理概要:
#   1. SeleniumでChromeを起動してYahoo! JAPANを開く
#   2. ページロードを待機してスクリーンショットを撮影
#   3. Google Drive APIを使ってマイドライブ直下にアップロード
#
# 前提条件:
#   - 同じフォルダに credentials.json (OAuthクライアント: デスクトップアプリ) があること
#   - 初回実行時のみブラウザでGoogleログイン→2段階認証(スマホで「はい」) → 権限同意
#   - 同意後に token.json が生成され、以降は自動的にDriveにアップロード可能
#
# 実行例:
#   python yahoo_screenshot_to_drive.py
#   python yahoo_screenshot_to_drive.py --url https://www.yahoo.co.jp/ --width 1920 --height 1080
#
# 必要ライブラリ:
#   pip install selenium google-api-python-client google-auth-oauthlib google-auth-httplib2
#
# 注意:
#   - 初回実行時はブラウザが自動で開きます
#   - credentials.json / token.json は秘密情報なので絶対に共有しないこと

import argparse
import os
from pathlib import Path
from datetime import datetime
from typing import Optional

# ---- Google Drive API関連ライブラリ ----
from googleapiclient.discovery import build
from googleapiclient.http import MediaFileUpload
from googleapiclient.errors import HttpError
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request

# ---- Selenium関連ライブラリ ----
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.support.ui import WebDriverWait

# スコープ: drive.file = このアプリが作成/開いたファイルのみにアクセス(最小権限)
SCOPES = ["https://www.googleapis.com/auth/drive.file"]
CREDENTIALS_FILE = "credentials.json"
TOKEN_FILE = "token.json"


def get_drive_service():
    """
    Google Drive APIクライアントを返す。
    - token.json が存在し有効ならそれを使う
    - 無効/期限切れならリフレッシュ or 再同意
    - 初回実行時はブラウザを開いて同意フローを実施
    """
    creds: Optional[Credentials] = None

    # 既存のトークンがあれば読み込み
    if os.path.exists(TOKEN_FILE):
        creds = Credentials.from_authorized_user_file(TOKEN_FILE, SCOPES)

    # トークンが無効なら更新 or 再同意
    if not creds or not creds.valid:
        if creds and creds.expired and creds.refresh_token:
            try:
                creds.refresh(Request())  # 有効期限切れ → refresh_tokenで更新
            except Exception:
                creds = None
        if not creds:
            # credentials.json が無ければ終了
            if not os.path.exists(CREDENTIALS_FILE):
                raise FileNotFoundError(
                    f"{CREDENTIALS_FILE} が見つかりません。Google Cloud で発行して配置してください。"
                )
            # 同意フロー開始(ブラウザ起動)
            flow = InstalledAppFlow.from_client_secrets_file(CREDENTIALS_FILE, SCOPES)
            creds = flow.run_local_server(port=0)

        # 新しいトークンを保存(次回以降は無人で利用可能)
        with open(TOKEN_FILE, "w", encoding="utf-8") as f:
            f.write(creds.to_json())

    return build("drive", "v3", credentials=creds)


def upload_to_drive(service, file_path: Path, mime: Optional[str] = None) -> str:
    """
    指定ファイルをGoogle Driveのマイドライブ直下にアップロードする。
    :param service: Google Drive API サービスオブジェクト
    :param file_path: アップロード対象ファイルのPath
    :param mime: MIMEタイプ(例: "image/png")
    :return: Google Drive上の fileId
    """
    metadata = {"name": file_path.name, "parents": ["root"]}
    media = MediaFileUpload(file_path.as_posix(), mimetype=mime, resumable=True)
    created = service.files().create(body=metadata, media_body=media, fields="id,name").execute()
    return created["id"]


def new_chrome(headless: bool = True, width: int = 1366, height: int = 768):
    """
    SeleniumでChromeを起動する。
    - Selenium 4.6以降はSelenium Managerがchromedriverを自動解決
    - ヘッドレスモードも対応
    """
    opts = Options()
    if headless:
        opts.add_argument("--headless=new")  # 新しいheadlessモード
    opts.add_argument(f"--window-size={width},{height}")
    opts.add_argument("--disable-gpu")
    opts.add_argument("--no-sandbox")
    opts.add_argument("--disable-dev-shm-usage")
    opts.add_argument("--lang=ja-JP")

    # 言語設定を日本語に
    prefs = {"intl.accept_languages": "ja-JP,ja"}
    opts.add_experimental_option("prefs", prefs)

    return webdriver.Chrome(options=opts)


def wait_page_loaded(driver, timeout_sec: int = 20):
    """
    ページのロード完了(document.readyState == "complete")まで待機する。
    """
    WebDriverWait(driver, timeout_sec).until(
        lambda d: d.execute_script("return document.readyState") == "complete"
    )


def main():
    # ---- コマンドライン引数の設定 ----
    parser = argparse.ArgumentParser(description="Open Yahoo! JAPAN, take screenshot, upload to Google Drive.")
    parser.add_argument("--url", default="https://www.yahoo.co.jp/", help="対象URL (デフォルト: Yahoo! JAPAN)")
    parser.add_argument("--width", type=int, default=1366, help="ウィンドウ幅")
    parser.add_argument("--height", type=int, default=768, help="ウィンドウ高さ")
    parser.add_argument("--outfile", default=None, help="出力PNGパス(省略時は artifacts/screenshots に保存)")
    args = parser.parse_args()

    # スクショの保存先を決定
    out_dir = Path("artifacts/screenshots")
    out_dir.mkdir(parents=True, exist_ok=True)
    ts = datetime.now().strftime("%Y%m%d-%H%M%S")
    out_path = Path(args.outfile) if args.outfile else out_dir / f"yahoo_{ts}.png"

    driver = None
    try:
        # ---- ブラウザ起動&ページ読み込み ----
        driver = new_chrome(headless=True, width=args.width, height=args.height)
        driver.get(args.url)
        wait_page_loaded(driver, timeout_sec=30)

        # スクリーンショットを保存
        ok = driver.save_screenshot(out_path.as_posix())
        if not ok:
            raise RuntimeError("スクリーンショット保存に失敗しました。")
        print(f"[OK] スクリーンショット保存: {out_path}")

        # ---- Google Driveにアップロード ----
        service = get_drive_service()
        file_id = upload_to_drive(service, out_path, mime="image/png")
        print(f"[OK] Google Drive アップロード完了: fileId={file_id}")

    except HttpError as e:
        print(f"[ERROR] Google API error: {e}")
    except Exception as e:
        print(f"[ERROR] 想定外エラー: {e}")
    finally:
        if driver:
            driver.quit()


if __name__ == "__main__":
    main()

セレニウムインストール、ほかのライブラリも

pip install selenium google-api-python-client google-auth-oauthlib google-auth-httplib2
python -m pip show selenium
PS C:\Users\tmori\python-repo> python -m pip show selenium
Name: selenium
Version: 4.35.0
Summary: Official Python bindings for Selenium WebDriver
Home-page: https://www.selenium.dev
Author:
Author-email:
License: Apache-2.0
Location: C:\Users\tmori\AppData\Local\Programs\Python\Python313\Lib\site-packages
Requires: certifi, trio, trio-websocket, typing_extensions, urllib3, websocket-client
Required-by:
PS C:\Users\tmori\python-repo> 

実行。エラーっぽい出力があるが成功した。

PS C:\Users\tmori\python-repo> python yahoo_screenshot_to_drive.py --url https://www.yahoo.co.jp/ --width 1920 --height 1080

DevTools listening on ws://127.0.0.1:55244/devtools/browser/8f4ed5df-ed91-48b8-9d1f-90f51f7bad8c    
[17120:7400:1002/211902.184:ERROR:net\socket\ssl_client_socket_impl.cc:902] handshake failed; returnDevTools listening on ws://127.0.0.1:55244/devtools/browser/8f4ed5df-ed91-48b8-9d1f-90f51f7bad8c    
[17120:7400:1002/211902.184:ERROR:net\socket\ssl_client_socket_impl.cc:902] handshake failed; returned -1, SSL error code 1, net_error -107
[17120:7400:1002/211902.589:ERROR:net\socket\ssl_client_socket_impl.cc:902] handshake failed; returned -1, SSL error code 1, net_error -107
[17120:7400:1002/211902.589:ERROR:net\socket\ssl_client_socket_impl.cc:902] handshake failed; returned -1, SSL error code 1, net_error -107
[17120:7400:1002/211902.755:ERROR:net\socket\ssl_client_socket_impl.cc:902] handshake failed; returned -1, SSL error code 1, net_error -107
[17120:7400:1002/211902.755:ERROR:net\socket\ssl_client_socket_impl.cc:902] handshake failed; returned -1, SSL error code 1, net_error -107
[17120:7400:1002/211902.805:ERROR:net\socket\ssl_client_socket_impl.cc:902] handshake failed; returned -1, SSL error code 1, net_error -107
[17120:7400:1002/211903.159:ERROR:net\socket\ssl_client_socket_impl.cc:902] handshake failed; returned -1, SSL error code 1, net_error -107
[17120:7400:1002/211903.188:ERROR:net\socket\ssl_client_socket_impl.cc:902] handshake failed; returned -1, SSL error code 1, net_error -107
[17120:7400:1002/211929.023:ERROR:net\socket\ssl_client_socket_impl.cc:902] handshake failed; returned -1, SSL error code 1, net_error -107
[17120:7400:1002/211902.805:ERROR:net\socket\ssl_client_socket_impl.cc:902] handshake failed; returned -1, SSL error code 1, net_error -107
[17120:7400:1002/211903.159:ERROR:net\socket\ssl_client_socket_impl.cc:902] handshake failed; returned -1, SSL error code 1, net_error -107
[17120:7400:1002/211903.188:ERROR:net\socket\ssl_client_socket_impl.cc:902] handshake failed; returned -1, SSL error code 1, net_error -107
[17120:7400:1002/211929.023:ERROR:net\socket\ssl_client_socket_impl.cc:902] handshake failed; returned -1, SSL error code 1, net_error -107
[17120:7400:1002/211903.159:ERROR:net\socket\ssl_client_socket_impl.cc:902] handshake failed; returned -1, SSL error code 1, net_error -107
[17120:7400:1002/211903.188:ERROR:net\socket\ssl_client_socket_impl.cc:902] handshake failed; returned -1, SSL error code 1, net_error -107
[17120:7400:1002/211929.023:ERROR:net\socket\ssl_client_socket_impl.cc:902] handshake failed; return[17120:7400:1002/211903.159:ERROR:net\socket\ssl_client_socket_impl.cc:902] handshake failed; returned -1, SSL error code 1, net_error -107
[17120:7400:1002/211903.188:ERROR:net\socket\ssl_client_socket_impl.cc:902] handshake failed; returned -1, SSL error code 1, net_error -107
[17120:7400:1002/211929.023:ERROR:net\socket\ssl_client_socket_impl.cc:902] handshake failed; return[17120:7400:1002/211903.188:ERROR:net\socket\ssl_client_socket_impl.cc:902] handshake failed; returned -1, SSL error code 1, net_error -107
[17120:7400:1002/211929.023:ERROR:net\socket\ssl_client_socket_impl.cc:902] handshake failed; returned -1, SSL error code 1, net_error -107
[17120:7400:1002/211929.023:ERROR:net\socket\ssl_client_socket_impl.cc:902] handshake failed; return[17120:7400:1002/211929.023:ERROR:net\socket\ssl_client_socket_impl.cc:902] handshake failed; returned -1, SSL error code 1, net_error -107
[17120:7400:1002/211929.421:ERROR:net\socket\ssl_client_socket_impl.cc:902] handshake failed; returned -1, SSL error code 1, net_error -107
ed -1, SSL error code 1, net_error -107
[17120:7400:1002/211929.421:ERROR:net\socket\ssl_client_socket_impl.cc:902] handshake failed; returned -1, SSL error code 1, net_error -107
[17120:7400:1002/211929.421:ERROR:net\socket\ssl_client_socket_impl.cc:902] handshake failed; returned -1, SSL error code 1, net_error -107
ed -1, SSL error code 1, net_error -107
[OK] スクリーンショット保存: artifacts\screenshots\yahoo_20251002-211858.png
[OK] スクリーンショット保存: artifacts\screenshots\yahoo_20251002-211858.png
[14036:23832:1002/211930.437:ERROR:components\device_event_log\device_event_log_impl.cc:198] [21:19:30.437] USB: usb_service_win.cc:105 SetupDiGetDeviceProperty({{A45C254E-DF1C-4EFD-8020-67D146A850E0}30.437] USB: usb_service_win.cc:105 SetupDiGetDeviceProperty({{A45C254E-DF1C-4EFD-8020-67D146A850E0}, 6}) failed: 隕∫エ縺瑚ヲ九▽縺九j縺セ縺帙s縲・(0x490)
, 6}) failed: 隕∫エ縺瑚ヲ九▽縺九j縺セ縺帙s縲・(0x490)
[14036:21856:1002/211931.246:ERROR:google_apis\gcm\engine\registration_request.cc:291] Registration [14036:21856:1002/211931.246:ERROR:google_apis\gcm\engine\registration_request.cc:291] Registration response error message: DEPRECATED_ENDPOINT
[OK] Google Drive アップロード完了: fileId=1ofJkGkIP7HQrsSJON527NXaTIarrRfKT
PS C:\Users\tmori\python-repo>

途中のエラーっぽい出力の意味

  1. handshake failed; returned -1, SSL error code 1, net_error -107

これは Chrome が裏で開く DevTools の通信でSSLハンドシェイクが一時失敗したログです。

よくある原因:

サイトが多数のサブリソースにアクセスしていて、その中の1つがタイムアウト/SSL不一致

ローカル環境で証明書の検証に時間がかかる

実際にはページロードが続行できて、スクショも成功してるので「無害な警告」に近いです。

token.jsonの役割がわからないんだけど、credential.jsonだけで完結しないのか?なぜ両方必要なのか?

まず整理:credentials.json と token.json の違い

  1. credentials.json

Google Cloud Console からダウンロードするファイル

中身は「クライアントID」と「クライアントシークレット」などの固定情報

アプリが Google に対して

「私はこのアプリ(DriveUploader)です」
と名乗るための 身分証明書

ただしこれだけでは「誰がログインしているか(ユーザー情報)」は分からない

👉 あくまでアプリ側の身分証明だけ

  1. token.json

初回認証フローを通したあとに自動生成されるファイル

中身は「ユーザー本人がDriveへのアクセスを許可した証明(アクセストークン+リフレッシュトークン)」

アプリが Google に対して

「このユーザーはすでにログイン済みで、Driveアクセスを許可しています」
と伝えるための鍵

これがあるおかげで、2回目以降はブラウザログインやスマホで「はい」を押さなくても済む

👉 ユーザー本人のログイン情報を保持するキャッシュ

なぜ両方必要なのか?

credentials.json …「アプリの身元」を示す(固定)

token.json …「ユーザーの認可済み状態」を保持する(変動)

例えるなら:

credentials.json = 「社員証(会社発行のIDカード)」

token.json = 「その社員が入館ゲートを通るときにもらった通行証(期限つきパス)」

社員証だけあっても、入館するたびに受付で「本人確認+書類提出」が必要になります。
でも一度通行証をもらえば、次からはゲートをスッと通れる。
その「通行証」を保存しているのが token.json です。

もし token.json を使わなかったら?

スクリプトを実行するたびに

ブラウザが開く

Googleログイン

2FAでスマホ「はい」

権限許可
を毎回やる羽目になります。

自動化したいのに、毎回手動操作が必要になってしまう → 自動化の意味がなくなる

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?