FTP接続の落とし穴?Python set_pasv()で解決するタイムアウトとエラー

2025-06-06

FTP でファイルを転送する際には、コントロール接続データ接続 の2種類の接続が使われます。

  • データ接続: 実際のファイルデータを転送するための接続です。
  • コントロール接続: コマンド(ユーザー名、パスワード、ファイル転送の指示など)をやり取りするための接続です。

このデータ接続の確立方法には、大きく分けて「アクティブモード」と「パッシブモード」の2つがあります。

アクティブモード (Active Mode)

  • 問題点: クライアント側のファイアウォールが、サーバーからの接続をブロックしてしまうことがあります。クライアント側で特定のポートを開放しておく必要があるため、セキュリティ上のリスクや設定の手間があります。
  • 仕組み: クライアントが任意のポートを開放し、そのポート番号をサーバーに伝えます (PORT コマンド)。サーバーはクライアントの指定したポートに接続してデータ転送を開始します。

パッシブモード (Passive Mode)

  • 利点: クライアント側のファイアウォールを意識する必要が少なく、より多くのネットワーク環境で動作します。サーバー側がポートを開放するため、クライアント側が積極的にポートを開放する必要がありません。
  • 仕組み: クライアントがデータ転送を要求すると、サーバーがデータ転送用のポートを開放し、そのポート番号をクライアントに伝えます (PASV コマンド)。クライアントはサーバーの指定したポートに接続してデータ転送を開始します。

ftplib.FTP.set_pasv(val) メソッドは、引数 val の値によってデータ転送モードを切り替えます。

  • ftp.set_pasv(False): アクティブモードを有効にします。
  • ftp.set_pasv(True): パッシブモードを有効にします。これがデフォルトの動作です。
from ftplib import FTP

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

# パッシブモードを明示的に有効にする(デフォルトなので通常は不要)
ftp.set_pasv(True)
# または、アクティブモードにする場合
# ftp.set_pasv(False)

# ファイルのダウンロードなど、データ転送を行う処理
# ftp.retrbinary('RETR filename.zip', open('filename.zip', 'wb').write)

ftp.quit()


ftplib.FTP.set_pasv() に関連する一般的なエラーとトラブルシューティング

ftplib.FTP.set_pasv() はデータ接続の確立方法を決定するため、主に接続タイムアウトデータ転送の失敗といった形で問題が表面化します。

接続タイムアウト (TimeoutError または socket.timeout)

