Python ftplib.FTP.mlsd()の代替手段:FTPサーバーの多様な状況に対応

2025-06-06

従来の LIST コマンド(ftplib.FTP.dir() などで利用)が、サーバーによって出力フォーマットが異なる可能性のある人間が読める形式のテキストを返すのに対し、MLSD (Machine List Directory) コマンドは、マシンが解析しやすいように標準化されたフォーマットで情報を返します。

ftplib.FTP.mlsd() の機能

mlsd() メソッドは、指定されたディレクトリ内の各ファイルおよびディレクトリについて、タプルのジェネレータを返します。各タプルは以下の2つの要素で構成されます。

  1. ファイルまたはディレクトリ名 (str): リストアップされたアイテムの名前です。
  2. 属性の辞書 (dict): そのアイテムに関する様々な属性をキーと値のペアで格納した辞書です。

属性の例

属性の辞書には、以下のような情報が含まれることがあります(サーバーの実装によって提供される属性は異なります)。

  • UNIX.gid: UNIXグループID
  • UNIX.uid: UNIXユーザーID
  • UNIX.mode: UNIX形式のパーミッションモード
  • unique: 一意な識別子
  • perm: 権限
  • modify: 最終更新日時(UTC)
  • size: ファイルのサイズ(バイト単位)
  • type: アイテムの種類(例: file, dir, link

使用例

from ftplib import FTP

ftp = None
try:
    # FTPサーバーに接続
    ftp = FTP('your_ftp_server.com')
    # ログイン(必要に応じて)
    ftp.login('your_username', 'your_password')

    # カレントディレクトリのMLSDリストを取得
    print("Current directory contents (MLSD):")
    for name, attrs in ftp.mlsd():
        print(f"Name: {name}")
        for attr_key, attr_value in attrs.items():
            print(f"  {attr_key}: {attr_value}")
        print("-" * 20)

    # 特定のディレクトリのMLSDリストを取得
    # 例: 'public_html' ディレクトリ
    print("\n'public_html' directory contents (MLSD):")
    for name, attrs in ftp.mlsd('public_html'):
        print(f"Name: {name}")
        for attr_key, attr_value in attrs.items():
            print(f"  {attr_key}: {attr_value}")
        print("-" * 20)

except Exception as e:
    print(f"An error occurred: {e}")
finally:
    if ftp:
        ftp.quit()
        print("FTP connection closed.")

mlsd() を使う利点

  • 豊富な情報: LIST よりも多くの属性情報(例: ファイルの種類、最終更新日時など)が提供される場合があります。
  • 堅牢性: LIST コマンドのようにサーバーの実装に依存する出力フォーマットの問題がありません。
  • プログラムによる解析の容易さ: 標準化されたキーと値のペアでデータが返されるため、プログラムでディレクトリの内容を簡単に解析し、必要な情報を取り出すことができます。
  • メモリ使用量: 非常に大きなディレクトリを mlsd() で一度に処理しようとすると、メモリを大量に消費する可能性があります。ジェネレータであるため、1つずつ処理されるため、一般的な LIST よりもメモリ効率が良い場合が多いですが、大量のファイルが存在する場合には注意が必要です。
  • サーバーのサポート: MLSD コマンドは、すべてのFTPサーバーでサポートされているわけではありません。サーバーが MLSD をサポートしていない場合、このメソッドを呼び出すとエラーが発生します。


ftplib.FTP.mlsd() の一般的なエラーとトラブルシューティング

ftplib.error_perm: 500 Unknown command. または 502 Command not implemented.

  • トラブルシューティング:
    • サーバーのMLSDサポートを確認する: FTPサーバーのドキュメントを確認するか、FTPクライアント(FileZillaなど)でサーバーに接続し、手動で MLSD コマンドを試してみることで、サーバーがサポートしているか確認できます。
    • 代替手段の使用: mlsd() が利用できない場合は、以下の代替手段を検討してください。
      • ftp.dir() の使用: これは LIST コマンドを実行し、人間が読める形式のリストを返します。ただし、サーバーによって出力フォーマットが異なるため、プログラムで解析するのはより困難です。
        # 例: ftp.dir() を使ってディレクトリリストを取得
        lines = []
        ftp.dir('.', lines.append) # カレントディレクトリをリストし、各行をlinesに追加
        for line in lines:
            print(line)
        # ここから各行をパースしてファイル名や属性を抽出する必要がある
        
      • ftp.nlst()ftp.voidcmd('MDTM <filename>') の組み合わせ: nlst() でファイル名のリストを取得し、各ファイルに対して MDTM コマンド(最終更新日時を取得するコマンド)を送信することで、タイムスタンプなどの情報を個別に入手できます。MDTM もRFC 3659で定義されていますが、MLSD よりも広くサポートされている場合があります(特に vsftpd など)。
        # 例: nlst() と MDTM を使ってファイル名と最終更新日時を取得
        files = ftp.nlst('.') # カレントディレクトリのファイル名リスト
        for filename in files:
            try:
                # MDTM コマンドで最終更新日時を取得
                # 応答は通常 "213 YYYYMMDDhhmmss" 形式
                response = ftp.voidcmd(f'MDTM {filename}')
                timestamp_str = response.split(' ')[1]
                print(f"File: {filename}, Modified: {timestamp_str}")
            except Exception as e:
                print(f"Could not get MDTM for {filename}: {e}")
        
      • サーバー設定の変更: サーバーの管理権限がある場合は、MLSD コマンドをサポートするようにサーバーソフトウェア(例: ProFTPDのmod_factsなど)を構成することを検討してください。
  • 原因: 最も一般的なエラーの一つです。FTPサーバーが MLSD コマンドをサポートしていない場合に発生します。MLSD はRFC 3659で定義された比較的新しい(2007年)コマンドであり、すべてのFTPサーバー、特に古いサーバーや特定の軽量なサーバー(例: vsftpdの一部の設定、IISのデフォルト設定など)ではサポートされていない場合があります。

タイムアウトエラー (socket.timeout または TimeoutError)

  • トラブルシューティング:
    • タイムアウト値の調整: FTP オブジェクトの作成時、または set_debuglevel() の前などに ftp.timeout 属性を設定することで、タイムアウト時間を延長できます。
      ftp = FTP('your_ftp_server.com', timeout=30) # 30秒に設定
      
    • パッシブモード(PASV)の問題: FTPは、コマンドチャネルとは別にデータチャネルを確立します。mlsd() のようなデータ転送を伴うコマンドでは、通常パッシブモードが使用されます。ファイアウォールやNAT環境では、パッシブモードで開かれるデータポートが適切にルーティングされないことがあります。
      • ftp.set_pasv(True) を明示的に設定する: ftplib はデフォルトでパッシブモードを使用しますが、明示的に設定することで確実性を高められます。
      • ファイアウォール設定の確認: クライアント側(スクリプトを実行しているマシン)とサーバー側双方のファイアウォールが、FTPのデータポート範囲を許可しているか確認します。
      • FTPサーバーのパッシブポート範囲の確認: サーバーが提供するパッシブモードのポート範囲が、ファイアウォールで適切に開かれているか確認します。
      • アクティブモード(PORT)の試行(非推奨): 特定の非常に限定された環境ではアクティブモードが機能する場合がありますが、クライアント側もデータポートを開放する必要があるため、ほとんどの現代のネットワーク環境では推奨されません。ftp.set_pasv(False) で試せますが、通常は問題を引き起こす可能性が高いです。
    • ネットワーク接続の安定性: 安定したインターネット接続を使用しているか確認します。
  • 原因: FTP接続が確立されているものの、データ転送(この場合はディレクトリリストの取得)中に一定時間応答がなかった場合に発生します。これは、ネットワークの問題(不安定な接続、パケットロス)、ファイアウォールの設定、FTPのパッシブモード(PASV)の問題などが原因で起こります。

認証エラー (ftplib.error_perm: 530 Login incorrect.)

  • トラブルシューティング:
    • ログイン情報の確認: ftp.login() メソッドで指定しているユーザー名とパスワードが正しいことを再確認します。
    • Anonymous FTP: 匿名FTPサーバーに接続する場合は、ユーザー名に 'anonymous'、パスワードに任意のメールアドレス(例: '[email protected]')を使用します。
    • 接続の順序: ftp.connect() の後に必ず ftp.login() を呼び出し、その後に mlsd() を呼び出すようにします。
  • 原因: mlsd() を呼び出す前に、FTPサーバーに正しくログインしていないか、認証情報(ユーザー名とパスワード)が間違っている場合に発生します。

AttributeError: 'FTP' object has no attribute 'mlsd'

  • トラブルシューティング:
    • Pythonのバージョンを確認: python --version でPythonのバージョンを確認します。
    • Pythonのバージョンをアップグレード: Python 3.3以降のバージョンにアップグレードすることを検討してください。
    • 代替手段の使用: アップグレードが不可能な場合は、上記で説明した ftp.dir()ftp.nlst() / ftp.voidcmd('MDTM ...') の組み合わせを使用します。
  • 原因: ftplib.FTP.mlsd() メソッドが利用できないPythonの古いバージョンを使用している場合に発生します。mlsd() はPython 3.3で追加されました。

ftplib.error_temp または ftplib.error_perm (汎用的なエラー)

  • トラブルシューティング:
    • デバッグレベルの向上: ftp.set_debuglevel(1) または ftp.set_debuglevel(2) を設定することで、FTPクライアントとサーバー間のやり取りを詳細に出力させることができます。これにより、エラーの原因となっているサーバーからの応答コードとメッセージを確認できます。
      ftp = FTP('your_ftp_server.com')
      ftp.set_debuglevel(2) # デバッグ出力を最大に設定
      ftp.login('user', 'pass')
      for name, attrs in ftp.mlsd():
          print(name, attrs)
      ftp.quit()
      
    • パスの確認: mlsd() に引数としてディレクトリパスを渡している場合、そのパスがサーバー上に存在するか、アクセス権があるかを確認します。パスが間違っていると 550 エラーなどが返されます。
    • 権限の問題: ログインしたFTPユーザーが、指定されたディレクトリの内容をリストアップする権限を持っているか確認します。
    • データ接続の問題: 425 Can't open data connection. のようなエラーは、パッシブモードの問題(上記参照)を示唆していることが多いです。
  • 原因: FTPサーバーから予期せぬ、またはエラーを示す応答コードが返された場合に発生します。具体的なコード(例: 550 No such file or directory.425 Can't open data connection. など)によって意味が異なります。
  • 外部FTPクライアントでのテスト: Pythonスクリプトで問題が発生した場合は、FileZillaなどのGUIベースのFTPクライアントを使用して、同じサーバー、同じ資格情報、同じパスで接続を試みてください。これにより、問題がPythonスクリプト固有のものなのか、それともFTPサーバー自体またはネットワーク設定の問題なのかを切り分けることができます。
  • 接続のクローズ: finally ブロックで ftp.quit() を呼び出し、FTP接続を確実に閉じることが重要です。これにより、リソースリークを防ぎ、サーバー側のアイドル接続を減らします。
  • エラーハンドリング: ftplib の操作はネットワークを介するため、常に try...except ブロックを使用して例外を適切に処理するようにしてください。


ftplib.FTP.mlsd() は、FTPサーバー上のディレクトリの内容を、マシンが解析しやすい標準化された形式で取得するために使われます。これは、ファイルやディレクトリの名前と、それに関連する**属性(辞書形式)**のペアを返します。

例 1: カレントディレクトリの内容をリストアップする

この例では、FTPサーバーに接続し、ログインした後、mlsd() を引数なしで呼び出して、カレントディレクトリのコンテンツをすべてリストアップします。

from ftplib import FTP
import os

# --- 設定(ご自身の環境に合わせて変更してください) ---
FTP_HOST = 'your_ftp_server.com'
FTP_USER = 'your_username'
FTP_PASS = 'your_password'
# ---------------------------------------------------

ftp = None # finallyブロックで確実にクローズするために初期化

try:
    print(f"Connecting to {FTP_HOST}...")
    ftp = FTP(FTP_HOST)
    ftp.set_debuglevel(1) # FTPコマンドのやり取りを表示(デバッグ用)

    print(f"Logging in as {FTP_USER}...")
    ftp.login(FTP_USER, FTP_PASS)
    print("Login successful.")

    print("\n--- Listing current directory contents using mlsd() ---")
    # mlsd() はジェネレータを返すので、forループでイテレートします
    # 各要素は (ファイル名, 属性辞書) のタプルです
    for name, attrs in ftp.mlsd():
        print(f"Item Name: {name}")
        print("  Attributes:")
        for key, value in attrs.items():
            print(f"    {key}: {value}")
        print("-" * 30) # 区切り線

except Exception as e:
    print(f"\nAn error occurred: {e}")

finally:
    if ftp:
        print("\nClosing FTP connection...")
        ftp.quit() # 接続を閉じる
        print("Connection closed.")

実行結果の例

Connecting to your_ftp_server.com...
*cmd* 'USER your_username'
*cmd* 'PASS *********'
*resp* '230 Login successful.'
Logging in as your_username...
Login successful.

--- Listing current directory contents using mlsd() ---
*cmd* 'MLSD'
*resp* '220 OK.'
Item Name: .
  Attributes:
    type: dir
    modify: 20240101120000
    perm: elcdfmps
    unique: 801g00001
------------------------------
Item Name: ..
  Attributes:
    type: cdir
    modify: 20240101100000
    perm: elcdfmps
    unique: 801g00002
------------------------------
Item Name: my_document.txt
  Attributes:
    type: file
    size: 1024
    modify: 20240515103000
    perm: r
    unique: 801g00003
    UNIX.mode: 0644
    UNIX.uid: 1001
    UNIX.gid: 1001
------------------------------
Item Name: photos
  Attributes:
    type: dir
    modify: 20240520154500
    perm: elcdfmps
    unique: 801g00004
    UNIX.mode: 0755
    UNIX.uid: 1001
    UNIX.gid: 1001
------------------------------
...
Closing FTP connection...
*cmd* 'QUIT'
*resp* '221 Goodbye.'
Connection closed.

解説

  • 属性辞書には、type (ファイルかディレクトリかなど)、size (ファイルサイズ)、modify (最終更新日時)、perm (パーミッション) など、様々な情報が含まれます。提供される属性はFTPサーバーによって異なります。
  • ftp.mlsd() は、ディレクトリ内の各アイテムについて、名前name)と属性の辞書attrs)をタプルとして返します。
  • ftp.set_debuglevel(1) を設定すると、FTPクライアントとサーバー間の実際のコマンドのやり取り (*cmd**resp*) が表示され、デバッグに役立ちます。

