Arquivos CSV (Comma-Separated Values) são um dos formatos mais comuns para troca de dados tabulares, amplamente utilizados em planilhas, bancos de dados e sistemas legados. Nesta aula, você aprenderá a manipular esses arquivos com o módulo csv da biblioteca padrão do Python, que oferece ferramentas robustas para leitura, escrita e processamento de dados.

Dominar o módulo csv é essencial para qualquer programador Python, pois permite integrar dados externos em seus programas, automatizar tarefas de importação/exportação e lidar com variações de formato (como delimitadores diferentes e cabeçalhos). Vamos explorar desde os conceitos básicos até técnicas mais avançadas, com exemplos práticos e boas práticas.

Módulo csv

O módulo csv implementa classes e funções para ler e escrever dados no formato CSV. Ele oferece uma API que abstrai as peculiaridades do formato, como a necessidade de citar campos que contenham delimitadores, quebras de linha ou aspas. A principal vantagem é que você não precisa se preocupar com esses detalhes manualmente.

O módulo fornece dois objetos principais: reader e writer. O reader permite iterar sobre as linhas de um arquivo CSV, convertendo cada linha em uma lista de strings. O writer faz o inverso: recebe uma lista de strings e a escreve no arquivo, adicionando os delimitadores e aspas necessários. Além disso, existem as classes DictReader e DictWriter, que trabalham com dicionários, como veremos adiante.

Um exemplo simples de leitura de um arquivo CSV:

import csv

with open('dados.csv', mode='r', encoding='utf-8') as arquivo:
    leitor = csv.reader(arquivo)
    for linha in leitor:
        print(linha)

Nesse exemplo, abrimos o arquivo dados.csv no modo leitura, criamos um objeto reader e iteramos sobre ele. Cada linha é uma lista de strings, onde cada elemento corresponde a uma coluna.

Para escrita, usamos o writer:

import csv

with open('saida.csv', mode='w', encoding='utf-8', newline='') as arquivo:
    escritor = csv.writer(arquivo, delimiter=';')
    escritor.writerow(['Nome', 'Idade'])
    escritor.writerow(['Ana', 30])
    escritor.writerow(['Bruno', 25])

Observe que passamos newline='' ao abrir o arquivo para evitar problemas com quebras de linha no Windows. O parâmetro delimiter permite escolher o separador de campos (padrão é vírgula).

Leitura e escrita

Vamos aprofundar as operações de leitura e escrita, incluindo o tratamento de cabeçalhos e a conversão de tipos. Ao ler um CSV, muitas vezes precisamos pular a primeira linha (cabeçalho) ou processar os dados como tipos específicos (inteiros, floats). O módulo csv não faz conversão automática; você deve fazer isso manualmente.

Para pular o cabeçalho, podemos usar a função next() no objeto leitor:

import csv

with open('dados.csv', 'r', encoding='utf-8') as arquivo:
    leitor = csv.reader(arquivo)
    cabecalho = next(leitor)  # pula a primeira linha
    print('Cabeçalho:', cabecalho)
    for linha in leitor:
        nome, idade = linha
        idade = int(idade)
        print(f'{nome} tem {idade} anos')

Na escrita, podemos escrever várias linhas de uma vez com writerows(). Também é possível usar o parâmetro quoting para controlar quando as aspas são adicionadas. Por exemplo, csv.QUOTE_NONNUMERIC cita apenas campos não numéricos.

import csv

dados = [
    ['Nome', 'Idade', 'Cidade'],
    ['Ana', 30, 'São Paulo'],
    ['Bruno', 25, 'Rio de Janeiro']
]

with open('saida.csv', 'w', encoding='utf-8', newline='') as arquivo:
    escritor = csv.writer(arquivo, quoting=csv.QUOTE_MINIMAL)
    escritor.writerows(dados)

Outro ponto importante é a leitura de arquivos grandes. O reader é um iterador, portanto não carrega todo o arquivo na memória de uma vez. Isso é eficiente para arquivos com milhões de linhas.

DictReader/DictWriter

As classes DictReader e DictWriter são mais convenientes quando o CSV possui cabeçalho, pois permitem acessar os campos pelo nome da coluna, em vez de índices numéricos. O DictReader usa a primeira linha como chaves dos dicionários para as linhas subsequentes.

Exemplo de leitura com DictReader:

import csv

with open('pessoas.csv', 'r', encoding='utf-8') as arquivo:
    leitor = csv.DictReader(arquivo)
    for linha in leitor:
        print(linha['Nome'], linha['Idade'])

O DictWriter funciona de forma semelhante, mas você precisa especificar os nomes dos campos (fieldnames) que serão usados como cabeçalho e chaves dos dicionários.

import csv

campos = ['Nome', 'Idade', 'Cidade']
dados = [
    {'Nome': 'Ana', 'Idade': 30, 'Cidade': 'São Paulo'},
    {'Nome': 'Bruno', 'Idade': 25, 'Cidade': 'Rio de Janeiro'}
]

with open('saida.csv', 'w', encoding='utf-8', newline='') as arquivo:
    escritor = csv.DictWriter(arquivo, fieldnames=campos)
    escritor.writeheader()  # escreve o cabeçalho
    escritor.writerows(dados)

Se o dicionário tiver chaves que não estão em fieldnames, você pode usar o parâmetro extrasaction para ignorar ou gerar erro. O padrão é 'raise', mas é comum usar 'ignore'.

Outra vantagem do DictReader é que ele permite especificar um delimitador diferente, como ponto e vírgula, muito comum em países que usam vírgula como separador decimal.

import csv

with open('dados.csv', 'r', encoding='utf-8') as arquivo:
    leitor = csv.DictReader(arquivo, delimiter=';')
    for linha in leitor:
        print(linha)

