【Python】WinSCPスクリプトを利用してリモートPCのファイルをダウンロードする方法

## 概要
Python×WinSCPスクリプトを利用することでリモート環境にあるファイルをローカル環境にダウンロードできます。paramikoという外部ライブラリを利用することでも実装できますが、今回は敢えてこの方法で紹介していきます。

## 実施環境
Windows11（Windows10でも可）
Python 3.12.4
WinSCP

## 実装方法

コードブロックを分けていますが、１本のファイルに記載して大丈夫です。

```download_remote_file.py
import os
import subprocess
```

```download_remote_file.py
def main():

    host = "127.0.0.1"
    user = "pecorimaru"
    password = "Cnetuser"
    remote_path = "/tmp/テスト.pdf"
    local_path = "E:\\data\\Documents\\SE\\Git\\Python\\jidoca312\\data\\fl01\\jd01\\"

    download_remote_file(host, user, password, remote_path, local_path)

    return
```
:::note info
上記コードが呼び出し元です。
リモートファイルをダウンロードする上で必要な情報は
* host         :  ホストIPアドレス
* user         :  ユーザー名
* password     :  パスワード
* remote_path  :  ダウンロード対象となるリモートファイルのパス
* local_path   :  ローカルPCにダウンロードするパス

となります。
Pythonで指定するパスは基本的に「/」でも「\\\」でも良いのですが、今回のローカルパスにおいては「\\\」の指定が必須です。
:::

```download_remote_file.py
def download_remote_file(host, user, password, remote_path, local_path):

    # ファイルダウンロード用のWinSCPスクリプトを作成（処理終了時に削除）
    path = "tmpscript.txt"
    with open(path, mode="w") as f:
        f.write("open %2%:%3%@%1%\r\n")
        f.write("get %4% %5%\r\n")
        f.write("exit")

    # ダウンロードに必要な情報を設定
    winscpcom = "C:/Program Files (x86)/WinSCP/WinSCP.com"

    # [コマンドイメージ]
    #   "C:/Program Files (x86)/WinSCP/WinSCP.com" 
    #   /script=tmpscript.txt /parameter 127.0.0.1 pecorimaru Cnetuser
    #   /tmp/テスト.pdf E:\\data\\Documents\\SE\\Git\\Python\\jidoca312\\data\\fl01\\jd01\\
    cmd_text = f"\"{winscpcom}\" /script={path} \
                /parameter {host} {user} {password} {remote_path} {local_path}"
                
    # コマンドを実行
    result = subprocess.run(cmd_text, shell=True)

    # リターンコードを取得
    print(f"returncode:{result.returncode}")

    # WinSCPスクリプトを削除
    os.remove(path)

    return
```
:::note info
WinSCP.comのパスはコマンド内でダブルクォーテーション「"」で囲う必要があるため注意。上記は「"」を文字列として扱うように「\\」でエスケープしています。
:::

```download_remote_file.py
if __name__ == "__main__":

    main()
```

## 実行

#### リモートパス
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/626788/c012d106-b34d-256a-64ca-7ee18bee69ab.png)

#### Pythonスクリプトを実行
```
PS E:\data\Documents\SE\Git\Python\jidoca312\src\fl01\jd01> python .\remotetest.py
サーバを探索中･･･
サーバに接続しています･･･
認証しています･･･
ユーザ名"pecorimaru" を使用中
入力済みパスワードで認証中
認証されました
セッションを開始しています･･･
セッションを開始しました
アクティブ セッション: [1] pecorimaru@127.0.0.1
テスト.pdf                   |         292 KB | 5657.9 KB/s | binary | 100%
returncode:0
```
スクリプトを実行すると上記のようなコンソール出力がされます。
リターンコード：0は成功なので、ローカルパスにファイルがダウンロードされました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/626788/d29e26a8-56cc-9e11-fab5-a1820fa4a431.png)

