ftplib.FTP.ntransfercmd()

2025-06-06

FTPでは、ファイル転送のために制御コネクションデータコネクションという2つの異なる接続が使われます。

  • データコネクション:実際のファイルデータ(ダウンロードやアップロードされるファイルの内容)の転送に使われます。
  • 制御コネクション:コマンド(例: ログイン、ディレクトリ変更、ファイル転送開始コマンドなど)のやり取りに使われます。

ftplib.FTP.ntransfercmd() は、このデータコネクションの確立とデータ転送コマンドの実行を担当します。

ntransfercmd() の主な機能と挙動

  1. データコネクションの準備:

    • FTPには「アクティブモード」と「パッシブモード」という2つのデータ転送モードがあります。
    • アクティブモード: クライアントが特定のポートを開き、サーバーからの接続を待ちます。ntransfercmd()PORT または EPRT コマンドをサーバーに送信し、クライアントが接続を待つポート情報を伝えます。
    • パッシブモード: サーバーが特定のポートを開き、クライアントからの接続を待ちます。ntransfercmd()PASV または EPASV コマンドをサーバーに送信し、サーバーが接続を待つポート情報を取得します。
  2. データ転送コマンドの送信:

    • データコネクションの準備が完了した後、ntransfercmd() は指定されたFTP転送コマンド(例: RETR filename (ダウンロード), STOR filename (アップロード) など)を制御コネクションを通じてサーバーに送信します。
  3. ソケットとサイズの返却:

    • このメソッドは、データ転送に使用されるソケットオブジェクトと、転送が予定されているデータの期待されるサイズをタプル (socket, expected_size) として返します。
    • 返されたソケットを使って、実際のデータの送受信(recv()sendall() など)を行うことができます。
    • expected_size は、特にダウンロードの場合にファイルのサイズを示すことがありますが、常に正確な値が返されるとは限りません(特にディレクトリリスティングなどの場合)。

transfercmd() との違い

ntransfercmd() と似たメソッドに ftplib.FTP.transfercmd() があります。主な違いは返り値です。

  • ntransfercmd() はソケットオブジェクトとデータの期待されるサイズのタプルを返します。
  • transfercmd() はソケットオブジェクトのみを返します。
from ftplib import FTP

ftp = FTP('ftp.example.com')
ftp.login('username', 'password')

# パッシブモードを設定 (デフォルトはパッシブモードですが、明示的に設定することもできます)
ftp.set_pasv(True)

# ファイルダウンロードの準備
# ntransfercmd はデータ転送のためのソケットと期待されるサイズを返す
data_socket, expected_size = ftp.ntransfercmd('RETR my_file.txt')

with open('local_my_file.txt', 'wb') as f:
    while True:
        # ソケットからデータを読み込む
        chunk = data_socket.recv(8192) # 8192バイトずつ読み込む例
        if not chunk:
            break
        f.write(chunk)

data_socket.close() # データコネクションを閉じる
ftp.voidresp() # 転送完了応答を待つ

print("ファイル 'my_file.txt' のダウンロードが完了しました。")

ftp.quit()


接続関連のエラー (ftplib.error_perm, OSError, socket.timeout)