これは最も一般的なエラーの一つです。データ接続が確立できずにタイムアウトします。

  • 考えられる原因とトラブルシューティング

    1. ファイアウォールの問題 (クライアント側)

      • パッシブモード (set_pasv(True)) の場合

        • クライアントがサーバーの指定したポートに接続しようとしますが、クライアント側のファイアウォールがそのアウトバウンド接続をブロックしている可能性があります。
        • 解決策
          クライアント側のファイアウォール設定を確認し、FTP データ接続に必要なポート範囲(通常は一時的なポート、1024番以降のランダムなポート)のアウトバウンド接続を許可してください。ただし、パッシブモードではサーバー側がポートを指定するため、クライアント側が特定のポートを開放する必要はありません。
      • アクティブモード (set_pasv(False)) の場合

        • クライアントがデータ接続のためにポートを開放し、サーバーからのインバウンド接続を待ち受けますが、クライアント側のファイアウォールがそのインバウンド接続をブロックしている可能性が高いです。
        • 解決策
          アクティブモードはファイアウォールとの相性が悪く、ほとんどの環境で推奨されません。どうしてもアクティブモードを使う必要がある場合は、クライアント側のファイアウォールで、FTP クライアントが使用するデータポート(通常はクライアントが指定する任意のポート)へのインバウンド接続を許可する必要があります。これはセキュリティリスクを高めます。
    2. ファイアウォールの問題 (サーバー側)

      • パッシブモード (set_pasv(True)) の場合

        • サーバーがデータ転送用のポートを開放し、その情報をクライアントに伝えますが、サーバー側のファイアウォールがクライアントからの接続をブロックしている可能性があります。
        • 解決策
          FTP サーバーが稼働しているサーバーのファイアウォール設定を確認し、パッシブモードのデータ接続に使用されるポート範囲(通常はサーバー側で設定可能、例: 49152-65534など)へのインバウンド接続を許可してください。
      • アクティブモード (set_pasv(False)) の場合

        • サーバーがクライアントの開放したポートに接続しようとしますが、サーバー側のファイアウォールがそのアウトバウンド接続をブロックしている可能性があります。
        • 解決策
          サーバー側のファイアウォールで、FTP サーバーがクライアントに接続するためのアウトバウンド接続を許可してください。
    3. NAT (ネットワークアドレス変換) / ルーターの問題

      • FTP は、コントロール接続とデータ接続で別々のポートを使用するため、NAT 環境(一般的なルーターやホームネットワークなど)で問題が発生しやすいプロトコルです。

      • パッシブモード (set_pasv(True)) の場合

        • サーバーが NAT の内側にある場合、サーバーがクライアントに通知する IP アドレスがプライベート IP アドレス(例: 192.168.x.x)のままになることがあります。クライアントはこのプライベート IP に接続しようとして失敗します。
        • 解決策
          FTP サーバーの設定で、パッシブモードで使用する外部 IP アドレスを明示的に指定する必要があります (pasv_addresspasv_external_ip などの設定項目)。多くの FTP サーバーソフトウェア(vsftpdなど)でこの設定が可能です。
        • ルーターが FTP ALG (Application-Level Gateway) をサポートしている場合、ALG を無効にすると問題が解決することがあります。ALG が FTP のデータ接続情報を誤って書き換えることがあります。
      • アクティブモード (set_pasv(False)) の場合

        • クライアントが NAT の内側にある場合、クライアントが自身のプライベート IP アドレスをサーバーに通知し、サーバーがそのプライベート IP に接続しようとして失敗します。
        • 解決策
          アクティブモードは NAT 環境での使用は非常に困難です。ほとんどの場合、パッシブモードに切り替えるべきです。
    4. FTP サーバーが特定のモードをサポートしていない

      • ごく稀に、FTP サーバーがパッシブモードをサポートしていなかったり、設定ミスで有効になっていなかったりする場合があります。
      • 解決策
        • デフォルトのパッシブモードで接続できない場合、ftp.set_pasv(False) を試してアクティブモードで接続できるか確認します。
        • サーバーの管理者またはドキュメントを確認し、どのモードが推奨され、どのポート範囲が使用されるかを確認します。
    5. 不適切なタイムアウト設定

      • ftplib.FTP オブジェクトの初期化時や connect() メソッドでタイムアウトを設定できます。短すぎるタイムアウト値は、接続が確立される前にエラーを引き起こす可能性があります。
      • 解決策
        FTP(host, timeout=...)ftp.connect(host, port, timeout=...) で適切なタイムアウト値を設定します。ネットワークの状況に応じて、数秒から数十秒のタイムアウトを設定すると良いでしょう。
    • TimeoutError: [WinError 10060] A connection attempt failed because the connected party did not properly respond after a period of time (Windowsの場合)
    • socket.timeout: timed out
    • データ転送(retrbinary, storbinary, nlst, list など)を実行した後に処理が停止し、最終的にタイムアウトする。