例 2: 特定のディレクトリの内容をリストアップし、ファイルのみをフィルタリングする

この例では、特定のサブディレクトリ (/path/to/remote_dir) の内容をリストアップし、その中からファイルのみを抽出し、その名前とサイズを表示します。

from ftplib import FTP
import os

# --- 設定(ご自身の環境に合わせて変更してください) ---
FTP_HOST = 'your_ftp_server.com'
FTP_USER = 'your_username'
FTP_PASS = 'your_password'
REMOTE_DIR = 'public_html' # リストアップしたいリモートディレクトリ
# ---------------------------------------------------

ftp = None

try:
    print(f"Connecting to {FTP_HOST}...")
    ftp = FTP(FTP_HOST)
    ftp.login(FTP_USER, FTP_PASS)
    print("Login successful.")

    print(f"\n--- Listing contents of '{REMOTE_DIR}' filtering only files ---")
    
    # mlsd() にパスを渡すことで、特定のディレクトリをリストアップできます
    for name, attrs in ftp.mlsd(REMOTE_DIR):
        # 属性辞書から 'type' キーを取得し、それが 'file' であるか確認
        if attrs.get('type') == 'file':
            file_size = attrs.get('size', 'N/A') # 'size' がない場合のデフォルト値
            print(f"  File: {name}, Size: {file_size} bytes")
        elif attrs.get('type') == 'dir':
            print(f"  Directory: {name}/")
        else:
            print(f"  Other: {name} (Type: {attrs.get('type')})")