ntransfercmd() を呼び出す前に、FTPサーバーへの制御コネクションが確立され、ログインが成功している必要があります。これらのエラーは、通常、データコネクションの確立やコマンド送信時に問題が発生していることを示します。

  • socket.timeout

    • 原因: データコネクションの確立やデータ転送中に、指定されたタイムアウト時間内に応答がなかった場合。
    • トラブルシューティング:
      • ネットワークの遅延: ネットワークが不安定であるか、非常に遅い可能性があります。
      • タイムアウト値の調整: ftp = FTP(host, timeout=60) のように、FTP オブジェクトを初期化する際にタイムアウト値を長く設定してみます。
      • ファイアウォール/プロキシ: これらが通信を遅延させている可能性があります。
  • OSError: [Errno X] Connection refused

    • 原因: データコネクションの確立中に、クライアントからサーバーへの接続が拒否された場合。
    • トラブルシューティング:
      • ファイアウォール: クライアント側またはサーバー側のファイアウォールがデータコネクションのポートをブロックしている可能性があります。一時的にファイアウォールを無効にしてテストするか、必要なポート(パッシブモードであればサーバーから通知される範囲のポート、アクティブモードであればクライアントが使用するポート)を開放します。
      • ネットワーク設定: サーバーが正しいIPアドレスとポートでリッスンしているか確認します。
    • 原因: サーバーから永久エラー応答(500番台)が返された場合。これは、要求された操作がサーバー側で許可されていないか、無効なコマンドである場合によく発生します。例:
      • 530 Not logged in.:ログインしていません。
      • 550 Requested action not taken. File unavailable (e.g., file not found, no access).:指定されたファイルが存在しない、またはアクセス権がありません。
      • 425 Can't open data connection.:データコネクションを開けませんでした。これは、ファイアウォール、NAT、またはFTPモード(アクティブ/パッシブ)の不一致が原因でよく発生します。
    • トラブルシューティング:
      • ログイン状態の確認: ftp.login() が成功していることを確認してください。
      • ファイルパス/コマンドの確認: ダウンロード/アップロードしようとしているファイルパスが正しいか、そのファイルに対する適切な権限があるかを確認してください。RETRSTOR などのコマンドに誤りがないかも確認します。
      • FTPモードの確認(重要):
        • パッシブモード (PASV): クライアント側のファイアウォールやNAT環境では、通常パッシブモードが推奨されます。ftp.set_pasv(True) を呼び出してパッシブモードに設定してください。ほとんどの現代的なクライアントとサーバーはこのモードをサポートしています。
        • アクティブモード (PORT): アクティブモードはサーバーがクライアントに接続してくるため、クライアント側のファイアウォールで特定のポートを開放する必要があります。これは設定が難しいため、通常はパッシブモードを試すべきです。
        • サーバーによっては、PASVコマンドに対して不正なIPアドレス(例: 0.0.0.0)を返すことがあります。FileZillaなどの一部のFTPクライアントはこれを修正してくれますが、ftplib はそのまま失敗する場合があります。この場合は、サーバーの設定を見直すか、FTPサーバーの管理者に問い合わせる必要があります。
      • サーバーログの確認: FTPサーバー側のログを確認することで、なぜエラーが返されたのかの詳細な理由がわかることがあります。
      • 権限の問題: ユーザーがそのファイルやディレクトリに対する適切な読み取り/書き込み権限を持っているか確認します。

データ転送関連のエラー

ntransfercmd() が成功し、ソケットが返された後でも、実際のデータ送受信中にエラーが発生することがあります。

  • ファイルが空になる、または一部しか転送されない

    • 原因: ntransfercmd() で取得したソケットからの読み書きが適切に終了していない可能性があります。特に、ダウンロードの際にループの終了条件が不適切だったり、socket.recv() が0バイトを返したときにループを抜ける処理が不足していたりする場合に発生します。アップロードの場合は、sendall() が完了する前にソケットを閉じてしまったり、ファイル全体が読み込まれていない可能性があります。
    • トラブルシューティング:
      • ダウンロード:
        data_socket, _ = ftp.ntransfercmd('RETR my_file.txt')
        with open('local_my_file.txt', 'wb') as f:
            while True:
                chunk = data_socket.recv(8192) # 適切なバッファサイズ
                if not chunk: # データがこれ以上ない場合
                    break
                f.write(chunk)
        data_socket.close()
        ftp.voidresp() # データ転送完了応答を待つ
        
      • アップロード:
        data_socket, _ = ftp.ntransfercmd('STOR my_file.txt')
        with open('local_my_file.txt', 'rb') as f:
            while True:
                chunk = f.read(8192)
                if not chunk:
                    break
                data_socket.sendall(chunk) # 全て送信されるまで待つ
        data_socket.close()
        ftp.voidresp() # データ転送完了応答を待つ
        
      • voidresp() の呼び出し: ntransfercmd() で取得したソケットを閉じたら、必ず ftp.voidresp() を呼び出してサーバーからのデータ転送完了応答(通常は 226 Transfer complete)を待つようにしてください。これを怠ると、次のFTPコマンドがサーバーによって正しく処理されない可能性があります。
  • TypeError: a bytes-like object is required, not 'str'

    • 原因: ftplib のバイナリ転送メソッド (retrbinary, storbinary) や ntransfercmd() で取得したソケットを使ってデータを送受信する際に、バイト列ではなく文字列を渡してしまった場合。
    • トラブルシューティング:
      • ファイルの読み書きはバイナリモード ('rb''wb') で開く必要があります。
      • 文字列を送信する場合は、'utf-8' など適切なエンコーディングでバイト列にエンコード (.encode('utf-8')) する必要があります。
      • 受信したバイト列を文字列として扱いたい場合は、デコード (.decode('utf-8')) する必要があります。

