PythonでFTPサーバ上のファイルの更新日時を取得する

2021.01.25

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

データアナリティクス事業本部のkobayashiです。

PythonスクリプトでFTPサーバーからファイルを取得する際にファイルの更新日時を取得する必要があったのでその方法をまとめます。

環境

  • Python 3.7.4

ftplibモジュール

ftplibモジュールはPython標準ライブラリです。このモジュールはPythonでFTPプロコトルを扱う際に使用するモジュールで、ftplibを使うことでPythonで簡単にFTPサーバーとファイルのやり取りを行えます。

ftplib --- FTPプロトコルクライアント — Python 3.7.9 ドキュメント

今回はFTPサーバーに置かれたファイル一覧とその更新日時を取得したいので次の2つのメソッドのどちらかを使用します。

  • FTP.mlsd()
  • FTP.dir()

それぞれのメソッドで取得できる情報は以下の様になります。

FTP.mlsd()で取得できる情報

('dir_1', {'size': '0', 'modify': '20210123060332.754', 'type': 'dir'}),
('aaa.zip', {'size': '120069834', 'modify': '20171004063206.603', 'type': 'file'}),
('bbb.zip', {'size': '24831718', 'modify': '20200918071810.745', 'type': 'file'}),
('ccc.zip', {'size': '703491', 'modify': '20170924153223.229', 'type': 'file'}),
...

FTP.dir()で取得できる情報

'drwx------   3 user group            0 Jan 23 06:03 dir_1',
'-rw-------   1 user group    120069834 Oct  4  2017 aaa.zip',
'-rw-------   1 user group     24831718 Sep 18 08:18 bbb.zip',
'-rw-------   1 user group       703491 Sep 24  2017 ccc.zip',
...

取得できる情報を比較するとmlsdメソッドのほうがdict形式で取得できます。一方dirメソッドで取得できるデータはFTPのLISTコマンドで取得できるデータを文字列として取得するだけです。したがってdirメソッドでは更新日時を取得するにはひと手間加える必要があります。またdirメソッドで取得できる更新日時は、更新日時の情報が一部省略されていて精度が劣ります。

では常にmlsdメソッドだけを使えばいいかというと一概にはそうとは言えません。理由としては 、FTPサーバがサポートしているコマンドが環境によって異なるためです。ftplibのmlsdメソッドではFTPサーバが RFC 3659 - Extensions to FTP で定義されたmlsdコマンドをサポートしていないとmlsdメソッドでファイル一覧が取得できません。その様な場合はftplibのdirメソッドでファイル一覧を取得する必要があります。

では実際にFTPサーバにあるファイルの更新日時を取得するスクリプトを作成します。 なお取得するファイル更新日時はその後の工程で扱いやすくするためにdatetime型にすることを目的としています。

FTP.mlsdメソッドを使った更新日時の取得

mlsdメソッドでファイル一覧を取得するので更新日時は比較的簡単に取得できます。

from datetime import datetime
import ftplib

FTP_HOST = "接続先"
FTP_USER = "接続ユーザー名"
FTP_PASS = "接続パスワード"
DOWNLOAD_PATH = "対象ディレクトリ"

now_dt = datetime.utcnow()
print("now_dt {}".format(now_dt))
client = None
try:
    client = ftplib.FTP(FTP_HOST, FTP_USER, passwd=FTP_PASS)
    client.cwd(DOWNLOAD_PATH)
    files = client.mlsd()

    for file in files:
        name = file[0]
        modified_dt = datetime.strptime(file[1]["modify"],
                                        "%Y%m%d%H%M%S.%f" if "." in file[1]["modify"] else "%Y%m%d%H%M%S")
        print(modified_dt, name)
except ftplib.error_perm:
    print('ファイルが見つかりません')
except Exception as e:
    print("Erro : {}".format(e))
finally:
    if client:
        client.close()

上記のスクリプトの要点だけを説明します。

  • 13行目:接続情報を使ってftplibモジュールでFTPサーバに接続する
  • 14行目:目的のファイル群あるディレクトリに移動する
  • 15行目:mlsdメソッドを使い目的のディレクトリ配下のファイル一覧情報を取得する
  • 17行目:ファイル一覧から一つずつファイル情報を取得する
  • 19行目:mlsdで取得する更新日時をdatetime型に変換する
    • FTPサーバによって取得できる更新日時がYYYYMMDDHHMMSSのパターン、もしくはマイクロ秒まであるYYYYMMDDHHMMSS.sssのパターンがあるのでparseするフォーマットを条件分岐させています。