except Exception as e:
    print(f"\nAn error occurred: {e}")

finally:
    if ftp:
        print("\nClosing FTP connection...")
        ftp.quit()
        print("Connection closed.")

解説

  • attrs.get('type') を使って、アイテムのタイプ('file''dir' など)を安全に取得しています。get() メソッドを使うことで、キーが存在しない場合にエラーではなく None(または指定したデフォルト値)が返されるため、より堅牢なコードになります。
  • ftp.mlsd(REMOTE_DIR) のように引数としてパスを渡すことで、カレントディレクトリ以外の特定のディレクトリをリストアップできます。

例 3: 最終更新日時でファイルをフィルタリングする

この例では、特定のディレクトリ内のファイルで、最終更新日時が特定のタイムスタンプよりも新しいものだけをリストアップします。

from ftplib import FTP
import datetime
import os

# --- 設定(ご自身の環境に合わせて変更してください) ---
FTP_HOST = 'your_ftp_server.com'
FTP_USER = 'your_username'
FTP_PASS = 'your_password'
REMOTE_DIR = 'downloads' # 検索対象のリモートディレクトリ
# ---------------------------------------------------

# 比較対象のタイムスタンプ (YYYYMMDDhhmmss 形式)
# 例: 2024年1月1日以降に更新されたファイルを探す
MIN_MODIFY_DATE_STR = '20240101000000'
min_modify_datetime = datetime.datetime.strptime(MIN_MODIFY_DATE_STR, '%Y%m%d%H%M%S')