FTPS (FTP over SSL/TLS) 関連のエラー

ftplib.FTP_TLS を使用している場合、ntransfercmd() の動作はさらにSSL/TLSハンドシェイクに関連する問題に直面することがあります。

  • ssl.SSLError: EOF occurred in violation of protocol
    • 原因: データコネクションのTLSハンドシェイクが失敗した場合。サーバーがセッション再利用を要求しているが、クライアントがそれに対応できない、またはTLSバージョンが一致しないなどが原因です。
    • トラブルシューティング:
      • ftp.prot_p() の呼び出し: 制御コネクションとデータコネクションの両方で暗号化を有効にするために、ftp.set_pasv(True) の後に ftp.prot_p() を呼び出す必要があります。
      • FTP_TLS のインスタンス化: ftplib.FTP_TLS を適切に使用しているか確認します。
      • サーバー設定: サーバーがFTPSセッション再利用を要求している場合、vsftpd.confrequire_ssl_reuse=NO のように設定を無効にすることで解決できる場合があります。
      • SSL/TLSバージョン: サーバーとクライアントでサポートしているTLSバージョンが一致しているか確認します。PythonのSSLコンテキストで特定のバージョンを指定する必要があるかもしれません。
  • 例外処理: try...except ftplib.all_errors: のように、ftplib が送出する可能性のある例外を適切に捕捉し、エラーメッセージをログに出力するようにしてください。
  • ネットワークツール: Wiresharkなどのネットワークパケットアナライザを使用して、FTP制御コネクションとデータコネクションの通信をキャプチャし、何が問題を引き起こしているかを詳細に分析することができます。特にファイアウォールやNATの問題を特定するのに役立ちます。
  • デバッグレベルの引き上げ: ftp.set_debuglevel(2) のように設定することで、ftplib が制御コネクションでのFTPコマンドと応答を詳細に出力します。これにより、どこの段階でサーバーからエラーが返されているのかを正確に把握できます。


基本的な考え方

ftplib.FTP.ntransfercmd(cmd) メソッドは、FTPデータ転送を開始するために必要な制御コネクションのコマンド (RETR, STOR など) をサーバーに送信し、データ転送用のソケットと、期待されるデータサイズを返します。

返り値: (data_socket, expected_size)

  • expected_size: 転送されるデータの推定サイズ。サーバーによっては正確な値が返されないこともあります。
  • data_socket: 実際のデータ転送(読み取りまたは書き込み)に使用するソケットオブジェクト。

このソケットを使って、Pythonの標準的なソケット操作 (recv(), sendall()) でデータを読み書きします。データ転送が完了したら、data_socket.close() を呼び出し、必ず ftp.voidresp() を呼び出してサーバーからの最終応答を待ちます。

例1: ファイルのダウンロード (バイナリモード)

これは、retrbinary() が内部的に行っている処理に近いですが、ntransfercmd() を使って手動で実装する例です。

import ftplib
import socket

# --- 設定 ---
FTP_HOST = 'ftp.example.com'  # 実際のFTPホストに置き換えてください
FTP_USER = 'your_username'     # 実際のユーザー名に置き換えてください
FTP_PASS = 'your_password'     # 実際のパスワードに置き換えてください
REMOTE_FILE = 'remote_test_file.zip' # ダウンロードするリモートファイル名
LOCAL_FILE = 'downloaded_test_file.zip' # 保存するローカルファイル名

