Python ftplib.FTP.transfercmd()徹底解説:FTPデータ転送の基本

2025-06-06

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

ftplib.FTP.transfercmd(cmd, rest=None)とは

このメソッドは、データ転送を伴うFTPコマンド(例:ファイルのダウンロード、アップロード、ディレクトリリスティングなど)をサーバーに送信する際に使用されます。

  • rest: これはオプションの引数で、途中からの転送(再開)を行う場合に、データのオフセット位置を指定します。FTPのRESTコマンドに対応します。デフォルトはNoneです。
  • cmd: これは実行したいFTPコマンドの文字列です。例えば、ファイルのダウンロードなら"RETR filename.txt"、ファイルのアップロードなら"STOR new_filename.txt"、カレントディレクトリのリストなら"LIST"などです。
  1. データ接続の確立: transfercmd()を呼び出すと、FTPクライアントはまずデータ転送用のポートを開き、サーバーにその情報を伝えます(PASVモードまたはPORTモード)。
  2. コマンドの送信: 次に、引数で指定されたcmd(例: RETR)をFTPサーバーに送信します。
  3. 応答の受信: サーバーはコマンドを受け取ると、データ転送を開始するための準備ができたことを示す応答を返します。
  4. ソケットの準備: transfercmd()は、データ転送に使用されるソケットオブジェクトと、データ転送を終了するために呼び出すべき終了関数(通常はftplib.FTP.voidresp()のようなもの)を含むタプルを返します。

通常の使い方とフロー

transfercmd()は、通常、以下のようなフローで使われます。

  1. FTP接続の確立: ftplib.FTP()でFTPサーバーに接続し、ログインします。
  2. データ転送の準備: transfercmd()を呼び出して、データ転送用のソケットと終了関数を取得します。
  3. データの送受信: 取得したソケットを使って、実際にデータを読み書きします。
  4. データ転送の終了: 取得した終了関数を呼び出して、データ転送を正常に終了させます。
from ftplib import FTP

ftp = None
try:
    # FTPサーバーに接続
    ftp = FTP('ftp.example.com')
    ftp.login('username', 'password')

    # ダウンロードするファイル名
    filename = 'remote_file.txt'

    # データ転送の準備 (RETR コマンドを送信)
    # conn はデータ転送用のソケットオブジェクト
    # rest はデータ転送完了後に呼び出す関数 (ftplib.FTP.voidresp など)
    conn, rest = ftp.transfercmd(f"RETR {filename}")

    # ローカルファイルにデータを書き込む
    with open(f"local_{filename}", 'wb') as fp:
        while True:
            # ソケットからデータを読み込む
            data = conn.recv(1024)
            if not data:
                break
            fp.write(data)

    # データ転送を正常に終了
    rest() # 通常は ftplib.FTP.voidresp() が呼び出される
    print(f"'{filename}' のダウンロードが完了しました。")

except Exception as e:
    print(f"エラーが発生しました: {e}")
finally:
    if ftp:
        ftp.quit()

