O módulo ftplib implementa o lado cliente do protocolo FTP na biblioteca padrão do Python. Ele permite listar diretórios, baixar, enviar, renomear e excluir arquivos em servidores compatíveis. Ainda é usado em integrações legadas, hospedagens, equipamentos e fluxos empresariais de troca de arquivos.
FTP simples transmite credenciais e dados sem criptografia. Em redes não confiáveis, prefira FTPS com FTP_TLS ou, quando o servidor exigir SFTP, use uma biblioteca própria para SSH — SFTP não é FTP e não é implementado por ftplib. Este guia prioriza FTPS, timeouts, limites e validação.
Conexão FTP básica
from ftplib import FTP
with FTP("ftp.example.com", timeout=15, encoding="utf-8") as ftp:
ftp.login("usuario", "senha")
print(ftp.pwd())
print(ftp.nlst())
Esse exemplo usa FTP sem criptografia e serve apenas para rede controlada ou servidor anônimo. Nunca envie senha real por FTP através da internet.
FTPS com FTP_TLS
import ssl
from ftplib import FTP_TLS
contexto = ssl.create_default_context()
contexto.minimum_version = ssl.TLSVersion.TLSv1_2
with FTP_TLS(
"ftp.example.com",
timeout=15,
context=contexto,
encoding="utf-8",
) as ftps:
ftps.login("usuario", "senha")
ftps.prot_p()
print(list(ftps.mlsd()))
FTP_TLS protege o canal de controle, mas o canal de dados só fica privado depois de prot_p(). Sem essa chamada, listagens e arquivos podem trafegar sem a proteção esperada.
O contexto padrão valida certificado e hostname. Não use contexto não verificado como correção para certificados expirados ou nomes incorretos. Consulte ssl no Python.
Credenciais
Não coloque usuário e senha no código-fonte. Carregue-os de variáveis de ambiente ou gerenciador de segredos. Arquivos .netrc podem ajudar em ferramentas locais, mas também contêm segredos e precisam de permissões restritas. O guia de netrc no Python mostra cuidados importantes.
Não habilite debug de protocolo em produção com credenciais reais. set_debuglevel(2) registra comandos e respostas e pode expor informações sensíveis.
Listagem estruturada com MLSD
Quando o servidor oferece suporte, mlsd() é preferível a interpretar texto de LIST.
with FTP_TLS(HOST, context=contexto, timeout=15) as ftps:
ftps.login(USER, PASSWORD)
ftps.prot_p()
for nome, fatos in ftps.mlsd(
"/entrada",
facts=["type", "size", "modify"],
):
print(nome, fatos)
O servidor não é obrigado a devolver todos os fatos solicitados. Use fatos.get("size") e valide valores antes de convertê-los.
NLST e LIST
nlst() devolve nomes, enquanto dir() e retrlines("LIST") produzem listagens textuais dependentes do servidor. Não extraia tamanho e data de LIST com posições fixas; formatos variam entre Unix, Windows e implementações.
Baixando arquivo em blocos
from pathlib import Path
DESTINO = Path("relatorio.csv")
LIMITE = 50 * 1024 * 1024
recebido = 0
def gravar(bloco: bytes) -> None:
global recebido
recebido += len(bloco)
if recebido > LIMITE:
raise ValueError("arquivo excede o limite")
arquivo.write(bloco)
with DESTINO.open("wb") as arquivo:
ftps.retrbinary(
"RETR relatorio.csv",
gravar,
blocksize=64 * 1024,
)
Use um arquivo temporário e renomeie somente após sucesso. Se a transferência falhar, não deixe o nome final apontar para conteúdo parcial.
Download atômico
from pathlib import Path
from tempfile import NamedTemporaryFile
final = Path("relatorio.csv")
with NamedTemporaryFile(
mode="wb",
dir=final.parent,
delete=False,
) as temporario:
caminho_temp = Path(temporario.name)
ftps.retrbinary("RETR relatorio.csv", temporario.write)
caminho_temp.replace(final)
Inclua tratamento que remova o temporário em caso de erro. O guia de tempfile no Python explica padrões seguros.
Verificação de integridade
FTP e FTPS não garantem que um arquivo seja o artefato esperado. Compare tamanho, hash publicado por canal confiável ou assinatura.
import hashlib
with open("relatorio.csv", "rb") as arquivo:
digest = hashlib.file_digest(arquivo, "sha256").hexdigest()
if digest != SHA256_ESPERADO:
raise ValueError("hash divergente")
Veja hashlib no Python. Um hash obtido do mesmo servidor comprometido não oferece autenticação independente.
Enviando arquivos
from pathlib import Path
caminho = Path("saida.zip")
if caminho.stat().st_size > 100 * 1024 * 1024:
raise ValueError("arquivo grande demais")
with caminho.open("rb") as arquivo:
ftps.storbinary(
"STOR saida.zip.part",
arquivo,
blocksize=64 * 1024,
)
ftps.rename("saida.zip.part", "saida.zip")
Enviar com nome temporário e renomear no servidor evita que consumidores encontrem um arquivo ainda incompleto. Confirme se o rename é atômico na implementação usada.
Modo texto e modo binário
Use retrbinary() e storbinary() para quase todos os arquivos, inclusive CSV e texto, quando você quer preservar bytes exatamente. Os métodos retrlines() e storlines() aplicam semântica de linhas e podem alterar finais de linha.
Encoding de nomes
Desde Python 3.9, o padrão é UTF-8 conforme RFC 2640. Servidores antigos podem usar outra codificação. Configure encoding somente com base no contrato; não tente sequências aleatórias silenciosamente.
Normalize nomes Unicode quando a aplicação exige comparação consistente, mas preserve o nome original para operações no servidor. Não use nomes remotos diretamente como caminhos locais sem validação.
Protegendo caminhos locais
from pathlib import Path
RAIZ = Path("downloads").resolve()
def destino_seguro(nome_remoto: str) -> Path:
nome = Path(nome_remoto).name
if nome in {"", ".", ".."}:
raise ValueError("nome inválido")
destino = (RAIZ / nome).resolve()
if RAIZ not in destino.parents:
raise ValueError("path traversal")
return destino
Remova separadores, nomes reservados e caracteres de controle conforme o sistema operacional.
Diretórios remotos
cwd(), mkd(), rmd() e pwd() manipulam diretórios. Evite montar comandos com caminhos não validados. O servidor interpreta a string enviada; não existe escaping universal que torne qualquer nome seguro.
Modo passivo
O modo passivo é ativado por padrão e costuma funcionar melhor com NAT e firewalls. set_pasv(False) usa modo ativo, que pode exigir conexões de entrada no cliente. Escolha conforme a infraestrutura e restrinja portas no servidor.
Retomando transferências
retrbinary() e storbinary() aceitam rest, geralmente um offset em bytes. O servidor pode não suportar.
offset = caminho_temp.stat().st_size
with caminho_temp.open("ab") as arquivo:
ftps.retrbinary(
"RETR imagem.iso",
arquivo.write,
rest=offset,
)
Antes de retomar, confirme que o arquivo remoto não mudou, usando tamanho, data e preferencialmente hash. Caso contrário, você pode concatenar versões diferentes.
Exceções do ftplib
As principais classes são error_temp para respostas 4xx temporárias, error_perm para 5xx permanentes, error_reply e error_proto. all_errors também inclui falhas de socket.
from ftplib import error_perm, error_temp
try:
ftps.retrbinary("RETR arquivo.dat", callback)
except error_temp as erro:
print("falha possivelmente temporária", erro)
except error_perm as erro:
print("permissão ou arquivo inexistente", erro)
Não repita automaticamente erros permanentes. Para temporários, use backoff, jitter e limite de tentativas.
Timeouts
Defina timeout na conexão. Também controle tempo total da tarefa e tamanho. Uma transferência que continua recebendo pequenos blocos pode durar indefinidamente apesar de não disparar timeout de socket.
Fechando a conexão
Use context manager. quit() envia o comando QUIT de forma educada, mas pode falhar se a conexão já estiver quebrada. O fechamento do contexto garante limpeza. Não reutilize uma instância depois de quit() ou close().
FTP, FTPS e SFTP
FTP é o protocolo original sem criptografia. FTPS adiciona TLS ao FTP e é suportado por FTP_TLS. SFTP é um protocolo diferente sobre SSH. Confundir os nomes gera erros de conexão e decisões inseguras.
Testes recomendados
Teste login inválido, certificado inválido, falta de prot_p(), nome Unicode, listagem sem MLSD, arquivo vazio, limite excedido, conexão interrompida, retomada incompatível, rename, erro temporário, erro permanente e limpeza do temporário.
Erros comuns
Os erros frequentes são usar FTP simples com senha, esquecer prot_p(), desativar validação TLS, interpretar LIST com posições fixas, confiar em nomes remotos, escrever diretamente no destino final, não limitar tamanho, registrar credenciais, repetir uploads não idempotentes e confundir FTPS com SFTP.
Conclusão
ftplib atende integrações FTP e FTPS sem dependências externas. Use FTP_TLS com contexto validado e prot_p(), prefira mlsd(), transfira em blocos para temporários, valide tamanho e integridade, proteja caminhos e trate erros por categoria.
Consulte a documentação oficial de ftplib, o RFC 959 do FTP e o RFC 4217 do FTPS. Para sistemas novos, considere protocolos mais simples de proteger e operar.







