ftplib.FTP.size()

2025-06-06

以下に詳しく説明します。

ftplib.FTP.size(filename)

  • 注意点
    • size()メソッドはファイルに対してのみ機能し、ディレクトリのサイズを取得することはできません。
    • すべてのFTPサーバーがSIZEコマンドをサポートしているわけではありません。一部の古いサーバーや特定の構成のサーバーでは、このコマンドが機能しない場合があります。その場合、ファイルのサイズを取得するには、ファイルをダウンロードしてからローカルでサイズを確認するなどの別の方法を検討する必要があります。
    • バイナリモード(TYPE I)に設定してからsize()を呼び出すことが推奨されます。これにより、サーバーがファイルサイズを正しく解釈できるようになります。
  • 戻り値
    • 成功した場合、ファイルのサイズがバイト単位の整数で返されます。
    • FTPサーバーがSIZEコマンドをサポートしていない場合、またはファイルが見つからない場合、Noneが返されることがあります。
  • 引数
    • filename: サイズを取得したいファイルの名前(文字列)を指定します。
  • 目的
    FTPサーバー上のfilenameで指定されたファイルのサイズを問い合わせます。


None が返される

最も一般的なケースで、size() メソッドが None を返すことがあります。これはエラーではありませんが、期待するファイルサイズが得られない状況です。

  • トラブルシューティング

    • ftp.sendcmd('TYPE I') を試す
      size() を呼び出す前に、明示的にバイナリモードに設定してみてください。
      ftp.sendcmd('TYPE I')
      file_size = ftp.size(file_name)
      
    • ファイルパスの確認
      ftp.nlst()ftp.dir() を使って、実際にサーバー上のファイルリストを確認し、ファイル名やパスが正しいことを確認します。
    • FTPサーバーのドキュメントを確認
      使用しているFTPサーバーが SIZE コマンドをサポートしているか、特定の要件があるかを確認します。
    • 別の方法を検討する
      SIZE コマンドがサポートされていない場合、ファイルを一時的にダウンロードしてローカルでサイズを確認する、またはサーバーが提供する別のメタデータ取得方法(もしあれば)を探す必要があります。ただし、大きなファイルをダウンロードするのは効率的ではありません。
    • FTPサーバーが SIZE コマンドをサポートしていない
      SIZE コマンドはFTPプロトコルの標準コマンドではありません。多くのFTPサーバーがサポートしていますが、一部の古いサーバーや特定のサーバー実装ではサポートされていない場合があります。
    • ファイルが存在しない/パスが間違っている
      指定したファイル名やパスがサーバー上に存在しない場合、None が返されます。大文字小文字の区別も考慮する必要があります。
    • 権限がない
      ユーザーにそのファイルのサイズを取得する権限がない場合。
    • バイナリモードになっていない
      一部のサーバーでは、SIZE コマンドを使用する前にバイナリモード(TYPE I)に設定する必要があります。ASCIIモード(TYPE A)のままだと、正しくサイズを取得できない場合があります。

ftplib.error_perm (5xx エラー)

ftplib.error_perm 例外は、サーバーが永続的なエラー(通常は5xx系の応答コード)を返した場合に発生します。

  • トラブルシューティング

    • ファイルパスと権限の確認
      まず、ファイルパスが正確であること、そしてログインしているユーザーがそのファイルにアクセスする権限を持っていることを確認します。
    • set_debuglevel() でデバッグ情報を取得
      ftp.set_debuglevel(2) を設定すると、FTPコマンドのやり取りが詳細に表示されます。これにより、サーバーからの正確なエラーメッセージ(応答コードとテキスト)を確認でき、問題の原因を特定するのに役立ちます。
      import sys
      from ftplib import FTP, all_errors
      
      try:
          ftp = FTP('ftp.example.com')
          ftp.set_debuglevel(2) # デバッグレベルを2に設定
          ftp.login('username', 'password')
          ftp.sendcmd('TYPE I')
          file_name = 'non_existent_file.txt'
          file_size = ftp.size(file_name)
          # ...
      except all_errors as e:
          print(f"FTPエラー: {e}", file=sys.stderr)
      
    • FTPサーバーのログを確認
      サーバーサイドで何が起きているかを確認するために、FTPサーバーのログを調べることが非常に有効です。
  • 原因

    • ファイルが見つからない (550 Requested action not taken: File not found など)
      指定したファイル名やパスがサーバー上に存在しない。
    • 権限の問題 (550 Permission denied など)
      ユーザーにそのファイルへのアクセス権限がない。
    • 無効なコマンド (500 Syntax error, command unrecognized など)
      サーバーが SIZE コマンドを認識しない。これは None が返される場合と似ていますが、サーバーによっては明確なエラーを返すことがあります。

接続関連のエラー

size() を呼び出す前に、そもそもFTPサーバーへの接続が確立されていない、または途中で切断された場合に発生するエラーです。

  • トラブルシューティング

    • 接続情報の確認
      ホスト名、ポート(デフォルトは21)、ユーザー名、パスワードが正しいことを再確認します。
    • ファイアウォールの確認
      クライアント側、サーバー側、またはその間のネットワーク機器のファイアウォールがFTP接続(特にデータ接続)をブロックしていないか確認します。
    • timeout パラメータの使用
      FTP() オブジェクトの作成時や connect() メソッドでタイムアウトを設定し、応答がない場合にプログラムがハングアップするのを防ぎます。
      ftp = FTP('ftp.example.com', timeout=60) # 60秒のタイムアウト
      
    • try...except ftplib.all_errors
      ほとんどのFTP関連のエラーをキャッチするために、包括的な例外ハンドリングを使用します。
  • 原因

    • 接続失敗
      ホスト名、ポート、認証情報が間違っている。
    • ネットワークの問題
      ファイアウォール、ネットワーク障害、タイムアウトなど。
    • アイドルタイムアウト
      接続が長時間アイドル状態だったためにサーバーによって切断された。
  • トラブルシューティング

    • FTP オブジェクトの encoding パラメータ
      Python 3では、ftplib.FTP のコンストラクタで encoding パラメータを指定できます。
      ftp = FTP('ftp.example.com', encoding='shift_jis') # または 'euc-jp', 'utf-8' など
      
    • サーバーの文字コードを特定
      多くのFTPサーバーはUTF-8をサポートしていますが、古いサーバーではShift_JISやEUC-JPが使用されている場合があります。サーバー管理者に確認するか、一般的な文字コードを試してみてください。
  • 原因

    • FTPサーバーとクライアントの文字コードの不一致
      FTPプロトコル自体は文字コードを規定していません。サーバーが期待する文字コードとPythonコードで使用している文字コードが異なる場合、ファイル名が正しく解釈されません。