ftplib.FTP.transfercmd()は、FTPのデータ転送を低レベルで制御したい場合に非常に有用なメソッドです。ftplibretrbinary()storbinary()のような高レベルなメソッドは、内部でこのtransfercmd()を利用してデータ転送を処理しています。



  1. ftplib.error_temp (4xx エラー):

    • 原因: 一時的なエラー(例:サーバーが一時的にビジー、利用可能なポートがない、データ接続確立のタイムアウトなど)。サーバーの応答コードが400番台の場合に発生します。
    • :
      • 421 Service not available, closing control connection. (サービスが利用できません)
      • 425 Can't open data connection. (データ接続を確立できません - ファイアウォール、NAT、FTPモードの問題など)
      • 426 Connection closed; transfer aborted. (転送中に接続が切断された)
    • トラブルシューティング:
      • リトライ: 一時的な問題であるため、数秒待ってから再試行すると解決することがあります。
      • ネットワーク設定の確認: ファイアウォール、ルーター、NAT (Network Address Translation) の設定を確認します。FTPはデータ転送に別のポートを使用するため、これらの設定が適切でないとデータ接続が確立できません。
      • FTPモードの確認:
        • アクティブモード (PORT): クライアントがサーバーにIPアドレスとポート番号を通知し、サーバーがそのポートに接続します。クライアント側のファイアウォールで特定のポートを開く必要があります。
        • パッシブモード (PASV): クライアントがサーバーにデータ接続用のポートを開くように要求し、サーバーがそのポート番号をクライアントに通知します。クライアントはそのポートに接続します。ほとんどの場合、パッシブモードの方がファイアウォール問題を回避しやすいため、デフォルトで使用されます。ftplibはデフォルトでパッシブモードを試行します。明示的にパッシブモードを無効にしている場合は、設定を見直してください。
  2. ftplib.error_perm (5xx エラー):

    • 原因: 永続的なエラー(例:権限不足、ファイルが見つからない、不正なコマンドなど)。サーバーの応答コードが500番台の場合に発生します。
    • :
      • 500 Syntax error, command unrecognized. (コマンドの文法エラー)
      • 501 Syntax error in parameters or arguments. (パラメータの文法エラー)
      • 550 Requested action not taken. File unavailable (e.g., file not found, no access). (ファイルが存在しない、またはアクセス権限がない)
      • 553 Requested action not taken. File name not allowed. (ファイル名が許可されていない)
    • トラブルシューティング:
      • コマンドの確認: transfercmd() に渡しているコマンド文字列 ("RETR filename.txt", "STOR new_file.txt") が正しいか、サーバーがそのコマンドをサポートしているかを確認します。特にファイル名やパスに間違いがないか注意深く確認します。
      • 権限の確認: ログインしているFTPユーザーが、対象のファイルやディレクトリに対して適切な読み書き権限を持っているか確認します。
      • ディレクトリの確認: ftp.cwd() などでカレントディレクトリを正しく設定しているか確認します。相対パスを使用している場合、現在の作業ディレクトリが重要になります。
  3. ftplib.error_proto:

    • 原因: サーバーからの応答がFTPプロトコル仕様に準拠していない場合(例:応答コードが数字で始まらないなど)。これは通常、FTPサーバーの実装に問題があるか、ネットワークの途中でデータが破損していることを示唆します。
    • トラブルシューティング:
      • FTPサーバーの確認: 使用しているFTPサーバーが標準に準拠しているか確認します。可能であれば、別のFTPクライアントで接続を試すなどして、サーバー側に問題がないかを切り分けます。
      • set_debuglevel() の使用: ftp.set_debuglevel(1) または 2 を設定することで、FTPコマンドとサーバーの応答を詳細に表示できます。これにより、どの応答がプロトコルエラーを引き起こしているかを特定できます。
  4. socket.timeout (タイムアウトエラー):

    • 原因: データ転送中に接続が長時間アイドル状態になった、またはサーバーからの応答が設定されたタイムアウト時間内に得られなかった場合。
    • トラブルシューティング:
      • タイムアウト設定の調整: ftp.connect(host, timeout=seconds)ftp.set_timeout(seconds) でタイムアウト時間を長く設定することを検討します。特に大きなファイルを転送する場合や、ネットワークが不安定な場合に有効です。
      • ネットワーク接続の確認: クライアントとサーバー間のネットワーク接続が安定しているか確認します。パケットロスが多い場合や帯域幅が低い場合は、タイムアウトしやすくなります。
      • Keep-aliveメカニズム: ftplib自体には明示的なキープアライブ機能はありませんが、transfercmd()で取得したソケットに対して、定期的にデータを送受信する(例:小さいチャンクでファイルを読み込む)ことで、接続がアイドル状態になるのを防ぐことができます。
  • FTPサーバーソフトウェアの互換性: 特定のFTPサーバーソフトウェアとftplibの間に既知の互換性問題がないか、オンラインフォーラムやドキュメントを検索してみるのも良いでしょう。
  • パッシブモードの明示的な設定: 一部の環境では、ftp.set_pasv(True) を明示的に呼び出すことで、データ接続の問題が解決することがあります。
  • FTPサーバーのログの確認: クライアント側で問題が解決しない場合、FTPサーバー側のログを確認することで、サーバーがどのようにクライアントからの要求を解釈し、どのようなエラーを返しているかについての貴重な情報が得られることがあります。
  • 高レベルAPIの使用の検討: transfercmd()は低レベルなAPIであり、ソケット操作やエラー処理を自分で記述する必要があります。多くの場合、ftp.retrbinary()ftp.storbinary() といった高レベルなメソッドを使用する方が、内部で適切なエラー処理やデータ転送完了処理(rest()の呼び出しなど)が実装されているため、簡単かつ安全です。これらのメソッドで要件を満たせるか再検討することをお勧めします。
  • 例外処理の厳密化: try...except ブロックを使用して、ftplibが送出するさまざまな例外(ftplib.all_errorsに含まれるもの)を適切にキャッチし、エラーメッセージをログに出力するなどして問題の原因特定に役立てます。
  • ftplib.set_debuglevel(level) の活用:
    • level=1: FTPコマンドとサーバーの主な応答を表示します。問題が発生した際に、どのコマンドで、どのようなサーバー応答があったのかを把握するのに非常に役立ちます。
    • level=2: さらに詳細なソケットレベルのデバッグ情報も表示します。