ftp = None

try:
    print(f"Connecting to {FTP_HOST}...")
    ftp = FTP(FTP_HOST)
    ftp.login(FTP_USER, FTP_PASS)
    print("Login successful.")

    print(f"\n--- Listing files in '{REMOTE_DIR}' modified after {MIN_MODIFY_DATE_STR} ---")
    
    for name, attrs in ftp.mlsd(REMOTE_DIR):
        if attrs.get('type') == 'file':
            modify_str = attrs.get('modify')
            if modify_str: # modify 属性が存在する場合
                try:
                    # modify 属性は YYYYMMDDhhmmss 形式の文字列です
                    item_modify_datetime = datetime.datetime.strptime(modify_str, '%Y%m%d%H%M%S')
                    
                    if item_modify_datetime > min_modify_datetime:
                        print(f"  Newer File: {name}")
                        print(f"    Last Modified: {item_modify_datetime}")
                        print(f"    Size: {attrs.get('size', 'N/A')} bytes")
                except ValueError:
                    print(f"  Warning: Could not parse modify date for {name}: {modify_str}")
            else:
                print(f"  Warning: No modify date found for file: {name}")

except Exception as e:
    print(f"\nAn error occurred: {e}")

finally:
    if ftp:
        print("\nClosing FTP connection...")
        ftp.quit()
        print("Connection closed.")