:::note info
リターンコード：0 以外で返ってくる場合は失敗しているので、コードを作り込む際はエラーハンドリングしておくと良いでしょう。
:::

## 応用パターン

### ローカルパスをファイル名まで指定することも可能
上記で紹介したコードではローカルパスはフォルダ名までを指定しましたが、ファイル名まで記載することでダウンロードするファイル名を指定することも可能です。

### ファイル名はアスタリスク（*）を用いたワイルドカードの指定が可能
フォルダ名は不可ですが、ファイル名であればワイルドカードを用いた指定ができるので、末尾にタイムスタンプが入ったログなどをまとめて取得する際などに便利に活用できます。試しに下記でメイン関数のリモートパスにアスタリスク（*）を指定してみます。

```download_remote_file.py
def main():

    host = "127.0.0.1"
    user = "pecorimaru"
    password = "Cnetuser"
    remote_path = "/tmp/テスト*.pdf"
    local_path = "E:\\data\\Documents\\SE\\Git\\Python\\jidoca312\\data\\fl01\\jd01\\"

    download_remote_file(host, user, password, remote_path, local_path)

    return
```

#### リモートパス
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/626788/0553df09-0306-65f2-de67-d16378465d65.png)

#### Pythonスクリプトを実行
```
PS E:\data\Documents\SE\Git\Python\jidoca312\src\fl01\jd01> python .\remotetest.py
サーバを探索中･･･
サーバに接続しています･･･
認証しています･･･
ユーザ名"pecorimaru" を使用中
入力済みパスワードで認証中
認証されました
セッションを開始しています･･･
セッションを開始しました
アクティブ セッション: [1] pecorimaru@127.0.0.1
テスト_20240819.pdf          |         292 KB | 5657.9 KB/s | binary | 100%
テスト.pdf                   |         292 KB | 7083.5 KB/s | binary | 100%
テスト_20240818.pdf          |         292 KB | 2252.7 KB/s | binary | 100%
returncode:0
```

:::note warn
ワイルドカードを用いて複数件ダウンロードする場合にファイル名の指定はできません。成功はしますが、どれか１ファイルに全て上書きされます。
:::

### 複数ファイルをダウンロードした後の処理
複数ファイルをダウンロードした場合、globライブラリをインポートしてパスリストを取得して後続処理すると良いでしょう。

```download_remote_file.py
import glob

def main():

    host = "127.0.0.1"
    user = "pecorimaru"
    password = "Cnetuser"
    remote_path = "/tmp/テスト*.pdf"
    local_path = "E:\\data\\Documents\\SE\\Git\\Python\\jidoca312\\data\\fl01\\jd01\\"

    download_remote_file(host, user, password, remote_path, local_path)

    # ダウンロードファイルのパスリストを取得
    path_list = glob.glob(local_path + "*")
    for path in path_list:
        print(path)
    
    return
```

#### 実行結果
```
PS E:\data\Documents\SE\Git\Python\jidoca312\src\fl01\jd01> python .\remotetest.py
サーバを探索中･･･
サーバに接続しています･･･
認証しています･･･
ユーザ名"pecorimaru" を使用中
入力済みパスワードで認証中
認証されました
セッションを開始しています･･･
セッションを開始しました
アクティブ セッション: [1] pecorimaru@127.0.0.1
テスト_20240819.pdf          |         292 KB | 3336.7 KB/s | binary | 100%
テスト.pdf                   |         292 KB | 3541.8 KB/s | binary | 100%
テスト_20240818.pdf          |         292 KB | 3187.8 KB/s | binary | 100%
returncode:0
E:\data\Documents\SE\Git\Python\jidoca312\data\fl01\jd01\テスト.pdf
E:\data\Documents\SE\Git\Python\jidoca312\data\fl01\jd01\テスト_20240818.pdf
E:\data\Documents\SE\Git\Python\jidoca312\data\fl01\jd01\テスト_20240819.pdf
```