通常、ftplib.FTP オブジェクトには retrbinary()storbinary() といった、より高レベルで使いやすいメソッドが用意されていますが、transfercmd() を直接使うことで、データ転送のプロセスをより細かく制御できます。

ここでは、transfercmd() を使った具体的なプログラミング例をいくつかご紹介します。

ファイルのダウンロード (RETR)

最も一般的な用途の一つです。RETR コマンドを使ってサーバーからファイルを取得します。

from ftplib import FTP
import os

# --- 設定 ---
FTP_HOST = 'your_ftp_host.com'
FTP_USER = 'your_username'
FTP_PASS = 'your_password'
REMOTE_FILE = 'path/to/remote_file.txt' # ダウンロードしたいサーバー上のファイル
LOCAL_FILE = 'downloaded_file.txt'     # 保存するローカルファイル名

ftp = None
try:
    # 1. FTP サーバーに接続し、ログイン
    print(f"Connecting to {FTP_HOST}...")
    ftp = FTP(FTP_HOST)
    ftp.login(FTP_USER, FTP_PASS)
    print("Logged in successfully.")

    # 2. `transfercmd()` を使ってデータ転送を準備 (RETR コマンドを送信)
    # conn: データ転送用のソケットオブジェクト
    # rest: 転送完了後に呼び出すべき関数 (通常は ftp.voidresp())
    print(f"Preparing to download '{REMOTE_FILE}'...")
    conn, rest = ftp.transfercmd(f"RETR {REMOTE_FILE}")

    # 3. 取得したソケットからデータを読み込み、ローカルファイルに書き込む
    # 'wb' モードでバイナリファイルを書き込み用に開く
    with open(LOCAL_FILE, 'wb') as fp:
        while True:
            # ソケットからデータを1024バイトずつ読み込む
            data = conn.recv(1024)
            if not data: # データがなければ転送終了
                break
            fp.write(data) # 読み込んだデータをファイルに書き込む
    print(f"'{REMOTE_FILE}' downloaded as '{LOCAL_FILE}'.")

    # 4. データ転送を正常に終了 (重要!)
    # これを呼び出さないと、FTPプロトコルのシーケンスが狂う可能性があります。
    rest()
    print("Data transfer finalized.")

except Exception as e:
    print(f"An error occurred: {e}")
finally:
    # 最後にFTP接続を閉じる
    if ftp:
        ftp.quit()
        print("FTP connection closed.")

ファイルのアップロード (STOR)

STOR コマンドを使ってファイルをサーバーにアップロードします。

from ftplib import FTP
import os

# --- 設定 ---
FTP_HOST = 'your_ftp_host.com'
FTP_USER = 'your_username'
FTP_PASS = 'your_password'
LOCAL_FILE = 'upload_this_file.txt' # アップロードしたいローカルファイル
REMOTE_FILE = 'uploaded_file.txt'   # サーバーに保存するファイル名

# テスト用にダミーファイルを作成
with open(LOCAL_FILE, 'w') as f:
    f.write("This is a test file for FTP upload.\n")
    f.write("Hello, ftplib.FTP.transfercmd()!\n")