解説

  • この例では、ファイルが持つ属性を基にした高度なフィルタリングの可能性を示しています。
  • strptime() 関数は文字列を日付/時刻オブジェクトに解析し、strftime() 関数はその逆を行います。フォーマットコード(例: %Y は年、%m は月)に注意してください。mlsd()modify 属性は通常 YYYYMMDDhhmmss 形式で提供されます。
  • datetime モジュールを使って、mlsd() が返す modify 属性の文字列を datetime オブジェクトに変換し、比較可能にしています。

これらの例を通じて、ftplib.FTP.mlsd() がFTPサーバーから構造化されたディレクトリ情報(ファイル名と属性辞書)を取得する上でいかに強力であるかが理解できたと思います。この情報を使って、ファイルの種類、サイズ、最終更新日時、権限などに基づいて様々な処理を行うことができます。



ftplib.FTP.mlsd() の代替手段

ftplib.FTP.mlsd() は非常に便利なメソッドですが、すべてのFTPサーバーが MLSD コマンドをサポートしているわけではありません(特に古いサーバー)。また、mlsd() が返す情報の形式が、必ずしも常に必要とされている形式ではない場合もあります。

以下に、mlsd() の主な代替手段を3つ紹介します。

ftplib.FTP.dir()

  • いつ使うか: MLSD が利用できず、かつ他の手段も使えない場合の最終手段として、または人間が内容を目視確認するだけでよい場合に限定されます。プログラムでパースする必要がある場合は、非常に複雑なロジックが必要になります。

  • 使用例:

    from ftplib import FTP
    
    # --- 設定(ご自身の環境に合わせて変更してください) ---
    FTP_HOST = 'your_ftp_server.com'
    FTP_USER = 'your_username'
    FTP_PASS = 'your_password'
    REMOTE_DIR = '.' # カレントディレクトリ
    # ---------------------------------------------------
    
    ftp = None
    try:
        ftp = FTP(FTP_HOST)
        ftp.login(FTP_USER, FTP_PASS)
        print(f"--- Listing contents of '{REMOTE_DIR}' using dir() ---")
    
        # dir() は各行をコールバック関数に渡すか、Noneの場合は標準出力にプリントします
        # ここではリストに格納するために lambda を使用
        lines = []
        ftp.dir(REMOTE_DIR, lambda line: lines.append(line))
    
        for line in lines:
            print(line)
    
        # ここから各行を解析して必要な情報を抽出するロジックが必要になります
        # 例: UNIX形式のリスト (ls -l) の場合
        # -rw-r--r--    1 user     group        1234 May 20 10:30 myfile.txt
        # のような行から、正規表現などを使って情報を取り出す
        # 注意: これは非常に困難で、エラーが発生しやすいです。
        print("\nNote: Parsing dir() output is complex due to varying formats.")
    
    except Exception as e:
        print(f"An error occurred: {e}")
    finally:
        if ftp:
            ftp.quit()
    
  • 特徴:

    • ほぼすべてのFTPサーバーでサポートされています。
    • LIST コマンドの出力はサーバーやOS (UNIX系、Windows系など) によってフォーマットが大きく異なるため、プログラムで正確にパースするのが非常に難しいです。
    • ファイル名、サイズ、最終更新日時、パーミッションなどの情報が含まれることが多いですが、その位置や形式は一貫していません。
  • 機能: 最も古くからある、一般的なディレクトリリスト取得コマンド (LIST コマンド) を実行します。サーバーは人間が読める形式の文字列を返します。

