Pythonを使ったSFTPへの接続、ファイルのリストアップ、アップロード、ダウンロードの方法についてご説明します。
安全なファイルやデータの転送を目的とするSFTPは、多くのユーザーにとって最良の選択肢となっています。SFTPは効率的でユーザーに優しいプロトコルですが、Pythonのようなプログラミング言語からSFTPサーバーに接続するには、内容をもう少し深掘りする必要があります。以下の記事を参考に、PythonからSFTPを利用し、接続できるようになればと思います。
必要条件
この作業に取り込む前に、設定がいくつか必要です。ここでは 'pysftp' パッケージを使って SFTP サーバに接続し、コマンドを渡します。インストールの準備ができたら、以下のコマンドを手動で実行します:
pip install pysftp
または、『requirements.txt』の ファイルを作成し、その中で依存関係を宣言します。そして、以下の内容を『requirements.txt』として保存します:
pysftp>=0.2.9
そして以下のように実行します:
pip install -r requirements.txt
SFTPへの接続
この記事では、sftp://user:password@host という URI 形式で SFTP サーバーに接続するのに必要な情報がすべて格納されているSFTPTOGO_URL
という環境変数を使用します。この変数は `urlparse` を使って URI の部分を抽出し、リモートサーバーのホスト鍵は~/.ssh/known_hosts
.を使ってデフォルトで検証されます。
接続が確立されると、SFTP クライアントオブジェクトが変数connection
に代入されます。
import pysftp
from urllib.parse import urlparse
import os
class Sftp:
def __init__(self, hostname, username, password, port=22):
"""Constructor Method"""
# Set connection object to None (initial value)
self.connection = None
self.hostname = hostname
self.username = username
self.password = password
self.port = port
def connect(self):
"""Connects to the sftp server and returns the sftp connection object"""
try:
# Get the sftp connection object
self.connection = pysftp.Connection(
host=self.hostname,
username=self.username,
password=self.password,
port=self.port,
)
except Exception as err:
raise Exception(err)
finally:
print(f"Connected to {self.hostname} as {self.username}.")
def disconnect(self):
"""Closes the sftp connection"""
self.connection.close()
print(f"Disconnected from host {self.hostname}")
ファイルのリストアップ
接続ができたので、それを使ってリモート SFTP サーバー上のファイル一覧が取得できます。これは、接続オブジェクトのlistdir
関数またはlistdir_attr
関数の呼び出しで行われます。これらの関数の引数にリモートパスか引数の指定なしによって、現在のリモートディレクトリのファイルとディレクトリの一覧を表示します。listdir
関数はファイル名のリスト(文字列)を返し、listdir_attr
関数はファイル名に加え、ファイルサイズ、作成、更新のタイムスタンプ、パーミッションを含む SFTPAttributes オブジェクトのリストを返します。この例では、ラッパー関数がジェネレータを返し、関数呼び出し側が返されたファイル一覧を反復処理できるようにしています。
def listdir(self, remote_path):
"""lists all the files and directories in the specified path and returns them"""
for obj in self.connection.listdir(remote_path):
yield obj
def listdir_attr(self, remote_path):
"""lists all the files and directories (with their attributes) in the specified path and returns them"""
for attr in self.connection.listdir_attr(remote_path):
yield attr
ファイルのアップロード
次のステップは、ファイルのアップロードです。接続オブジェクトのput
関数を使って、ローカルファイルへのパスと、アップロード終了時にファイルが置かれるべき場所であるリモートパスを渡します。関数の呼び出しは次のようになります:connection.put("./local.txt", "./remote.txt")
def upload(self, source_local_path, remote_path):
"""
Uploads the source files from local to the sftp server.
"""
try:
print(
f"uploading to {self.hostname} as {self.username} [(remote path: {remote_path});(source local path: {source_local_path})]"
)
# Download file from SFTP
self.connection.put(source_local_path, remote_path)
print("upload completed")
except Exception as err:
raise Exception(err)
ファイルのダウンロード
最後は、ファイルのダウンロードです。接続オブジェクトの get
関数を使って、リモートファイルへのパスと、ダウンロードしたファイルを保存するローカルパスを渡します。この関数は、次のように呼び出します: connection.get("./remote.txt", "./download.txt")
def download(self, remote_path, target_local_path):
"""
Downloads the file from remote sftp server to local.
Also, by default extracts the file to the specified target_local_path
"""
try:
print(
f"downloading from {self.hostname} as {self.username} [(remote path : {remote_path});(local path: {target_local_path})]"
)
# Create the target directory if it does not exist
path, _ = os.path.split(target_local_path)
if not os.path.isdir(path):
try:
os.makedirs(path)
except Exception as err:
raise Exception(err)
# Download from remote sftp server to local
self.connection.get(remote_path, target_local_path)
print("download completed")
except Exception as err:
raise Exception(err)
まとめ
というわけで、ゴールまで辿り着きました! もし、このプログラムを最初から最後まで実行したい場合は、次のコードをコピーして main.py
として保存してください。
import pysftp
from urllib.parse import urlparse
import os
class Sftp:
def __init__(self, hostname, username, password, port=22):
"""Constructor Method"""
# Set connection object to None (initial value)
self.connection = None
self.hostname = hostname
self.username = username
self.password = password
self.port = port
def connect(self):
"""Connects to the sftp server and returns the sftp connection object"""
try:
# Get the sftp connection object
self.connection = pysftp.Connection(
host=self.hostname,
username=self.username,
password=self.password,
port=self.port,
)
except Exception as err:
raise Exception(err)
finally:
print(f"Connected to {self.hostname} as {self.username}.")
def disconnect(self):
"""Closes the sftp connection"""
self.connection.close()
print(f"Disconnected from host {self.hostname}")
def listdir(self, remote_path):
"""lists all the files and directories in the specified path and returns them"""
for obj in self.connection.listdir(remote_path):
yield obj
def listdir_attr(self, remote_path):
"""lists all the files and directories (with their attributes) in the specified path and returns them"""
for attr in self.connection.listdir_attr(remote_path):
yield attr
def download(self, remote_path, target_local_path):
"""
Downloads the file from remote sftp server to local.
Also, by default extracts the file to the specified target_local_path
"""
try:
print(
f"downloading from {self.hostname} as {self.username} [(remote path : {remote_path});(local path: {target_local_path})]"
)
# Create the target directory if it does not exist
path, _ = os.path.split(target_local_path)
if not os.path.isdir(path):
try:
os.makedirs(path)
except Exception as err:
raise Exception(err)
# Download from remote sftp server to local
self.connection.get(remote_path, target_local_path)
print("download completed")
except Exception as err:
raise Exception(err)
def upload(self, source_local_path, remote_path):
"""
Uploads the source files from local to the sftp server.
"""
try:
print(
f"uploading to {self.hostname} as {self.username} [(remote path: {remote_path});(source local path: {source_local_path})]"
)
# Download file from SFTP
self.connection.put(source_local_path, remote_path)
print("upload completed")
except Exception as err:
raise Exception(err)
if __name__ == "__main__":
sftp_url = os.environ.get("SFTPTOGO_URL")
if not sftp_url:
print("First, please set environment variable SFTPTOGO_URL and try again.")
exit(0)
parsed_url = urlparse(sftp_url)
sftp = Sftp(
hostname=parsed_url.hostname,
username=parsed_url.username,
password=parsed_url.password,
)
# Connect to SFTP
sftp.connect()
# Lists files with attributes of SFTP
path = "/"
print(f"List of files with attributes at location {path}:")
for file in sftp.listdir_attr(path):
print(file.filename, file.st_mode, file.st_size, file.st_atime, file.st_mtime)
# Upload files to SFTP location from local
local_path = "/Users/saggi/Downloads/tls2.png"
remote_path = "/tls2.png"
sftp.upload(local_path, remote_path)
# Lists files of SFTP location after upload
print(f"List of files at location {path}:")
print([f for f in sftp.listdir(path)])
# Download files from SFTP
sftp.download(
remote_path, os.path.join(remote_path, local_path + '.backup')
)
# Disconnect from SFTP
sftp.disconnect()
最後に、以下のコマンドで実行します:
python main.py
Pythonを使ったSFTPへの接続完了です。お疲れ様でした。