これらのエラーは、set_pasv() 自体よりも、FTP サーバーとのコマンドのやり取りやプロトコル違反に関連していることが多いです。

  • 考えられる原因とトラブルシューティング

    1. FTP サーバーの応答と ftplib の期待値の不一致

      • 稀に、FTP サーバーが標準的な FTP プロトコルに厳密に従わない応答を返すことがあり、ftplib がそれをエラーと解釈する場合があります。
      • 解決策
        • デバッグレベルを上げて詳細なログを確認します。ftp.set_debuglevel(2) を設定すると、FTP コマンドの送受信がコンソールに表示され、サーバーからの応答を直接確認できます。
        • サーバーのドキュメントや既知の問題を調査します。
        • 場合によっては、ftplib の特定の部分を継承して動作をオーバーライドする必要があるかもしれませんが、これは稀なケースです。
    2. 認証情報、パス、ファイル名の問題

      • set_pasv() 自体とは直接関係ありませんが、データ転送に進む前のログインやパスの変更でエラーが発生している可能性があります。
      • 解決策
        • ユーザー名、パスワード、パスが正しいか再確認します。
        • ファイル名やディレクトリ名に特殊文字が含まれていないか、エンコーディングの問題がないか確認します(ftplib.FTPencoding パラメータが役立つ場合があります)。
  • エラーの兆候

    • ftplib.error_perm: 550 Permission denied. (権限エラー)
    • ftplib.error_reply: 5xx ... (サーバーからの予期せぬ応答コード)
    • ftplib.error_proto: Invalid response to ... (プロトコル違反)
  1. デバッグレベルの設定
    ftp.set_debuglevel(2) を設定して、FTP のコマンドと応答のログを詳細に確認することが、問題の特定に非常に役立ちます。

    import ftplib
    
    ftp = ftplib.FTP('your_ftp_server.com')
    ftp.set_debuglevel(2) # デバッグレベルを2に設定
    
    try:
        ftp.login('username', 'password')
        ftp.set_pasv(True) # または False を試す
        # データ転送操作
        ftp.retrlines('LIST') 
        ftp.quit()
    except ftplib.all_errors as e:
        print(f"FTP Error: {e}")
    except Exception as e:
        print(f"General Error: {e}")
    
  2. モードの切り替えを試す
    デフォルトのパッシブモード (ftp.set_pasv(True)) で問題が発生する場合、ftp.set_pasv(False) に切り替えてアクティブモードを試してみてください。ただし、アクティブモードはファイアウォールによってブロックされやすいため、一時的なテスト目的での使用が推奨されます。

  3. 別の FTP クライアントでテスト
    FileZilla やコマンドラインの ftp コマンドなど、別の FTP クライアントで同じサーバーに接続し、ファイル転送が成功するかどうかを確認します。これにより、問題が Python コードにあるのか、ネットワーク/サーバー設定にあるのかを切り分けることができます。

  4. FTP サーバーの設定確認
    可能であれば、接続先の FTP サーバーの設定を確認し、パッシブモードまたはアクティブモードが正しく設定されており、必要なポートが開放されていることを確認します。特に NAT 環境下のサーバーでは、pasv_address の設定が重要です。

  5. 一時的なファイアウォール無効化 (テスト目的)
    テスト環境でのみ、一時的にクライアント側やサーバー側のファイアウォールを無効にして、問題がファイアウォールに起因するものかを確認します。ただし、本番環境や安全が確保できない環境では絶対に避けてください。



ftplib.FTP.set_pasv() は、FTP のデータ転送モードを「パッシブモード」にするか「アクティブモード」にするかを制御します。デフォルトはパッシブモード (True) です。

基本的な使い方とパッシブモード(デフォルト)の例

set_pasv(True) はデフォルトの動作なので、明示的に呼び出さなくてもパッシブモードになります。

import ftplib
import os

# --- 設定(ご自身の環境に合わせて変更してください) ---
FTP_HOST = 'ftp.example.com'  # FTPサーバーのアドレス
FTP_USER = 'your_username'    # FTPユーザー名
FTP_PASS = 'your_password'    # FTPパスワード
REMOTE_DIR = '/path/to/remote/dir' # リモートサーバー上のディレクトリ
LOCAL_FILE = 'my_local_file.txt'   # ダウンロードするローカルファイル名
REMOTE_FILE = 'remote_file.txt'    # アップロードするリモートファイル名

# テスト用のローカルファイルを作成
with open(LOCAL_FILE, 'w') as f:
    f.write('This is a test file for FTP upload.\n')
    f.write('日本語のテキストもテストできます。\n')

try:
    print(f"Connecting to FTP server: {FTP_HOST}...")
    ftp = ftplib.FTP(FTP_HOST) # 接続タイムアウトを設定する場合: ftplib.FTP(FTP_HOST, timeout=30)
    ftp.set_debuglevel(1) # デバッグレベルを設定すると、FTPコマンドのやり取りが見えます

    print("Logging in...")
    ftp.login(user=FTP_USER, passwd=FTP_PASS)
    print(f"Current working directory: {ftp.pwd()}")

    # --- パッシブモード(デフォルト)での操作 ---
    # set_pasv(True) はデフォルトなので、通常は明示的に呼び出す必要はありませんが、
    # 意図を明確にするために記述することもできます。
    print("\n--- Using Passive Mode (default) ---")
    ftp.set_pasv(True) 
    print("Passive mode is ON.")

    # リモートディレクトリに移動
    print(f"Changing remote directory to: {REMOTE_DIR}")
    ftp.cwd(REMOTE_DIR)
    print(f"Current working directory on remote: {ftp.pwd()}")

    # 1. ファイル一覧の取得 (LIST コマンド)
    print("\nListing files in remote directory:")
    files = []
    ftp.retrlines('LIST', files.append) # 各行をfilesリストに追加
    for f in files:
        print(f)

    # 2. ファイルのアップロード (STOR コマンド)
    upload_file_name = f"uploaded_{os.path.basename(LOCAL_FILE)}"
    print(f"\nUploading {LOCAL_FILE} to {upload_file_name}...")
    with open(LOCAL_FILE, 'rb') as fp:
        ftp.storbinary(f'STOR {upload_file_name}', fp)
    print(f"Successfully uploaded {upload_file_name}.")

    # 3. ファイルのダウンロード (RETR コマンド)
    download_file_name = f"downloaded_{os.path.basename(REMOTE_FILE)}"
    print(f"\nDownloading {REMOTE_FILE} to {download_file_name}...")
    with open(download_file_name, 'wb') as fp:
        ftp.retrbinary(f'RETR {REMOTE_FILE}', fp.write)
    print(f"Successfully downloaded {download_file_name}.")

    # 4. アップロードしたファイルの削除
    print(f"\nDeleting uploaded file: {upload_file_name}...")
    ftp.delete(upload_file_name)
    print(f"Successfully deleted {upload_file_name}.")