try:
    # 1. FTP接続の確立とログイン
    print(f"{FTP_HOST} に接続中...")
    ftp = ftplib.FTP(FTP_HOST, timeout=30) # タイムアウトを設定
    print("ログイン中...")
    ftp.login(FTP_USER, FTP_PASS)
    print("ログイン成功。")

    # 2. パッシブモードの設定 (推奨)
    # ほとんどの環境でパッシブモードが推奨されます。
    ftp.set_pasv(True)
    print("パッシブモードを設定しました。")

    # 3. ntransfercmd() を使ってダウンロードを開始
    # 'RETR' コマンドでリモートファイルを取得
    print(f"リモートファイル '{REMOTE_FILE}' のダウンロード準備中...")
    data_socket, expected_size = ftp.ntransfercmd(f'RETR {REMOTE_FILE}')
    print(f"データ転送ソケットを取得。期待されるサイズ: {expected_size}バイト。")

    # 4. ソケットからデータを読み込み、ローカルファイルに書き込む
    bytes_received = 0
    with open(LOCAL_FILE, 'wb') as f:
        print(f"'{LOCAL_FILE}' に書き込み中...")
        while True:
            # 8192バイトずつ読み込む
            chunk = data_socket.recv(8192)
            if not chunk: # データがこれ以上ない場合
                break
            f.write(chunk)
            bytes_received += len(chunk)
            # 進捗表示 (オプション)
            # print(f"\r受信済み: {bytes_received} / {expected_size if expected_size > 0 else '不明'} バイト", end='')
    print(f"\nダウンロード完了。受信済み: {bytes_received} バイト。")

    # 5. データソケットを閉じ、サーバーからの最終応答を待つ
    data_socket.close()
    ftp.voidresp() # 転送完了を示すサーバーからの応答を待つ
    print("データ転送コネクションを閉じ、最終応答を待機しました。")

except ftplib.all_errors as e:
    print(f"FTPエラーが発生しました: {e}")
except socket.timeout:
    print("接続またはデータ転送がタイムアウトしました。")
except Exception as e:
    print(f"予期せぬエラーが発生しました: {e}")
finally:
    if 'ftp' in locals() and ftp.sock:
        print("FTP接続を閉じます。")
        ftp.quit()

例2: ファイルのアップロード (バイナリモード)

これも storbinary() の内部処理に近いですが、ntransfercmd() を使って手動で実装する例です。

import ftplib
import socket
import os

# --- 設定 ---
FTP_HOST = 'ftp.example.com'  # 実際のFTPホストに置き換えてください
FTP_USER = 'your_username'     # 実際のユーザー名に置き換えてください
FTP_PASS = 'your_password'     # 実際のパスワードに置き換えてください
LOCAL_FILE = 'local_upload_test.txt' # アップロードするローカルファイル名
REMOTE_FILE = 'uploaded_test_file.txt' # リモートに保存するファイル名

# テスト用のローカルファイルを作成
with open(LOCAL_FILE, 'w') as f:
    f.write("This is a test file for FTP upload.\n")
    f.write("Line 2 of the test file.\n")
    f.write("Line 3 of the test file.\n")
print(f"テストファイル '{LOCAL_FILE}' を作成しました。")

try:
    # 1. FTP接続の確立とログイン
    print(f"{FTP_HOST} に接続中...")
    ftp = ftplib.FTP(FTP_HOST, timeout=30)
    print("ログイン中...")
    ftp.login(FTP_USER, FTP_PASS)
    print("ログイン成功。")

    # 2. パッシブモードの設定 (推奨)
    ftp.set_pasv(True)
    print("パッシブモードを設定しました。")

    # 3. ntransfercmd() を使ってアップロードを開始
    # 'STOR' コマンドでリモートにファイルを格納
    print(f"リモートファイル '{REMOTE_FILE}' のアップロード準備中...")
    data_socket, _ = ftp.ntransfercmd(f'STOR {REMOTE_FILE}')
    print("データ転送ソケットを取得。")

    # 4. ローカルファイルからデータを読み込み、ソケットに書き込む
    bytes_sent = 0
    with open(LOCAL_FILE, 'rb') as f:
        print(f"'{LOCAL_FILE}' から読み込み、アップロード中...")
        while True:
            chunk = f.read(8192) # 8192バイトずつ読み込む
            if not chunk: # ファイルの終端に達した場合
                break
            data_socket.sendall(chunk) # 全て送信されるまで待つ
            bytes_sent += len(chunk)
            # print(f"\r送信済み: {bytes_sent} バイト", end='')
    print(f"\nアップロード完了。送信済み: {bytes_sent} バイト。")

    # 5. データソケットを閉じ、サーバーからの最終応答を待つ
    data_socket.close()
    ftp.voidresp() # 転送完了を示すサーバーからの応答を待つ
    print("データ転送コネクションを閉じ、最終応答を待機しました。")