ftplib.FTP.nlst()

  • いつ使うか: ディレクトリ内のアイテム名だけが必要な場合に最適です。例えば、特定の名前のファイルが存在するかどうかを確認する、またはダウンロードするファイルのリストを事前に取得する場合などです。

  • 使用例:

    from ftplib import FTP
    
    # --- 設定(ご自身の環境に合わせて変更してください) ---
    FTP_HOST = 'your_ftp_server.com'
    FTP_USER = 'your_username'
    FTP_PASS = 'your_password'
    REMOTE_DIR = 'public_html' # リストアップしたいリモートディレクトリ
    # ---------------------------------------------------
    
    ftp = None
    try:
        ftp = FTP(FTP_HOST)
        ftp.login(FTP_USER, FTP_PASS)
        print(f"--- Listing names in '{REMOTE_DIR}' using nlst() ---")
    
        # nlst() はファイル名とディレクトリ名のリストを返します
        items = ftp.nlst(REMOTE_DIR)
    
        for item_name in items:
            print(f"Item Name: {item_name}")
    
    except Exception as e:
        print(f"An error occurred: {e}")
    finally:
        if ftp:
            ftp.quit()
    
  • 特徴:

    • dir() と同様に、ほぼすべてのFTPサーバーでサポートされています。
    • mlsd()dir() と異なり、サイズ、タイムスタンプ、パーミッションなどの詳細な属性は返しません。名前のリストのみです。
    • 結果はPythonのリストとして返されるため、プログラムでの処理が非常に簡単です。
  • 機能: ディレクトリ内のファイルとサブディレクトリの名前のみをリストとして取得します (NLST コマンド)。