except ftplib.all_errors as e:
    print(f"FTP Error occurred: {e}")
except Exception as e:
    print(f"An unexpected error occurred: {e}")
finally:
    if 'ftp' in locals() and ftp.sock is not None:
        try:
            print("\nQuitting FTP session.")
            ftp.quit()
        except ftplib.all_errors as e:
            print(f"Error during quit: {e}")

# 後処理: 作成したローカルファイルを削除
if os.path.exists(LOCAL_FILE):
    os.remove(LOCAL_FILE)
if os.path.exists(download_file_name):
    os.remove(download_file_name)

解説

  • ftp.quit(): FTPセッションを終了し、コントロール接続を閉じます。
  • ftp.delete(upload_file_name): リモートサーバー上のファイルを削除します。
  • ftp.retrbinary(f'RETR {REMOTE_FILE}', fp.write): バイナリモードでファイルをダウンロードします。RETR コマンドはファイルを取得します。fp.write は、受信したデータを書き込むためのコールバック関数です。
  • ftp.storbinary(f'STOR {upload_file_name}', fp): バイナリモードでファイルをアップロードします。STOR コマンドはファイルを保存します。fp はファイルオブジェクトで、read() メソッドを持つ必要があります。
  • ftp.retrlines('LIST', files.append): リモートディレクトリのファイル一覧を取得します。LIST コマンドはディレクトリの内容を ASCII モードで取得します。files.append は、各行を files リストに追加するためのコールバック関数です。
  • ftp.cwd(REMOTE_DIR): リモートサーバー上の作業ディレクトリを変更します。
  • ftp.set_pasv(True): データ転送モードをパッシブモードに設定します。これはデフォルトの動作であり、多くのネットワーク環境(特にクライアント側がファイアウォールやNATの背後にある場合)で推奨されます。
    • パッシブモードの動作
      クライアントが PASV コマンドをサーバーに送信すると、サーバーはデータ転送用のポートを開放し、そのポート番号をクライアントに返します。クライアントはそのポートに接続してデータ転送を開始します。
  • ftp.login(user=FTP_USER, passwd=FTP_PASS): FTPサーバーにログインします。
  • ftp.set_debuglevel(1): デバッグレベルを1に設定することで、FTPクライアントとサーバー間のコマンドと応答のやり取りをコンソールに表示させることができます。問題が発生した場合の診断に非常に役立ちます。
  • ftplib.FTP(FTP_HOST): 指定されたホストにFTPコントロール接続を確立します。

アクティブモードの例

アクティブモードはファイアウォールとの相性が悪いため、ほとんどの環境で推奨されませんが、もしアクティブモードで接続する必要がある場合の例です。

import ftplib
import os

# --- 設定(ご自身の環境に合わせて変更してください) ---
FTP_HOST = 'ftp.example.com'  # FTPサーバーのアドレス
FTP_USER = 'your_username'    # FTPユーザー名
FTP_PASS = 'your_password'    # FTPパスワード
REMOTE_DIR = '/path/to/remote/dir' # リモートサーバー上のディレクトリ

# テスト用のローカルファイルを作成
LOCAL_FILE = 'my_local_file_active.txt'
with open(LOCAL_FILE, 'w') as f:
    f.write('This is a test file for active mode FTP upload.\n')