except ftplib.all_errors as e:
    print(f"FTPエラーが発生しました: {e}")
except socket.timeout:
    print("接続またはデータ転送がタイムアウトしました。")
except Exception as e:
    print(f"予期せぬエラーが発生しました: {e}")
finally:
    if 'ftp' in locals() and ftp.sock:
        print("FTP接続を閉じます。")
        ftp.quit()
    # テストファイルをクリーンアップ
    if os.path.exists(LOCAL_FILE):
        os.remove(LOCAL_FILE)
        print(f"テストファイル '{LOCAL_FILE}' を削除しました。")

nlst()dir() (または retrlines()) が内部的に行う処理を、ntransfercmd() を使って手動で実装する例です。ディレクトリリストは通常テキスト形式で返されるため、recv().decode() でデコードします。

import ftplib
import socket

# --- 設定 ---
FTP_HOST = 'ftp.example.com'  # 実際のFTPホストに置き換えてください
FTP_USER = 'your_username'     # 実際のユーザー名に置き換えてください
FTP_PASS = 'your_password'     # 実際のパスワードに置き換えてください
REMOTE_PATH = '.' # リストを取得するリモートパス ('.' はカレントディレクトリ)

try:
    print(f"{FTP_HOST} に接続中...")
    ftp = ftplib.FTP(FTP_HOST, timeout=30)
    print("ログイン中...")
    ftp.login(FTP_USER, FTP_PASS)
    print("ログイン成功。")

    ftp.set_pasv(True)
    print("パッシブモードを設定しました。")

    # 1. ntransfercmd() を使ってディレクトリリストの取得を開始
    # 'LIST' コマンドで詳細なリストを取得
    # 'NLST' コマンドを使うとファイル名のみのリストを取得できます
    print(f"リモートパス '{REMOTE_PATH}' のディレクトリリストを取得準備中...")
    data_socket, _ = ftp.ntransfercmd(f'LIST {REMOTE_PATH}')
    print("データ転送ソケットを取得。")

    # 2. ソケットからデータを読み込み、行ごとに表示
    received_data = b''
    print("--- ディレクトリリスト ---")
    while True:
        chunk = data_socket.recv(8192)
        if not chunk:
            break
        received_data += chunk
    
    # 受信したバイト列をUTF-8でデコードして表示
    # サーバーのエンコーディングが異なる場合、適切に修正してください (例: 'latin-1')
    print(received_data.decode('utf-8')) 
    print("-------------------------")

    # 3. データソケットを閉じ、サーバーからの最終応答を待つ
    data_socket.close()
    ftp.voidresp()
    print("データ転送コネクションを閉じ、最終応答を待機しました。")

except ftplib.all_errors as e:
    print(f"FTPエラーが発生しました: {e}")
except socket.timeout:
    print("接続またはデータ転送がタイムアウトしました。")
except Exception as e:
    print(f"予期せぬエラーが発生しました: {e}")
finally:
    if 'ftp' in locals() and ftp.sock:
        print("FTP接続を閉じます。")
        ftp.quit()

これらの例は、ftplib.FTP.ntransfercmd() を使用してFTPデータ転送を直接制御する方法を示しています。通常はより高レベルのメソッド (retrbinary, storbinary など) を使う方が簡単でエラーも少ないですが、特定の要件(例: プログレスバーの実装、特殊なデータ処理)がある場合には、ntransfercmd() が非常に役立ちます。

重要な注意点:

  • エンコーディング: テキストデータを扱う場合(特にディレクトリリストなど)、サーバーのエンコーディングに合わせて適切に decode() する必要があります。デフォルトはUTF-8ですが、latin-1 や他のエンコーディングが必要な場合もあります。
  • set_pasv(True): ほとんどのクライアント環境で、パッシブモード (set_pasv(True)) が推奨されます。
  • ftp.voidresp(): データソケットを閉じた後、必ず ftp.voidresp() を呼び出して、サーバーからのデータ転送完了を示す応答を待ちます。これを忘れると、次のFTPコマンドが正しく処理されない可能性があります。
  • エラーハンドリング: ftplib.all_errorssocket.timeout などの例外を適切に処理することが重要です。