ftp = None
try:
    print(f"Connecting to {FTP_HOST}...")
    ftp = FTP(FTP_HOST)
    ftp.login(FTP_USER, FTP_PASS)
    print("Logged in successfully.")

    # 2. `transfercmd()` を使ってデータ転送を準備 (STOR コマンドを送信)
    print(f"Preparing to upload '{LOCAL_FILE}' to '{REMOTE_FILE}'...")
    conn, rest = ftp.transfercmd(f"STOR {REMOTE_FILE}")

    # 3. ローカルファイルからデータを読み込み、取得したソケットに書き込む
    # 'rb' モードでバイナリファイルを読み込み用に開く
    with open(LOCAL_FILE, 'rb') as fp:
        while True:
            # ファイルからデータを1024バイトずつ読み込む
            data = fp.read(1024)
            if not data: # データがなければ読み込み終了
                break
            conn.sendall(data) # 読み込んだデータをソケットに書き込む

    print(f"'{LOCAL_FILE}' uploaded as '{REMOTE_FILE}'.")

    # 4. データ転送を正常に終了 (重要!)
    rest()
    print("Data transfer finalized.")

except Exception as e:
    print(f"An error occurred: {e}")
finally:
    if ftp:
        ftp.quit()
        print("FTP connection closed.")
    # テストファイルを削除
    if os.path.exists(LOCAL_FILE):
        os.remove(LOCAL_FILE)

ディレクトリのリスト取得 (LIST)

LIST コマンドを使って現在のディレクトリの内容をリスト表示します。

from ftplib import FTP

# --- 設定 ---
FTP_HOST = 'your_ftp_host.com'
FTP_USER = 'your_username'
FTP_PASS = 'your_password'
# (必要に応じて、リスト表示したいディレクトリに移動: ftp.cwd('/some/directory'))

ftp = None
try:
    print(f"Connecting to {FTP_HOST}...")
    ftp = FTP(FTP_HOST)
    ftp.login(FTP_USER, FTP_PASS)
    print("Logged in successfully.")

    # 2. `transfercmd()` を使ってデータ転送を準備 (LIST コマンドを送信)
    # 通常、LIST コマンドはディレクトリの内容をテキスト形式で返します。
    print("Getting directory listing...")
    conn, rest = ftp.transfercmd("LIST")

    # 3. 取得したソケットからデータを読み込み、表示
    # LIST コマンドの出力は通常テキストなので、デコードして表示
    listing_data = b''
    while True:
        data = conn.recv(1024)
        if not data:
            break
        listing_data += data

    # 通常はUTF-8でデコードしますが、サーバーの設定によっては別のエンコーディングが必要な場合もあります
    print("\n--- Directory Listing ---")
    print(listing_data.decode('utf-8'))
    print("-------------------------\n")

    # 4. データ転送を正常に終了 (重要!)
    rest()
    print("Data transfer finalized.")

except Exception as e:
    print(f"An error occurred: {e}")
finally:
    if ftp:
        ftp.quit()
        print("FTP connection closed.")
  • デバッグレベル: ftp.set_debuglevel(1) を設定すると、FTPコマンドとサーバーの応答がコンソールに表示され、デバッグに非常に役立ちます。
  • 高レベルAPIの優先: これらの例は transfercmd() の使い方を示すためのものですが、実際のアプリケーションでは ftp.retrbinary(), ftp.storbinary(), ftp.nlst() のような高レベルなメソッドを使用する方が、内部でエラー処理や rest() の呼び出しが適切に実装されているため、よりシンプルで堅牢なコードになります。transfercmd() は、高レベルAPIでは実現できない特殊な要件がある場合に検討すると良いでしょう(例: プログレスバーの表示、部分的なデータ転送の再開など)。
  • バイナリモード: ftplib はデフォルトでバイナリモード(TYPE I コマンド)を使用します。テキストファイルを転送する場合でも、通常はバイナリモードで処理する方が安全です。ファイルの読み書きは 'rb''wb' モードで行いましょう。
  • エラーハンドリング: try...except...finally ブロックを使って、ネットワークエラーやFTPサーバーからのエラー(ftplib.error_temp, ftplib.error_perm など)を適切に処理することが重要です。
  • rest() の呼び出し: 各例で強調しているように、transfercmd() が返した rest 関数は、データの送受信が完了した後に必ず呼び出す必要があります。これを怠ると、FTPプロトコルのシーケンスが崩れ、次のコマンドがエラーになったり、接続がハングアップしたりする原因となります。