try:
    print(f"Connecting to FTP server: {FTP_HOST}...")
    ftp = ftplib.FTP(FTP_HOST)
    ftp.set_debuglevel(1)

    print("Logging in...")
    ftp.login(user=FTP_USER, passwd=FTP_PASS)
    print(f"Current working directory: {ftp.pwd()}")

    # --- アクティブモードでの操作 ---
    print("\n--- Using Active Mode ---")
    ftp.set_pasv(False) # ここでアクティブモードに切り替えます
    print("Active mode is ON.")

    # リモートディレクトリに移動
    print(f"Changing remote directory to: {REMOTE_DIR}")
    ftp.cwd(REMOTE_DIR)
    print(f"Current working directory on remote: {ftp.pwd()}")

    # ファイルのアップロード(アクティブモード)
    upload_file_name_active = f"uploaded_active_{os.path.basename(LOCAL_FILE)}"
    print(f"\nUploading {LOCAL_FILE} to {upload_file_name_active} in active mode...")
    with open(LOCAL_FILE, 'rb') as fp:
        ftp.storbinary(f'STOR {upload_file_name_active}', fp)
    print(f"Successfully uploaded {upload_file_name_active}.")

    # アップロードしたファイルの削除
    print(f"\nDeleting uploaded file: {upload_file_name_active}...")
    ftp.delete(upload_file_name_active)
    print(f"Successfully deleted {upload_file_name_active}.")


except ftplib.all_errors as e:
    print(f"FTP Error occurred (Active Mode): {e}")
    print("Note: Active mode often fails due to firewall issues.")
except Exception as e:
    print(f"An unexpected error occurred (Active Mode): {e}")
finally:
    if 'ftp' in locals() and ftp.sock is not None:
        try:
            print("\nQuitting FTP session (Active Mode).")
            ftp.quit()
        except ftplib.all_errors as e:
            print(f"Error during quit: {e}")

# 後処理: 作成したローカルファイルを削除
if os.path.exists(LOCAL_FILE):
    os.remove(LOCAL_FILE)

アクティブモードの動作

  • 注意点
    クライアント側のファイアウォールがサーバーからのインバウンド接続(データ接続)をブロックするため、この例はほとんどの環境で接続エラーになる可能性が高いです。テストする際は、一時的にファイアウォールを無効にするか、特定のポートを開放する必要がありますが、これはセキュリティリスクを伴います。
  • アクティブモードの動作
    クライアントが PORT コマンドをサーバーに送信し、データ転送用に開放したクライアント側のポート番号とIPアドレスをサーバーに伝えます。サーバーはクライアントの指定したポートに接続してデータ転送を開始します。
  • ftp.set_pasv(False): データ転送モードをアクティブモードに設定します。
  1. 実際のFTPサーバー情報に置き換える
    FTP_HOST, FTP_USER, FTP_PASS, REMOTE_DIR は、実際に接続するFTPサーバーの情報に置き換えてください。テスト用の公開FTPサーバーや、自分で立てたFTPサーバーを使用することをお勧めします。
  2. セキュリティ
    実際の認証情報をコードに直接書き込むのは、セキュリティ上のリスクがあります。本番環境では、環境変数や設定ファイルから読み込むようにしてください。
  3. エラーハンドリング
    try...except...finally ブロックを使用して、FTP接続中の様々なエラー(接続タイムアウト、認証失敗、ファイルアクセスエラーなど)を適切に処理することが重要です。ftplib.all_errorsftplib モジュールが送出するすべての例外の基底クラスです。
  4. ファイルパス
    ローカルファイルのパスは、スクリプトが実行される環境に合わせて調整してください。
  5. ディレクトリ権限
    REMOTE_DIR に指定したディレクトリに、ファイルのアップロードや削除の権限があることを確認してください。


はい、ftplib.FTP.set_pasv() はFTPのデータ転送モード(パッシブ/アクティブ)を設定するための直接的なメソッドですが、このメソッド自体に代替手段があるわけではありません。 ftplib ライブラリを使用する限り、データ転送モードを明示的に制御したい場合は set_pasv() を呼び出すことになります。

しかし、ftplib を使用する代わりに、FTP通信を行う他のライブラリや方法、またはFTPの代替となるファイル転送プロトコルを使うという意味で、「代替手段」を説明することは可能です。

ここでは、ftplib を使ったFTP通信における set_pasv() の直接的な代替ではなく、より広範な意味での「ファイル転送の代替手段」や「FTP通信の別の方法」について説明します。

ftplib.FTP の代わりに ftplib.FTP_TLS を使用する (FTPS)