ftplib.FTP.nlst() と ftplib.FTP.voidcmd('MDTM <filename>') の組み合わせ

  • いつ使うか: mlsd() が使えず、かつファイル名と最終更新日時が重要な場合に有効です。特にファイル数がある程度限定されている場合に適しています。

  • 使用例:

    from ftplib import FTP
    import datetime
    
    # --- 設定(ご自身の環境に合わせて変更してください) ---
    FTP_HOST = 'your_ftp_server.com'
    FTP_USER = 'your_username'
    FTP_PASS = 'your_password'
    REMOTE_DIR = '.' # カレントディレクトリ
    # ---------------------------------------------------
    
    ftp = None
    try:
        ftp = FTP(FTP_HOST)
        ftp.login(FTP_USER, FTP_PASS)
        print(f"--- Listing names and modification times in '{REMOTE_DIR}' ---")
    
        # まず nlsm() でファイル名とディレクトリ名のリストを取得
        items = ftp.nlst(REMOTE_DIR)
    
        for item_name in items:
            try:
                # MDTM コマンドで最終更新日時を取得
                # 応答は通常 "213YYYYMMDDhhmmss" 形式
                # voidcmd は応答文字列全体を返す
                response = ftp.voidcmd(f'MDTM {item_name}')
                # レスポンスからタイムスタンプ部分を抽出
                timestamp_str = response.split(' ')[1].strip()
                # datetime オブジェクトに変換
                mod_time = datetime.datetime.strptime(timestamp_str, '%Y%m%d%H%M%S')
                print(f"  Item: {item_name}, Modified: {mod_time}")
            except Exception as e:
                # MDTM がファイルでない場合や、エラーの場合
                print(f"  Item: {item_name}, Could not get modification time ({e})")
    
    except Exception as e:
        print(f"An error occurred: {e}")
    finally:
        if ftp:
            ftp.quit()
    
  • 特徴:

    • MDTM コマンドは MLSD ほどではないですが、LIST よりは新しく、多くのFTPサーバーでサポートされています(RFC 3659)。
    • nlst() と組み合わせることで、ファイル名と最終更新日時という、よく使われる2つの情報を比較的簡単に取得できます。
    • ただし、ファイルのサイズやパーミッションなど、他の属性は取得できません。
    • ファイル数が多い場合、各ファイルに対して個別にコマンドを発行するため、ネットワークのオーバーヘッドが大きくなる可能性があります。
  • 機能: nlst() でファイル名のリストを取得し、その後、各ファイルに対して MDTM (Modification Time) コマンドを個別に発行して、最終更新日時を取得します。

メソッドサーバーサポート返す情報解析の容易さネットワークオーバーヘッド
ftplib.FTP.mlsd()中 (新しいサーバー)名前 + 構造化された属性辞書低 (単一のデータ転送)
ftplib.FTP.dir()高 (ほとんど)人間が読めるテキスト行低 (困難)低 (単一のデータ転送)
ftplib.FTP.nlst()高 (ほとんど)名前のみのリスト低 (単一のデータ転送)
nlst() + voidcmd('MDTM')中~高名前 + 最終更新日時 (各ファイル個別)高 (ファイルごとにコマンド)

推奨されるアプローチの優先順位

  1. まず mlsd() を試す: 最も効率的で、構造化されたデータが得られます。
  2. mlsd() が利用できない場合、必要な情報に応じて nlst() または nlst() + MDTM を検討します。
    • 名前だけが必要なら nlst()
    • 名前と最終更新日時が必要なら nlst() + MDTM
  3. 上記すべてが不可能な場合、または非常に古いサーバーとの互換性が必要な場合に限り、dir() の出力をパースする複雑なロジックを検討します。しかし、これは推奨されません。