ftplib.FTP.retrbinary(command, callback, blocksize=8192, rest=None)

  • :
    import ftplib
    
    FTP_HOST = 'ftp.example.com'
    FTP_USER = 'your_username'
    FTP_PASS = 'your_password'
    REMOTE_FILE = 'remote_image.jpg'
    LOCAL_FILE = 'downloaded_image.jpg'
    
    def handle_binary(block):
        # 読み込んだデータの塊をファイルに書き込む
        f.write(block)
        # プログレスバーなどの進捗表示もここで行える
        # print(f"\rReceived {f.tell()} bytes...", end='')
    
    try:
        ftp = ftplib.FTP(FTP_HOST, timeout=30)
        ftp.login(FTP_USER, FTP_PASS)
        ftp.set_pasv(True)
    
        print(f"'{REMOTE_FILE}' のダウンロードを開始します。")
        with open(LOCAL_FILE, 'wb') as f:
            # RETR コマンドを内部的に発行し、データを handle_binary に渡す
            ftp.retrbinary(f'RETR {REMOTE_FILE}', handle_binary)
        print(f"\nダウンロード完了: '{LOCAL_FILE}'")
    
    except ftplib.all_errors as e:
        print(f"FTPエラー: {e}")
    except Exception as e:
        print(f"予期せぬエラー: {e}")
    finally:
        if 'ftp' in locals() and ftp.sock:
            ftp.quit()
    
  • メリット:
    • データ転送ロジックがシンプルになります。
    • エラーハンドリングが組み込まれています。
    • プログレスバーの実装が容易です(コールバック関数内で進捗を更新)。
  • 特徴:
    • ntransfercmd() を使ってデータソケットを自分で管理する必要がなく、指定したコールバック関数を通じてデータの塊を受け取ることができます。
    • blocksize (デフォルトは8192バイト) ごとにデータが読み込まれ、callback 関数に渡されます。
    • rest パラメータを使用すると、中断されたダウンロードを再開できます。
  • 用途: リモートのファイルをバイナリモードでダウンロードする場合に使用します。

ftplib.FTP.storbinary(command, fp, blocksize=8192, callback=None, rest=None)

  • :
    import ftplib
    import os
    
    FTP_HOST = 'ftp.example.com'
    FTP_USER = 'your_username'
    FTP_PASS = 'your_password'
    LOCAL_FILE = 'local_upload.txt'
    REMOTE_FILE = 'uploaded_file.txt'
    
    # テスト用のローカルファイルを作成
    with open(LOCAL_FILE, 'w') as f:
        f.write("This is a test content for upload.\n")
        f.write("Another line of text.\n")
    
    try:
        ftp = ftplib.FTP(FTP_HOST, timeout=30)
        ftp.login(FTP_USER, FTP_PASS)
        ftp.set_pasv(True)
    
        print(f"'{LOCAL_FILE}' のアップロードを開始します。")
        with open(LOCAL_FILE, 'rb') as fp:
            # STOR コマンドを内部的に発行し、fp からデータを読み込んで送信
            ftp.storbinary(f'STOR {REMOTE_FILE}', fp)
        print(f"アップロード完了: '{REMOTE_FILE}'")
    
    except ftplib.all_errors as e:
        print(f"FTPエラー: {e}")
    except Exception as e:
        print(f"予期せぬエラー: {e}")
    finally:
        if 'ftp' in locals() and ftp.sock:
            ftp.quit()
        if os.path.exists(LOCAL_FILE):
            os.remove(LOCAL_FILE) # テストファイルを削除
    
  • メリット:
    • ファイル全体のアップロード処理を自動的に行ってくれます。
    • エラーハンドリングが組み込まれています。
  • 特徴:
    • fp はファイルライクオブジェクト(通常は open(..., 'rb') で開かれたファイルオブジェクト)である必要があります。
    • fp から blocksize ごとにデータが読み込まれ、サーバーに送信されます。
    • callback 関数を渡すことも可能ですが、これは通常、retrbinary の場合ほど一般的ではありません。
  • 用途: ローカルファイルをバイナリモードでリモートにアップロードする場合に使用します。