ftplib.FTP.transfercmd() は低レベルなデータ転送メソッドであり、FTPプロトコルの詳細な制御が必要な場合に利用されます。しかし、ほとんどの一般的なFTP操作においては、ftplibモジュールが提供するより高レベルな代替メソッドを使用する方が、コードがシンプルになり、エラーハンドリングも容易になります。

ftplibには、ファイルのアップロード、ダウンロード、ディレクトリリストの取得など、一般的な操作のための便利なメソッドが用意されています。これらは内部で transfercmd() を利用していますが、ユーザーはソケット操作や rest() の呼び出しなどを意識する必要がありません。

ファイルのダウンロード

  • ftp.retrlines(command, callback=None)

    • 用途: テキストファイルを1行ずつダウンロードする場合に利用します。
    • 引数:
      • command: RETR コマンド文字列。
      • callback: 各行が読み込まれるたびに呼び出される関数。この関数は1つの引数(読み込まれた行文字列)を受け取ります。デフォルトでは、print() が使用され、行が標準出力に表示されます。
    • 特徴: retrbinary() と同様に、行ごとの処理を callback に任せます。
    • 用途: バイナリファイルをダウンロードする際に最もよく使われるメソッドです。
    • 引数:
      • command: RETR コマンド文字列 (例: "RETR remote_file.txt")。
      • callback: ダウンロード中にデータブロックが受信されるたびに呼び出される関数。この関数は1つの引数(受信したデータブロック)を受け取ります。これを利用して、プログレスバーの表示などが実装できます。
      • blocksize: 一度に読み込むデータのバイト数。
      • rest: 途中からの転送を再開する場合のオフセット(バイト数)。
    • 特徴: transfercmd() でソケットから手動でデータを読み込む代わりに、callback 関数にデータの処理を任せることができます。最終的な rest() の呼び出しも自動で行われます。

例: retrbinary() を使ったファイルダウンロード

from ftplib import FTP
import os

FTP_HOST = 'your_ftp_host.com'
FTP_USER = 'your_username'
FTP_PASS = 'your_password'
REMOTE_FILE = 'path/to/remote_file.txt'
LOCAL_FILE = 'downloaded_file_high_level.txt'

def handle_data(data):
    # 受信したデータをローカルファイルに書き込む
    global file_handle
    file_handle.write(data)

file_handle = None # グローバル変数としてファイルハンドラを保持

ftp = None
try:
    print(f"Connecting to {FTP_HOST}...")
    ftp = FTP(FTP_HOST)
    ftp.login(FTP_USER, FTP_PASS)
    print("Logged in successfully.")

    print(f"Downloading '{REMOTE_FILE}' using retrbinary()...")
    with open(LOCAL_FILE, 'wb') as fp:
        file_handle = fp # ファイルハンドラをcallback関数で利用できるようにする
        # retrbinaryが内部で transfercmd() を呼び出し、データの読み込みと rest() を処理する
        ftp.retrbinary(f"RETR {REMOTE_FILE}", handle_data)
    print(f"'{REMOTE_FILE}' downloaded as '{LOCAL_FILE}'.")

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

ファイルのアップロード

  • ftp.storlines(command, fp, callback=None)

    • 用途: テキストファイルを1行ずつアップロードする場合に利用します。
    • 引数:
      • command: STOR または APPE コマンド文字列。
      • fp: アップロードするテキストファイルのファイルオブジェクト。
      • callback: 各行が送信されるたびに呼び出される関数。
  • ftp.storbinary(command, fp, blocksize=8192, callback=None, rest=None)

    • 用途: バイナリファイルをアップロードする際に最もよく使われるメソッドです。
    • 引数:
      • command: STOR または APPE コマンド文字列 (例: "STOR new_file.txt")。
      • fp: アップロードするファイルのファイルオブジェクト(読み込みモードで開かれている必要があります)。
      • blocksize: 一度に送信するデータのバイト数。
      • callback: データブロックが送信されるたびに呼び出される関数。
      • rest: 途中からの転送を再開する場合のオフセット。
    • 特徴: ファイルオブジェクトからデータを読み込み、ソケットに書き込む処理を自動で行います。transfercmd()conn.sendall() を手動で記述する手間が省けます。