実行結果

2021-01-23 06:03:32.754000 dir_1
2017-10-04 06:32:06.603000 aaa.zip
2020-09-18 07:18:10.745000 bbb.zip
2017-09-24 15:32:23.229000 ccc.zip

この様な形でFTPサーバ上のファイルの更新日時が取得できました。

FTP.dirメソッドを使った更新日時の取得

では次にdirメソッドを使ってファイル一覧とその更新情報を取得します。 dirメソッドは先に述べたとおりLISTコマンドの結果を単純な文字列として取得するだけなので更新日時を取得する場合は様々な加工を行う必要があります。その流れは以下になります。

  1. 文字列を半角スペースでsplitする
  2. splitした文字列から更新日時を含む文字列をjoinする
  3. joinした更新日時を表す文字列のパターンによって適用する日時フォーマットを変更する
  4. 日時フォーマットを使って更新日付の文字列をparseする

具体的なスクリプトは下記になります。

from datetime import datetime
import ftplib

FTP_HOST = "接続先"
FTP_USER = "接続ユーザー名"
FTP_PASS = "接続パスワード"
DOWNLOAD_PATH = "対象ディレクトリ"

now_dt = datetime.utcnow()
print("now_dt {}".format(now_dt))
client = None
try:
    client = ftplib.FTP(FTP_HOST, FTP_USER, passwd=FTP_PASS)
    client.cwd(DOWNLOAD_PATH)

    lines = []
    client.dir(lines.append)

    for line in lines:
        dir_str = line.split()
        file_name = dir_str[-1]
        if ":" in dir_str[7]:
            modified_dt = datetime.strptime("{} {}".format(datetime.utcnow().year, " ".join(dir_str[5:8])),
                                            "%Y %b %d %H:%M")
            if modified_dt > now_dt:
                modified_dt = datetime.strptime("{} {}".format(datetime.utcnow().year - 1, " ".join(dir_str[5:8])),
                                                "%Y %b %d %H:%M")
        else:
            modified_dt = datetime.strptime(" ".join(dir_str[5:8]), "%b %d %Y")

        print(modified_dt, file_name)
except ftplib.error_perm:
    print('ファイルが見つかりません')
except Exception as e:
    print("Erro : {}".format(e))
finally:
    if client:
        client.close()

上記のスクリプトの要点を説明します。

  • 17行目:dirメソッドでFTPサーバのファイル一覧をlinesへ格納する
  • 19行目:ファイル一覧から一つずつファイル情報を取得する
  • 20行目:取得したファイル情報をsplitする
  • 22-29行目:ファイルの更新日時の文字列のパターンによってparseに使う日時フォーマットを変更し、そのフォーマットを使ってjoinした日付文字列をparseする

22-29行目の説明をパータン別に解説します。

dirメソッドで取得できる更新日時のパターンとしては以下の2パターンがあります。

  • Jan 23 06:03
    • 更新日時が1年以内の場合
  • Oct 4 2019
    • 更新日時が1年より前の場合

更新日時が1年以内の場合はparseする際に今年の年%Yを付加してparseしています。この際に1年以内だけど年%Yが今年ではない場合は正しい更新日時にならなくなってしまいます。その様な場合はparseする際に付加する年%Yを去年の年%Yにしています。また更新日時が1年より前の場合は時間がないフォーマットでparseしています。

実行結果

2021-01-23 06:03:00 dir_1
2017-10-04 00:00:00 aaa.zip
2020-09-18 08:18:00 bbb.zip
2017-09-24 00:00:00 ccc.zip

この様な形でdirメソッドを使ってFTPサーバ上のファイルの更新日時が取得できました。 先程のmlsdメソッドを使った場合と比較して更新日時の精度が劣っていることがわかります。

まとめ

ftplibモジュールを使ってFTPサーバ上のファイルの更新日時を取得してみました。接続先のサーバがmlsdコマンドをサポートしている場合はmlsdメソッドを使う以外の選択肢はありません。他方、FTPサーバがmlsdコマンドをサポートしていない場合はdirメソッドを使って更新日時を取得しましょう。

最後まで読んで頂いてありがとうございました。