ftplib.FTP.size() のトラブルシューティングのキーポイントは以下の通りです。

  1. ftp.set_debuglevel(2) で詳細なログを確認する。 これが最も重要で、サーバーからの具体的な応答を確認できます。
  2. ftp.sendcmd('TYPE I') でバイナリモードに設定する。
  3. ファイル名とパスが正確であることを確認する。
  4. FTPサーバーが SIZE コマンドをサポートしているか確認する。
  5. 権限の問題ではないか確認する。
  6. try...except ftplib.all_errors で例外を適切に処理する。
  7. 必要に応じて文字コード設定を試す。


最もシンプルな例です。ファイルが存在し、サーバーが SIZE コマンドをサポートしている場合に機能します。



ftplib.FTP.size() が使えない/使わない場合

  1. FTPサーバーが SIZE コマンドをサポートしていない
  2. ftplib.FTP.size()None を返す、または error_perm を返す
  3. より詳細なファイル情報(最終更新日時、パーミッションなど)も同時に取得したい

このような場合に、以下の方法を検討できます。

ftplib.FTP.mlsd() を使用する (推奨される代替手段)

mlsd() (Machine Readable List Directory) コマンドは、FTPプロトコルの比較的新しい拡張機能であり、ファイルに関する構造化された情報を取得できます。これにより、ファイルのサイズだけでなく、タイプ(ファイルかディレクトリか)、最終更新日時、パーミッションなどを効率的に取得できます。

mlsd() は、イテレータを返し、各要素は (ファイル名, 属性の辞書) のタプルです。属性の辞書に 'size' キーが含まれていれば、それがファイルのサイズです。

利点

  • 多くの場合、SIZE コマンドよりも信頼性が高い。
  • ファイルとディレクトリの区別が容易。
  • size() よりも多くの情報(タイプ、日時など)を一度に取得できる。

欠点

  • すべてのFTPサーバーが MLSD コマンドをサポートしているわけではない(比較的モダンなサーバーでのみ利用可能)。

コード例

from ftplib import FTP, all_errors
import sys

FTP_HOST = 'ftp.example.com'
FTP_USER = 'your_username'
FTP_PASS = 'your_password'
TARGET_DIR = '/' # 調査対象のディレクトリ

print(f"FTPサーバー: {FTP_HOST}")
print(f"対象ディレクトリ: {TARGET_DIR}")

ftp = None

try:
    ftp = FTP(FTP_HOST)
    ftp.login(FTP_USER, FTP_PASS)
    ftp.sendcmd('TYPE I') # バイナリモード推奨

    print(f"ディレクトリ '{TARGET_DIR}' の内容を MLSD で取得中...")
    
    # ディレクトリを移動
    ftp.cwd(TARGET_DIR)

    file_info_list = []
    # mlsd() はイテレータを返す
    for name, attrs in ftp.mlsd():
        # 'type' 属性でファイルかディレクトリかを判別
        if attrs.get('type') == 'file':
            # 'size' 属性が存在すれば、それがサイズ
            size = attrs.get('size')
            if size:
                file_info_list.append((name, int(size)))
            else:
                file_info_list.append((name, "サイズ情報なし"))
        elif attrs.get('type') == 'dir':
            file_info_list.append((name + '/', "ディレクトリ"))
        else:
            file_info_list.append((name, "不明なタイプ"))

    print("\n--- MLSD によるファイルサイズ情報 ---")
    for name, size_info in file_info_list:
        if isinstance(size_info, int):
            print(f"  {name:<30}: {size_info} バイト ({size_info / (1024 * 1024):.2f} MB)")
        else:
            print(f"  {name:<30}: {size_info}")
    print("---------------------------------------")

except all_errors as e:
    print(f"エラー: FTP操作中に問題が発生しました: {e}", file=sys.stderr)
    print("MLSDコマンドがサーバーでサポートされていない可能性があります。", file=sys.stderr)

finally:
    if ftp and ftp.sock:
        try:
            ftp.quit()
            print("FTP接続を閉じました。")
        except all_errors as e:
            print(f"警告: FTP接続の終了中にエラーが発生しました: {e}", file=sys.stderr)

print("\n--- 処理終了 ---")

これは最も確実な方法ですが、ファイルのサイズが大きい場合には非効率的で、ネットワーク帯域を消費します。

手順

  1. ファイルを一時的にダウンロードする。
  2. ダウンロードした一時ファイルのサイズをローカルで取得する。
  3. 一時ファイルを削除する。

利点

  • 最も信頼性が高い。
  • どんなFTPサーバーでも(最低限のファイル転送機能があれば)動作する。

欠点

  • 一時ファイルの保存と削除の管理が必要。
  • 大きなファイルでは時間がかかり、ネットワークリソースを大量に消費する。