例: storbinary() を使ったファイルアップロード

from ftplib import FTP
import os

FTP_HOST = 'your_ftp_host.com'
FTP_USER = 'your_username'
FTP_PASS = 'your_password'
LOCAL_FILE = 'upload_this_file_high_level.txt'
REMOTE_FILE = 'uploaded_file_high_level.txt'

# テスト用にダミーファイルを作成
with open(LOCAL_FILE, 'w') as f:
    f.write("This is a test file for high-level FTP upload.\n")

ftp = None
try:
    print(f"Connecting to {FTP_HOST}...")
    ftp = FTP(FTP_HOST)
    ftp.login(FTP_USER, FTP_PASS)
    print("Logged in successfully.")

    print(f"Uploading '{LOCAL_FILE}' to '{REMOTE_FILE}' using storbinary()...")
    with open(LOCAL_FILE, 'rb') as fp: # バイナリ読み込みモードで開く
        # storbinaryが内部で transfercmd() を呼び出し、データの書き込みと rest() を処理する
        ftp.storbinary(f"STOR {REMOTE_FILE}", fp)
    print(f"'{LOCAL_FILE}' uploaded as '{REMOTE_FILE}'.")

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

ディレクトリリストの取得

  • ftp.nlst(argument='')

    • 用途: カレントディレクトリにあるファイルとディレクトリの名前のリスト(短い形式)を取得します。
    • 引数:
      • argument: オプションでパスを指定できます。
    • 戻り値: ファイル/ディレクトリ名の文字列のリスト。
    • 特徴: 最もシンプルなリスト取得方法で、ファイル名のみが必要な場合に適しています。

例: nlst()dir() を使ったディレクトリリスト取得

from ftplib import FTP

FTP_HOST = 'your_ftp_host.com'
FTP_USER = 'your_username'
FTP_PASS = 'your_password'

ftp = None
try:
    print(f"Connecting to {FTP_HOST}...")
    ftp = FTP(FTP_HOST)
    ftp.login(FTP_USER, FTP_PASS)
    print("Logged in successfully.")

    # nlst() を使ってファイル/ディレクトリ名のみを取得
    print("\n--- Listing (nlst) ---")
    files_and_dirs = ftp.nlst()
    for item in files_and_dirs:
        print(item)
    print("------------------------\n")

    # dir() を使って詳細なリストを取得
    print("\n--- Detailed Listing (dir) ---")
    ftp.dir() # デフォルトではprint()がcallbackとして使われる
    print("------------------------------\n")

except Exception as e:
    print(f"An error occurred: {e}")
finally:
    if ftp:
        ftp.quit()
        print("FTP connection closed.")
メソッド (transfercmd() の代替)主な用途transfercmd() を直接使う場合との比較
ftp.retrbinary()バイナリファイルのDLtransfercmd() の後に conn.recv() とファイル書き込みを手動で行い、rest() を呼び出す手間が不要。callbackでプログレスバーを実装しやすい。
ftp.retrlines()テキストファイルのDLtransfercmd() の後に conn.recv() で受信データをデコードし、行ごとに処理する手間が不要。callbackでテキスト処理が容易。
ftp.storbinary()バイナリファイルのULtransfercmd() の後に fp.read() でファイルから読み込み、conn.sendall() で送信し、rest() を呼び出す手間が不要。
ftp.storlines()テキストファイルのULtransfercmd() の後にテキストファイルを読み込み、行ごとに conn.sendall() で送信し、rest() を呼び出す手間が不要。
ftp.nlst()ファイル/ディレクトリ名リストtransfercmd("NLST") を呼び出し、ソケットからデータを読み込み、デコードし、行ごとにパースする手間が不要。戻り値はリストとして直接取得できる。
ftp.dir()詳細なディレクトリリストtransfercmd("LIST") を呼び出し、ソケットからデータを読み込み、デコードし、行ごとにパースする手間が不要。callbackを使って出力処理をカスタマイズできる。