ftplib.FTP_TLS は、FTP over SSL/TLS (FTPS) をサポートするクラスです。これは通常のFTP接続を暗号化するため、セキュリティが大幅に向上します。set_pasv() の機能は FTP_TLS でもそのまま利用できます。

  • 特徴
    • コントロール接続とデータ接続の両方を暗号化できます。
    • 明示的FTPS (AUTH TLS) と暗黙的FTPS (FTPSポート990) の両方に対応しますが、通常は明示的FTPSが使われます。
    • パッシブモード (set_pasv(True)) が引き続き推奨されます。
  • いつ使うか
    セキュリティが重要な場合。パスワードやファイルデータがネットワーク上で盗聴されるのを防ぎたい場合。
import ftplib
import ssl # SSL/TLSエラーのハンドリング用

# --- 設定 ---
FTPS_HOST = 'your_ftps_server.com' # FTPSサーバーのアドレス
FTPS_USER = 'your_username'
FTPS_PASS = 'your_password'
REMOTE_DIR = '/path/to/remote/dir'
LOCAL_FILE = 'my_local_file.txt' # アップロードするファイル

# (ftplib.FTP の例と同じく、LOCAL_FILE を作成するコードは省略)

try:
    print(f"Connecting to FTPS server: {FTPS_HOST}...")
    # FTPS_TLS を使用し、implicit=False で明示的FTPSをデフォルトにする
    # ssl_version=ssl.PROTOCOL_TLSv1_2 などでプロトコルバージョンを指定することも可能
    ftp = ftplib.FTP_TLS(FTPS_HOST) 
    ftp.set_debuglevel(1)

    print("Logging in...")
    # TLS接続を確立 (AUTH TLS コマンドを送信)
    ftp.auth() 
    ftp.login(user=FTPS_USER, passwd=FTPS_PASS)
    
    # データ接続も暗号化することを必須にする
    ftp.prot_p() # 'P' (Private) または 'C' (Clear)。デフォルトは 'P' なので通常は不要
    
    # パッシブモードを設定 (デフォルトだが明示的に指定)
    ftp.set_pasv(True) 
    print("Passive mode is ON for FTPS.")

    print(f"Current working directory: {ftp.pwd()}")
    
    # ファイルのアップロード例 (FTPと同じメソッド)
    upload_file_name = f"uploaded_ftps_{os.path.basename(LOCAL_FILE)}"
    print(f"\nUploading {LOCAL_FILE} to {upload_file_name} via FTPS...")
    with open(LOCAL_FILE, 'rb') as fp:
        ftp.storbinary(f'STOR {upload_file_name}', fp)
    print(f"Successfully uploaded {upload_file_name}.")

    # 後処理: アップロードしたファイルを削除 (オプション)
    ftp.delete(upload_file_name)

except (ftplib.all_errors, ssl.SSLError) as e:
    print(f"FTPS Error occurred: {e}")
except Exception as e:
    print(f"An unexpected error occurred: {e}")
finally:
    if 'ftp' in locals() and ftp.sock is not None:
        try:
            print("\nQuitting FTPS session.")
            ftp.quit()
        except ftplib.all_errors as e:
            print(f"Error during quit: {e}")

paramiko ライブラリを使用する (SFTP)

SFTP (SSH File Transfer Protocol) は、SSHプロトコル上で動作するファイル転送プロトコルです。FTPとは全く異なるプロトコルであり、セキュリティが非常に高く、ファイアウォール設定もSSHポート(通常22番)のみを考慮すればよいため、FTPよりも推奨されることが多いです。

  • インストール
    pip install paramiko
  • 特徴
    • 全ての通信がSSHによって暗号化されるため、非常に安全。
    • FTPのようなデータ接続とコントロール接続の概念がないため、set_pasv() のようなモード設定は不要。
    • SSHキー認証もサポート。
    • サーバー側でSSHデーモンが動作している必要がある。
  • いつ使うか
    セキュリティが最優先で、FTPサーバーではなくSSHサーバーにアクセスできる場合。ファイアウォールの問題を最小限に抑えたい場合。
import paramiko
import os

# --- 設定 ---
SFTP_HOST = 'your_sftp_server.com' # SFTPサーバーのアドレス
SFTP_PORT = 22                     # SFTPのポート (通常22)
SFTP_USER = 'your_username'
SFTP_PASS = 'your_password'        # パスワードまたは秘密鍵のパス
# PRIVATE_KEY_PATH = '/path/to/your/private_key' # 秘密鍵を使う場合

REMOTE_DIR = '/path/to/remote/dir'
LOCAL_FILE = 'my_local_file.txt' # アップロードするファイル

