1

Pitch: 5 pegadinhas ao converter XML de NF-e em planilha (com código Python)

Se você já somou nota cancelada no faturamento ou viu a chave de acesso virar 3,52609E+43 no Excel, este post é para você.

Aviso logo de cara: nós, da Brasil Utils, criamos a ferramenta paga que aparece no fim do post. Mas a maior parte do texto é o que aprendemos escrevendo o parser, e o código abaixo resolve o básico sem ferramenta nenhuma.

O problema

Escritório contábil, e-commerce, integrador de ERP: todo mundo recebe XML de NF-e aos milhares (ZIP do ERP, portal da SEFAZ, e-mail do fornecedor), e alguém precisa disso numa planilha ou num banco. Normalmente em dois níveis: uma linha por nota (totais e impostos) e uma linha por item (NCM, CFOP, quantidades).

1. Sem namespace, o find() não acha nada

Todo elemento está no namespace http://www.portalfiscal.inf.br/nfe. Este script lê uma pasta de XMLs, ignora arquivos quebrados, valida a chave e gera um CSV que abre direto no Excel brasileiro:

import csv, glob
import xml.etree.ElementTree as ET

NS = {"nfe": "http://www.portalfiscal.inf.br/nfe"}

def txt(el, path):
    return el.findtext(path, default="", namespaces=NS) if el is not None else ""

def dv_chave(ch43: str) -> int:
    # módulo 11, pesos 2 a 9 da direita para a esquerda
    pesos = [2, 3, 4, 5, 6, 7, 8, 9]
    s = sum(int(d) * pesos[i % 8] for i, d in enumerate(reversed(ch43)))
    r = s % 11
    return 0 if r < 2 else 11 - r

linhas = []
for caminho in sorted(glob.glob("xmls/*.xml")):
    try:
        raiz = ET.parse(caminho).getroot()
    except ET.ParseError as e:
        print("XML inválido, ignorado:", caminho, e)
        continue
    inf = raiz.find(".//nfe:infNFe", NS)
    if inf is None:  # evento (cancelamento, CC-e...) ou outro documento
        continue
    ide, emit = inf.find("nfe:ide", NS), inf.find("nfe:emit", NS)
    tot = inf.find("nfe:total/nfe:ICMSTot", NS)
    chave = inf.get("Id", "")[3:]  # "NFe" + 44 dígitos
    linhas.append({
        "chave": chave,
        "chave_valida": len(chave) == 44 and chave.isdigit()
                        and dv_chave(chave[:43]) == int(chave[43]),
        "modelo": txt(ide, "nfe:mod"),  # 55 = NF-e, 65 = NFC-e
        "serie": txt(ide, "nfe:serie"),
        "numero": txt(ide, "nfe:nNF"),
        "emissao": txt(ide, "nfe:dhEmi"),
        "emitente_cnpj": txt(emit, "nfe:CNPJ"),
        "emitente": txt(emit, "nfe:xNome"),
        "valor_nota": txt(tot, "nfe:vNF").replace(".", ","),
    })

# "Excel Brasil": ponto e vírgula, vírgula decimal e BOM UTF-8
if linhas:
    with open("notas.csv", "w", newline="", encoding="utf-8-sig") as f:
        w = csv.DictWriter(f, fieldnames=list(linhas[0]), delimiter=";")
        w.writeheader()
        w.writerows(linhas)

2. Nem todo XML tem a mesma raiz

Aparecem nfeProc (nota com protocolo de autorização), NFe "pelado" (sem protocolo, que pode nem ter sido autorizada) e enviNFe (lote com várias notas). Por isso o script usa .//nfe:infNFe em vez de um caminho fixo. Para separar autorizada, denegada e rejeitada, leia o cStat dentro de protNFe/infProt.

3. Valide a chave de acesso

Os 44 dígitos terminam num DV em módulo 11 (a função dv_chave acima). Vale também conferir a chave contra o conteúdo (UF, AAMM, CNPJ, modelo, série e número), porque XML editado à mão existe.

4. O cancelamento chega em outro arquivo

O cancelamento (evento 110111) vem num procEventoNFe separado. Se você só olha as notas, a cancelada entra no faturamento. Cruze os eventos com as notas pela chave.

5. Os detalhes que mais dão trabalho

  • Os impostos por item ficam em grupos variáveis (ICMS00, ICMS10, ICMSSN102...), então é preciso tratar o grupo de forma genérica, sem fixar o nome da tag.
  • A reforma tributária já colocou IBS/CBS no layout (NT 2025.002).
  • LGPD: NFC-e e nota para consumidor trazem CPF. Pense se você realmente precisa guardá-lo.
  • O Excel come zeros à esquerda e transforma a chave em notação científica. O BOM e o ; ajudam, mas na importação marque a coluna da chave como Texto.

A ferramenta (o pitch)

Juntamos tudo isso num Actor na Apify, o NF-e XML Parser to Excel/CSV — Converter XML de Nota Fiscal: https://apify.com/brasil_utils/nfe-xml-parser

Você envia XMLs ou ZIPs (inclusive ZIP dentro de ZIP) e recebe JSON e os CSVs INVOICES.csv, ITEMS.csv e EVENTS.csv, no formato padrão ou no Excel Brasil. Ele traz o status do protocolo, os impostos por item (com IBS/CBS, ST e Simples Nacional), DIFAL, pagamentos, troco e duplicatas, e mascara o CPF por padrão. Lê arquivos em UTF-8, ISO-8859-1 e UTF-16 e processa cerca de 1.000 notas em uns 4 segundos. Custa US$0,003 por nota processada; erros, eventos (cancelamento, CC-e, inutilização) e chaves duplicadas não são cobrados, e se a execução for reiniciada ela continua de onde parou sem cobrar de novo. Roda pelo painel, pela API ou pelo Make, Zapier e n8n. Quer só ver a saída antes de mandar suas notas? O campo de colar XML já vem com uma nota fictícia de demonstração: rodar só com ela cobra 1 nota (US$0,003), e se você enviar arquivos a demo é ignorada.

Se você só precisa de algumas colunas, o script acima resolve. E ficamos curiosos: qual foi o XML mais estranho que já apareceu para vocês? Casos que ainda não tratamos são muito bem-vindos nos comentários.

Quem escreve é o Eduardo, desenvolvedor da Brasil Utils. Respondo pessoalmente aqui nos comentários.

Carregando publicação patrocinada...