ftplib.FTP.retrlines(command, callback=None)

  • :
    import ftplib
    
    FTP_HOST = 'ftp.example.com'
    FTP_USER = 'your_username'
    FTP_PASS = 'your_password'
    REMOTE_DIR = '.' # カレントディレクトリのリストを取得
    
    def list_callback(line):
        # 各行がこの関数に渡される
        print(f"LIST: {line}")
    
    try:
        ftp = ftplib.FTP(FTP_HOST, timeout=30)
        ftp.login(FTP_USER, FTP_PASS)
        ftp.set_pasv(True)
        # サーバーによってはUTF-8エンコーディングが必要な場合がある
        # ftp.encoding = 'utf-8' 
    
        print(f"'{REMOTE_DIR}' のディレクトリリストを取得します。")
        # LIST コマンドを内部的に発行し、各行を list_callback に渡す
        ftp.retrlines(f'LIST {REMOTE_DIR}', list_callback)
        print("ディレクトリリスト取得完了。")
    
        # ファイル名のみ取得する場合
        print("\nファイル名のみを取得します (NLST):")
        files = []
        ftp.retrlines('NLST', files.append) # リストに直接追加
        print(files)
    
    except ftplib.all_errors as e:
        print(f"FTPエラー: {e}")
    except Exception as e:
        print(f"予期せぬエラー: {e}")
    finally:
        if 'ftp' in locals() and ftp.sock:
            ftp.quit()
    
  • メリット:
    • テキストデータの処理(特に改行コードの扱い)が簡単になります。
    • ディレクトリリストの取得に非常に便利です。
  • 特徴:
    • サーバーから受信したデータを自動的に行ごとに読み込み、デコードします。
    • 各行は callback 関数に渡されます。callback を指定しない場合、各行は標準出力 (sys.stdout) に出力されます。
    • エンコーディングは ftp.encoding 属性で制御できます(デフォルトは 'latin-1' で、多くのFTPサーバーのリスト出力に適しています。UTF-8 の場合もあります)。
  • 用途: リモートのテキストファイルをダウンロードしたり、ディレクトリリスト (LIST, NLST) を取得したりする場合に使用します。
  • :
    import ftplib
    import os
    
    FTP_HOST = 'ftp.example.com'
    FTP_USER = 'your_username'
    FTP_PASS = 'your_password'
    LOCAL_FILE = 'local_text_upload.txt'
    REMOTE_FILE = 'uploaded_text_file.txt'
    
    # テスト用のローカルファイルを作成
    with open(LOCAL_FILE, 'w') as f:
        f.write("Line 1 of text file.\n")
        f.write("Line 2 of text file.\r\n") # 異なる改行コードも自動的に変換される
        f.write("Line 3 of text file.\n")
    
    try:
        ftp = ftplib.FTP(FTP_HOST, timeout=30)
        ftp.login(FTP_USER, FTP_PASS)
        ftp.set_pasv(True)
    
        print(f"'{LOCAL_FILE}' のテキストアップロードを開始します。")
        with open(LOCAL_FILE, 'r') as fp:
            # STOR コマンドを内部的に発行し、fp から行ごとに読み込んで送信
            ftp.storlines(f'STOR {REMOTE_FILE}', fp)
        print(f"アップロード完了: '{REMOTE_FILE}'")
    
    except ftplib.all_errors as e:
        print(f"FTPエラー: {e}")
    except Exception as e:
        print(f"予期せぬエラー: {e}")
    finally:
        if 'ftp' in locals() and ftp.sock:
            ftp.quit()
        if os.path.exists(LOCAL_FILE):
            os.remove(LOCAL_FILE)
    
  • メリット:
    • テキストファイルのアップロード、特に改行コードの処理が簡単になります。
  • 特徴:
    • fp はファイルライクオブジェクト(通常は open(..., 'r') で開かれたファイルオブジェクト)である必要があります。
    • fp から行ごとにデータが読み込まれ、サーバーに送信されます。
    • 行末の改行コードは自動的にFTPの慣例に合わせて変換されます。
  • 用途: ローカルのテキストファイルをリモートにアップロードする場合に使用します。

これらの高レベルメソッドは、ntransfercmd() を直接使用する場合に比べて、以下のような利点があります。

  1. 簡潔性: データソケットの管理、データのブロック読み書き、転送完了後の voidresp() の呼び出しなどを、開発者が手動で行う必要がありません。
  2. 安全性: 内部的に適切なエラーハンドリングやプロトコルの順序が保証されています。
  3. 利便性: バイナリモードとテキストモードの切り替え、改行コードの自動処理などが組み込まれており、開発者はコンテンツの種類に応じた適切なメソッドを選択するだけで済みます。