# (ftplib.FTP の例と同じく、LOCAL_FILE を作成するコードは省略)

try:
    print(f"Connecting to SFTP server: {SFTP_HOST}:{SFTP_PORT}...")
    transport = paramiko.Transport((SFTP_HOST, SFTP_PORT))
    
    # パスワード認証
    transport.connect(username=SFTP_USER, password=SFTP_PASS)
    
    # または秘密鍵認証
    # private_key = paramiko.RSAKey.from_private_key_file(PRIVATE_KEY_PATH)
    # transport.connect(username=SFTP_USER, pkey=private_key)

    sftp = paramiko.SFTPClient.from_transport(transport)
    print("SFTP connection established.")

    # リモートディレクトリに移動(SFTPではパスが異なる場合がある)
    # SFTPは chdir() でディレクトリが存在しないとエラーになるため、mkdir -p 的な処理が必要な場合もある
    sftp.chdir(REMOTE_DIR) 
    print(f"Current working directory on remote: {sftp.getcwd()}")

    # ファイルのアップロード (SFTP)
    upload_file_name_sftp = f"uploaded_sftp_{os.path.basename(LOCAL_FILE)}"
    remote_path = os.path.join(sftp.getcwd(), upload_file_name_sftp)
    print(f"\nUploading {LOCAL_FILE} to {remote_path} via SFTP...")
    sftp.put(LOCAL_FILE, remote_path)
    print(f"Successfully uploaded {upload_file_name_sftp}.")

    # ファイル一覧の取得 (SFTP)
    print("\nListing files in remote directory (SFTP):")
    for entry in sftp.listdir():
        print(entry)

    # 後処理: アップロードしたファイルを削除 (オプション)
    print(f"\nDeleting uploaded file: {upload_file_name_sftp}...")
    sftp.remove(remote_path)
    print(f"Successfully deleted {upload_file_name_sftp}.")

except paramiko.AuthenticationException:
    print("SFTP Error: Authentication failed. Check username/password/key.")
except paramiko.SSHException as e:
    print(f"SFTP Error: SSH connection failed: {e}")
except FileNotFoundError:
    print(f"SFTP Error: Local file not found: {LOCAL_FILE}")
except Exception as e:
    print(f"An unexpected error occurred: {e}")
finally:
    if 'sftp' in locals() and sftp:
        sftp.close()
    if 'transport' in locals() and transport:
        transport.close()

requests ライブラリを使用する (HTTP/HTTPS)

もしファイルがWebサーバーから提供されている場合、またはWebサーバーにアップロードしたい場合、HTTP/HTTPSプロトコルを使用することができます。これはFTPとは全く異なるユースケースですが、ファイル転送という意味での代替です。

  • インストール
    pip install requests
  • 特徴
    • 非常に一般的で、Pythonの requests ライブラリは使いやすい。
    • ファイアウォール問題が少ない(通常、HTTP/HTTPSポートは開放されているため)。
    • ダウンロードは容易だが、アップロードはサーバーがAPIを提供している必要がある。
  • いつ使うか
    Webサーバー上のファイルをダウンロードしたい場合、またはRESTful APIなどを介してファイルをアップロードしたい場合。
import requests
import os

# --- 設定 ---
# ダウンロードURL (例: 公開されている画像ファイルなど)
DOWNLOAD_URL = 'http://example.com/some_public_file.zip' 
# アップロード先URL (RESTful APIなど、通常はPUT/POSTメソッドを使用)
# UPLOAD_URL = 'http://your_webserver.com/api/upload' 

LOCAL_DOWNLOAD_FILE = 'downloaded_web_file.zip'

try:
    print(f"Downloading from {DOWNLOAD_URL}...")
    response = requests.get(DOWNLOAD_URL, stream=True) # stream=True で大容量ファイル対応
    response.raise_for_status() # HTTPエラーがあれば例外を発生させる

    with open(LOCAL_DOWNLOAD_FILE, 'wb') as f:
        for chunk in response.iter_content(chunk_size=8192):
            f.write(chunk)
    print(f"Successfully downloaded to {LOCAL_DOWNLOAD_FILE}.")

    # --- アップロードの例 (サーバー側で対応するAPIが必要) ---
    # print(f"\nUploading {LOCAL_FILE} to {UPLOAD_URL}...")
    # with open(LOCAL_FILE, 'rb') as f:
    #     files = {'file': f}
    #     response = requests.post(UPLOAD_URL, files=files)
    #     response.raise_for_status()
    # print(f"Successfully uploaded via HTTP: {response.text}")

