遍历FTP列表

6 投票
5 回答
13216 浏览
提问于 2025-04-15 16:43

我想从一个FTP服务器上获取所有目录的名字,并把它们按层级顺序存储在一个多维列表或字典里。

比如说,服务器上有这样的结构:

/www/
    mysite.com
        images
            png
            jpg

最后运行这个脚本后,我希望能得到一个这样的列表:

['/www/'
  ['mysite.com'
    ['images'
      ['png'],
      ['jpg']
    ]
  ]
]

我尝试使用一个递归函数,像这样:

def traverse(dir):
    FTP.dir(dir, traverse)

这里的FTP.dir会返回这样的格式:

drwxr-xr-x    5 leavesc1 leavesc1     4096 Nov 29 20:52 mysite.com

所以我用line[56:]可以得到目录的名字(mysite.com)。我在递归函数里用到了这个。

但是我一直没法让它正常工作。我尝试了很多不同的方法,但都不行。还遇到了很多FTP错误(有时候找不到目录,这个是逻辑问题,有时候服务器返回意外错误,没有日志,我也没法调试)。

总结一下我的问题:如何从FTP服务器获取一个层级目录列表?

5 个回答

2

你可能不喜欢这个答案,但“这要看服务器的情况”或者更准确地说,“这要看服务器的输出格式”。

不同的服务器可以设置成显示不同的输出,所以你最开始的想法在大多数情况下是行不通的。

上面提到的“简单而慢的实现”会导致很多错误,甚至有些FTP服务器会把你踢掉(这可能就是你在大约7次之后遇到的情况...)。

6

这是我写的一个Python 3脚本的初稿,运行起来比调用cwd()快多了。你只需要传入服务器、端口、目录、用户名和密码作为参数。我把输出保留成列表,留给大家自己去练习。

import ftplib
import sys

def ftp_walk(ftp, dir):
    dirs = []
    nondirs = []
    for item in ftp.mlsd(dir):
        if item[1]['type'] == 'dir':
            dirs.append(item[0])
        else:
            nondirs.append(item[0])
    if nondirs:
        print()
        print('{}:'.format(dir))
        print('\n'.join(sorted(nondirs)))
    else:
        # print(dir, 'is empty')
        pass
    for subdir in sorted(dirs):
        ftp_walk(ftp, '{}/{}'.format(dir, subdir))

ftp = ftplib.FTP()
ftp.connect(sys.argv[1], int(sys.argv[2]))
ftp.login(sys.argv[4], sys.argv[5])
ftp_walk(ftp, sys.argv[3])
11

这里有一个简单但比较慢的实现方法。之所以慢,是因为它会尝试进入每一个目录来判断它是文件还是文件夹,不过这样做是有效的。你可以通过解析LIST命令的输出结果来优化这个过程,但这会依赖于服务器的具体实现。

import ftplib

def traverse(ftp, depth=0):
    """
    return a recursive listing of an ftp server contents (starting
    from the current directory)

    listing is returned as a recursive dictionary, where each key
    contains a contents of the subdirectory or None if it corresponds
    to a file.

    @param ftp: ftplib.FTP object
    """
    if depth > 10:
        return ['depth > 10']
    level = {}
    for entry in (path for path in ftp.nlst() if path not in ('.', '..')):
        try:
            ftp.cwd(entry)
            level[entry] = traverse(ftp, depth+1)
            ftp.cwd('..')
        except ftplib.error_perm:
            level[entry] = None
    return level

def main():
    ftp = ftplib.FTP("localhost")
    ftp.connect()
    ftp.login()
    ftp.set_pasv(True)

    print traverse(ftp)

if __name__ == '__main__':
    main()

撰写回答