Para hacer una araña que explore un servidor FTP se puede aprovechar el_ftplib de_python... solo tiene un impedimento, que cuando se pide la lista de archivos en un directorio se devuelve por stdout, lo que es bastante molesto para algo como esto.

La solución más sencilla es substituir la salida estándar ( sys.stdout ) por un objeto que almacene los datos, el único método que hace falta en el objeto es objeto.write(string) , el que se usa para mostrar strings por la pantalla, esto puede servir:

class catcher():

def clean(self):

  self.trap = ""

def readlines(self):

  return self.trap.split("\n")

def readline(self):

  l = self.readlines()

  res = l.pop(0)

  self.trap = "\n".join(l).strip()

  return res

def init(self):

  self.clean()

def write(self, s):

  self.trap += s

Después de dar el cambiazo por stdout y  de que recoja los string, este queda en catcher.trap, además se pueden utilizar los siguientes métodos:

catcher.readlines() Devuelve lo recojido como un array de strings (una por línea).

catcher.readline() Devuelve una línea de lo recojido y la elimina.

catcher.clean() Elimina lo recojido

Después hace falta convertir las líneas en algo útil, esto se puede hacer con:

import sys, re

Elimina los dobles espacios

def smash(s):

b = ""

while b != s:

   b = s

   s = s.replace("  ", " ")

return s

Convierte las líneas de ls en algo útil

def parseLs(sa, cdir = None):

d = []

cd = (cdir != None)

for s in sa: # Por cada línea

   s = smash(s.strip()) # Se elimina lo que sobra



   if (len(s) < 1): # Si aún queda algo

       continue



   o = {} # Aquí se guardarán los datos

   sld = s.split(" ") # Se separan por los espacios



   # "Si para solucionar un problema se te ocurre

   # 'esto lo puedo hacer con expresiones regulares!'

   # ...ya tienes dos problemas" -- No me acuerdo xD

   if (re.match(".{10}\ [0-9]{1,9}\ [a-z,A-Z,0-9]{1,50}\ " +

       "[a-z,A-Z,0-9]{1,50}\ [0-9]{1,1000}\ [a-z,A-Z,0-9]{3}\ " +

       "[0-9]{2}\ [0-9]{2}\:[0-9]{2}\ .{1,255}", s) != None):



       # Lee la información

       if (len(sld) < 5) or not(":" in s):

           print >>sys.stderr, "-|>", s

           continue



       o['perm' ] = sld[0]

       o['num'  ] = sld[1]

       o['user' ] = sld[2]

       o['group'] = sld[3]

       o['size' ] = sld[4]

       o['mon'  ] = sld[5]

       o['day'  ] = sld[6]

       o['hour' ] = sld[7]



       if (cd):

           o['pwd'] = cdir



       n = s.index(":")

       # Obtiene el resto y se guarda como el nombre

       o['name' ] = s[ (s[n : ].index(" ") + n + 1) : ].strip()

       d.append(o) # Se añade a la lista



   # Otra opción de formateado

   elif(re.match(".{10}\ [0-9]{1,9}\ [a-z,A-Z,0-9]{1,50}\ " +

        "[a-z,A-Z,0-9]{1,50}\ [0-9]{1,1000}\ [a-z,A-Z,0-9]{3}\ " +

        "[0-9]{2}\ [0-9]{4}\ .{1,255}",s) != None):



       # Lo mismo

       o['perm' ] = sld[0]

       l = len(sld[0])

       o['num'  ] = sld[1]

       l += len(sld[1])

       o['user' ] = sld[2]

       l += len(sld[2])

       o['group'] = sld[3]

       l += len(sld[3])

       o['size' ] = sld[4]

       l += len(sld[4])

       o['mon'  ] = sld[5]

       l += len(sld[5])

       o['year' ] = sld[6]

       l += len(sld[6])

       o['name' ] = s[ s[ 7 + l : ].index(" ") + 8 + l : ]



       if (cd):

           o['pwd'] = cdir



       d.append(o) # Se añade a la lista



   # Si falla

   else:

       print >>sys.stderr, "--->", s, "<---"

       raw_input("")

return d

Solo hay que alimentar la función parseLs con el array de líneas (desde catcher.readlines() por ejemplo) y opcionalmente con el directorio actual (solo se añade como parámetro) y devuelve un array de diccionarios con al menos las entradas 'perm' (permisos), 'num', 'user', 'group', 'size' y 'mon' (mes).

Nota: parseLs() funciona con los formateados que me he encontrado, que seguramente no sean todos.

A partir de ahí el resto es parecido a una araña normal, por ejemplo [ ftp_crawler.py ], si quieres guardar los datos en un PostgreSQL, hay que descomentar las líneas 8, 41 (aquí esta la query, el nombre de la tabla es nu, cambiala por la que quieras) a 45 y 124 (en esta se define el nombre de la base de datos). La tabla usa estas columnas: 'permisos', 'numero', 'usuario', 'grupo', 'tamanho', 'path'  (varchar y pista)

Nos vemos

[Referencia] http://docs.python.org/library/ftplib.html