except requests.exceptions.RequestException as e:
    print(f"HTTP/HTTPS Error occurred: {e}")
except Exception as e:
    print(f"An unexpected error occurred: {e}")
finally:
    # 後処理
    if os.path.exists(LOCAL_DOWNLOAD_FILE):
        # os.remove(LOCAL_DOWNLOAD_FILE) # 必要に応じてダウンロードしたファイルを削除
        pass

s3fs などのクラウドストレージライブラリを使用する

AWS S3、Google Cloud Storage、Azure Blob Storageなどのクラウドストレージを使用している場合、それぞれのプロバイダが提供するPython SDKや便利なラッパーライブラリ(例: s3fs for S3)を使用するのが最も効率的です。これらのプロトコルはFTPとは全く異なり、set_pasv() のような概念は存在しません。

  • インストール例
    pip install s3fs boto3 (AWS S3の場合)
  • 特徴
    • 非常にスケーラブルで信頼性が高い。
    • 認証とアクセス制御はクラウドプロバイダの仕組みに依存。
    • FTPとは根本的に異なるアーキテクチャ。
  • いつ使うか
    クラウドストレージにファイルを保存/取得したい場合。
# AWS S3 の例 (boto3 または s3fs)
# boto3 を使った基本的な S3 アップロード/ダウンロード
import boto3

# --- 設定 ---
AWS_ACCESS_KEY_ID = 'YOUR_ACCESS_KEY_ID'
AWS_SECRET_ACCESS_KEY = 'YOUR_SECRET_ACCESS_KEY'
S3_BUCKET_NAME = 'your-unique-s3-bucket-name'
LOCAL_FILE = 'my_local_file.txt'
S3_KEY = 'path/to/my_object.txt' # S3バケット内のオブジェクトキー

# (ftplib.FTP の例と同じく、LOCAL_FILE を作成するコードは省略)

try:
    print("Connecting to AWS S3...")
    s3 = boto3.client(
        's3',
        aws_access_key_id=AWS_ACCESS_KEY_ID,
        aws_secret_access_key=AWS_SECRET_ACCESS_KEY
    )

    # ファイルのアップロード
    print(f"Uploading {LOCAL_FILE} to s3://{S3_BUCKET_NAME}/{S3_KEY}...")
    s3.upload_file(LOCAL_FILE, S3_BUCKET_NAME, S3_KEY)
    print("Successfully uploaded to S3.")

    # ファイルのダウンロード
    LOCAL_DOWNLOAD_FILE = f"downloaded_s3_{os.path.basename(LOCAL_FILE)}"
    print(f"Downloading s3://{S3_BUCKET_NAME}/{S3_KEY} to {LOCAL_DOWNLOAD_FILE}...")
    s3.download_file(S3_BUCKET_NAME, S3_KEY, LOCAL_DOWNLOAD_FILE)
    print("Successfully downloaded from S3.")

    # オブジェクトの削除
    print(f"Deleting s3://{S3_BUCKET_NAME}/{S3_KEY}...")
    s3.delete_object(Bucket=S3_BUCKET_NAME, Key=S3_KEY)
    print("Successfully deleted from S3.")

except Exception as e:
    print(f"AWS S3 Error occurred: {e}")
finally:
    if os.path.exists(LOCAL_FILE):
        os.remove(LOCAL_FILE)
    if os.path.exists(LOCAL_DOWNLOAD_FILE):
        os.remove(LOCAL_DOWNLOAD_FILE)

ftplib.FTP.set_pasv() 自体の「代替メソッド」というものは存在しません。しかし、FTPによるファイル転送の代わりに、以下の選択肢を検討することで、より現代的で安全、または特定のユースケースに適したファイル転送方法を選択できます。

  1. FTP_TLS (FTPS)
    FTPをSSL/TLSで暗号化し、セキュリティを向上させる場合に ftplib.FTP_TLS を使用。set_pasv() は引き続き使用。
  2. SFTP
    SSHプロトコル上で動作するセキュアなファイル転送が必要な場合は paramiko ライブラリを使用。set_pasv() の概念は存在しない。
  3. HTTP/HTTPS
    WebサーバーからのダウンロードやWeb API経由のアップロードには requests ライブラリを使用。
  4. クラウドストレージSDK
    クラウドストレージを使用している場合は、それぞれのプロバイダが提供するSDK(例: boto3 for AWS S3)を使用。