Cuidados

Ao trabalhar com arquivos CSV, alguns cuidados são essenciais para evitar erros e problemas de segurança.

  • Encoding: Sempre especifique o encoding ao abrir o arquivo (ex.: utf-8). Arquivos CSV podem vir em outros encodings, como latin-1 ou cp1252. Use encoding='utf-8' para garantir compatibilidade, mas se encontrar erros de decodificação, tente outros encodings. No Windows, o padrão pode ser cp1252; use encoding='utf-8-sig' para ler arquivos com BOM.
  • Delimitadores: O padrão é a vírgula, mas em alguns contextos (como Brasil) usa-se ponto e vírgula. Sempre verifique o formato do arquivo e ajuste o parâmetro delimiter.
  • Quebras de linha: Ao escrever, use newline='' no open() para evitar que o Python converta quebras de linha de forma indesejada.
  • Injeção de fórmulas: Em planilhas como Excel, campos que começam com =, +, - ou @ podem ser interpretados como fórmulas, causando vulnerabilidades (CSV injection). Para mitigar, você pode prefixar esses campos com uma aspas simples (') ou um espaço.
  • Tratamento de aspas e caracteres especiais: O módulo csv lida automaticamente com aspas, mas se você estiver gerando CSV manualmente, cuidado com caracteres como vírgulas dentro dos campos.
  • Linhas malformadas: Arquivos CSV podem conter linhas com número diferente de colunas. Use csv.Error para tratar exceções ou valide os dados antes de processar.

Aqui está um exemplo de escrita segura contra injeção de fórmulas:

import csv

def sanitizar(valor):
    if isinstance(valor, str) and valor.startswith(('=', '+', '-', '@')):
        return "'" + valor
    return valor

campos = ['Nome', 'Formula']
dados = [
    {'Nome': 'Ana', 'Formula': '=SOMA(A1:A2)'},
    {'Nome': 'Bruno', 'Formula': '2+2'}
]

with open('seguro.csv', 'w', encoding='utf-8', newline='') as arquivo:
    escritor = csv.DictWriter(arquivo, fieldnames=campos)
    escritor.writeheader()
    for linha in dados:
        linha = {k: sanitizar(v) for k, v in linha.items()}
        escritor.writerow(linha)

Boas práticas

Ao trabalhar com CSV, use sempre o gerenciador de contexto with para garantir que o arquivo seja fechado corretamente. Prefira DictReader e DictWriter para legibilidade e manutenção. Se estiver lidando com arquivos muito grandes, processe linha por linha em vez de carregar tudo na memória. E lembre-se de testar com diferentes encodings e delimitadores para garantir robustez.

Referências

Exercícios

  1. Crie um script Python que leia um arquivo CSV chamado alunos.csv contendo as colunas nome e nota, e imprima na tela o nome de cada aluno com sua nota, formatado como Nome: ... - Nota: ....

    ✓ Resposta:
    import csv
    
    with open('alunos.csv', 'r', encoding='utf-8') as arquivo:
        leitor = csv.reader(arquivo)
        next(leitor, None)  # pula cabeçalho se existir
        for linha in leitor:
            nome, nota = linha
            print(f'Nome: {nome} - Nota: {nota}')
    
  2. Escreva um script que crie um arquivo CSV chamado produtos.csv com as colunas produto, preco e quantidade. Inclua pelo menos 3 produtos com preços decimais.

    ✓ Resposta:
    import csv
    
    produtos = [
        ['produto', 'preco', 'quantidade'],
        ['Caneta', 1.50, 100],
        ['Caderno', 12.90, 50],
        ['Lápis', 0.80, 200]
    ]
    
    with open('produtos.csv', 'w', encoding='utf-8', newline='') as arquivo:
        escritor = csv.writer(arquivo)
        escritor.writerows(produtos)
    
  3. Usando DictReader, leia um arquivo CSV chamado vendas.csv (com colunas vendedor e total) e calcule o total de vendas de todos os vendedores.

    ✓ Resposta:
    import csv
    
    total_geral = 0
    with open('vendas.csv', 'r', encoding='utf-8') as arquivo:
        leitor = csv.DictReader(arquivo)
        for linha in leitor:
            total_geral += float(linha['total'])
    print(f'Total de vendas: R$ {total_geral:.2f}')
    
  4. Escreva um script que use DictWriter para criar um arquivo CSV chamado funcionarios.csv com os campos nome, departamento e salario. Os dados devem ser fornecidos como uma lista de dicionários.

    ✓ Resposta:
    import csv
    
    campos = ['nome', 'departamento', 'salario']
    funcionarios = [
        {'nome': 'Ana', 'departamento': 'TI', 'salario': 5000},
        {'nome': 'Bruno', 'departamento': 'RH', 'salario': 4000}
    ]
    
    with open('funcionarios.csv', 'w', encoding='utf-8', newline='') as arquivo:
        escritor = csv.DictWriter(arquivo, fieldnames=campos)
        escritor.writeheader()
        escritor.writerows(funcionarios)
    
  5. Explique como evitar o problema de injeção de fórmulas ao gerar um CSV que será aberto em Excel. Dê um exemplo de código que sanitiza os dados.

    ✓ Resposta: Para evitar injeção de fórmulas, devemos prefixar campos que começam com =, +, - ou @ com uma aspas simples ('). Exemplo:
    import csv
    
    def sanitizar(valor):
        if isinstance(valor, str) and valor.startswith(('=', '+', '-', '@')):
            return "'" + valor
        return valor
    
    # Uso no DictWriter:
    # linha = {k: sanitizar(v) for k, v in linha.items()}