ftplib – Cliente para el protocolo FTP

ftplib presenta dos clases: FTP y FTP_TLS. Ambas implementan el lado del cliente del protocolo de transferencia de archivos, pero la segunda aporta algunas funciones extra para trabajar con conexiones seguras. Es también utilizado por urllib para manejar URLs que usen FTP, indica la documentación.

En este artículo veremos cómo funciona y cómo utilizar el módulo. Luego, haremos una clase que presenta una pequeña API de mayor nivel para trabajar con el protocolo.

Para comenzar vamos a ver el ejemplo de una sesión básica con algunas tareas que presenta la documentación:

from ftplib import FTP

ftp = FTP('ftp.debian.org') 
ftp.login()
ftp.cwd('debian')
ftp.retrlines('LIST')
ftp.retrbinary('RETR README', open('README', 'wb').write)
ftp.quit()

Veamos línea por línea:

from ftplib import FTP

Importa la clase FTP, necesaria para establecer una conexión, enviar y recibir datos.

ftp = FTP('ftp.debian.org')

Se crea una instancia de dicha clase. Toma como argumentos host, user, passwd, acct y timeout siendo todos opcionales. Por defecto el puerto 21 es utilizado. Si se quisiera especificar uno diferente, se debe hacer:

ftp = FTP()
ftp.connect('ftp.debian.org', 22)

De esta manera se estaría utilizando el puerto 22.

ftp.login()

Ingresa con el usuario y contraseña especificados. Por defecto estos son «anonymous» y «anonymous@», respectivamente. Un tercer parámetro denominado acct (que por defecto es una cadena vacía) indica el nombre de la cuenta. No todos los clientes soportan ACCT, como así tantos otros siempre utilizan «ACCT noaccount».

ftp.login("admin", "admin123", "noaccount")

… podría haberse utilizado para especificar como usario «admin», «admin123» como contraseña y «noaccount» como datos de la cuenta.

ftp.cwd('debian')

La función FTP.cwd() es utilizada para cambiar de directorio o carpeta (change working directory). En este caso, luego de acceder como un usuario anónimo cambia la ubicación a la carpeta debian.

ftp.retrlines('LIST')

Retorna información sobre los archivos y carpetas en la ubicación actual. LIST es un comando definido por el protocolo, así como otros que también pueden aplicarse en esta función como RETR, NLST o MLSD. Para más información sobre los comandos véase RFC 959. El segundo parámetro es la función callback a la que se llama por cada dato recibido, por ejemplo:

def callback(info):
    print info
...
ftp.retrlines('LIST', callback)
...

Resulta en lo mismo que en el código original, ya que el callback por defecto imprime la información en sys.stdout.

ftp.retrbinary('RETR README', open('README', 'wb').write)

Ejecuta el comando RETR para descargar el archivo README en modo binario. El segundo parámetro es una función callback que será llamada por cada bloque de bytes recibidos, que a su vez estos son pasados como argumento a dicha función. En este caso se pasa la función write de un objeto file. Sin embargo, el archivo nunca se cierra, así que podría hacerse:

with open('README', 'wb') as f:
    ftp.retrbinary('RETR README', f.write)

El tercer parámetro opcional maxblocksize indica el tamaño máximo que leerá el socket que utiliza a bajo nivel (por defecto 8192). Si se especifica el último parámetro rest, un comando REST es enviado al servidor pasándolo como argumento (None por defecto).

ftp.quit()

Envía el comando QUIT al servidor para cerrar la conexión. Nótese que si el servidor responde con una excepción, FTP.close() será utilizado. Una vez aplicado cualquiera de los dos métodos no es posible reutilizar la instancia.

Obsérvese el siguiente código en donde se ejecutan varias funciones junto con su explicación. Nótese que para algunas funciones es necesario tener los privilegios correspondientes:

from ftplib import FTP, error_perm
# ...
ftp.dir()  # Imprime el contenido de la carpeta actual
print ftp.pwd()  # Imprime la ruta actual ("/debian")
ftp.mkd("nueva_carpeta")  # Crea una nueva carpeta ((m)a(k)e (d)irectory)
ftp.rmd("nueva_carpeta")  # La elimina ((r)e(m)ove (d)irectory)

# Retorna el tamaño del archivo especificado. El comando "SIZE"
# no es estándar, por lo que retorna None en caso de fallar. 
# De lo contrario, retorna un entero representando los bytes.
readme_size = ftp.size("README")
if readme_size is None:
    print "Comando SIZE no soportado."
else:
    print "README: %d bytes." % readme_size

ftp.rename("README", "LEEME")  # Renombrar un archivo

try:
    # Envia un comando y retorna la respuesta
    print ftp.sendcmd("SIZE LEEME")
except error_perm as e:
    print e

ftp.delete("LEEME")  # Eliminar

Conexión segura

Para esto el módulo brinda la clase FTP_TLS, que hereda de FTP definiendo cuatro objetos adicionales: ssl_version, auth(), prot_p() y prot_c(). Obsérvese el siguiente ejemplo brindado por la documentación:

from ftplib import FTP_TLS

ftps = FTP_TLS('ftp.python.org')
ftps.login()           # ingresar como anónimo
ftps.prot_p()          # cambiar a conexión segura
ftps.retrlines('LIST')
ftps.quit()

Puedes encontrar la descripción detallada en este enlace.

Cargar y descargar archivos

Por último una pequeña API para subir y descargar archivos vía FTP, sin necesidad de tener conocimiento de los comandos propios del protocolo.

xftp.py (descargar como ZIP)

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from ftplib import FTP

class XFTP(FTP):
    """API de alto nivel para cargar y descargar archivos
       vía FTP"""
    
    def upload(self, filename, callback=None):
        with open(filename, "rb") as f:
            self.storbinary("STOR " + filename, f, callback=callback)

    def download(self, filename, callback=None):
        cmd = "RETR " + filename
        if callback is None:
            # Usar la callback por defecto
            with open(filename, "wb") as f:
                self.retrbinary(cmd, f.write)
        else:
            # Callback del usuario
            self.retrbinary(cmd, callback)

Ejemplo:

from xftp import XFTP
from ftplib import error_perm
from os import rename

ftp = XFTP("ftp.debian.org")
ftp.login()
ftp.cwd("debian")
ftp.download("README")
rename("README", "LEER")
try:
    ftp.upload("LEER")
except error_perm:
    print "No tienes los permisos suficientes."
ftp.quit()

Especificando callbacks:

with open("archivo_recibido", "wb") as f:
    ftp.download("README", f.write)

Versión

Python 2.7

Curso online 👨‍💻

¡Ya lanzamos el curso oficial de Recursos Python en Udemy! Un curso moderno para aprender Python desde cero con programación orientada a objetos, SQL y tkinter en 2024.

Consultoría 💡

Ofrecemos servicios profesionales de desarrollo y capacitación en Python a personas y empresas. Consultanos por tu proyecto.

3 comentarios.

    • Raycris Maldonado says:

      Esto fue lo que me sirvio para recorrer multiples carpetas:

      auxAlmacenChrysler = CHRYSLER_PIEZAS + 'Auxiliar de almacen'
      ftp.cwd(auxAlmacenChrysler)
      print('\nNos encontramos en la carpeta: '+ ftp.pwd() + "\n")
      ftp.dir();

      CHRYSLER_PIEZAS -> Es una constante la cual tiene el path donde quiero entrar.
      Auxiliar de almacen – > Es el nombre de la carpeta final la cual debo entrar.
      ftp.cwd(auxAlmacenChrysler) -> Entra a la carpeta con el path indicado.

Deja una respuesta