Dados abertos da ANVISA: o que o CSV não diz
Vitor Ayres (abre em nova aba)
A API Consultas Externas da ANVISA não tem alimentos, cosméticos nem saneantes. Esses cadastros, que a ANVISA chama de registros válidos ou produtos regularizados, e as medidas contra produtos irregulares, saem em CSV no portal de dados abertos, atualizados nos dias úteis. Cada conjunto tem um dicionário de dados em PDF, com as colunas e um passo a passo para abrir no Excel. O que o dicionário não conta é o que importa para ler o arquivo por programa, todo dia, sem perder linhas.
Este texto registra o que encontramos ao converter sete desses arquivos para Parquet, todos os dias, num processo automático. Cada comportamento foi medido nos arquivos de outubro de 2026, com a data indicada. A ANVISA pode mudar o formato depois disso.
Resumo
- A codificação é Windows-1252, não Latin-1. Nossa primeira anotação errou isso.
- Aspas dentro de um valor não são escapadas, e há quebras de linha dentro de valores. Nenhum leitor pronto leu os arquivos inteiros.
- As datas vêm em dia/mês ou mês/dia, conforme o arquivo, sem aviso.
- O número de processo tem 13, 14 ou 17 dígitos, e 14 é o tamanho de um CNPJ.
- Em produtos irregulares, a coluna
nu_cnpjnão é a empresa autuada. - Cosméticos tem 1.204.980 linhas, e 11.144 processos aparecem duas vezes.
- O navegador não lê os arquivos direto, porque o servidor não envia CORS.
Onde estão os dados e por que o navegador não lê
Os cadastros de alimentos, saneantes e cosméticos ficam em CONSULTAS/PRODUTOS/, no mesmo servidor. As medidas de fiscalização, de todas as áreas, ficam em CONSULTAS/EMPRESA_FISCALIZACAO_PRODUTO/. O ciclo de análise das petições de alimentos fica na raiz.
O servidor envia ETag e Last-Modified, e responde 304 a um If-None-Match. Um dia sem mudança custa uma resposta vazia por arquivo. Ele não envia cabeçalhos de CORS, então uma página no navegador não consegue ler os arquivos. Quem quer uma busca no navegador precisa hospedar uma cópia.
Os tamanhos, medidos em outubro de 2026:
| Arquivo | CSV | Linhas |
|---|---|---|
| Alimentos | 33,7 MB | 66.681 |
| Alimentos, detalhe das apresentações | 23 MB | 66.941 |
| Saneantes | 25 MB | 144.384 |
| Produtos irregulares | 98 MB | 79.986 |
| Cosméticos | 228 MB | 1.204.980 |
As contagens são do dia em que cada arquivo entrou na conversão. O manifesto publicado tem as de hoje.
Codificação: Windows-1252, não Latin-1
Nossa primeira anotação dizia Latin-1. O dicionário de alimentos diz o certo, “1252 (padrão ANSI)”, mas só no passo a passo de importação do Excel, onde quem lê por programa não procura. Os dois são quase iguais, e a diferença está nos bytes de 0x80 a 0x9F. No Latin-1 eles são caracteres de controle. No Windows-1252 eles são o travessão (0x96), o apóstrofo curvo (0x92), as aspas curvas (0x93 e 0x94) e o símbolo de marca registrada (0x99). Os arquivos usam todos esses.
No arquivo de cosméticos, o byte 0x96 aparece 21.282 vezes e o 0x92, 5.590 vezes. Lidos como Latin-1, esses nomes ganhariam caracteres invisíveis no lugar de um travessão.
Dois defeitos já vêm de origem e ficam como estão: 11 nomes de produto foram gravados como UTF-8 e lidos como Windows-1252 antes de a ANVISA exportar, então aparecem como LOÇÃO, e 260 razões sociais começam com os caracteres de controle \x17\x10\x16\x18.
Aspas que não fecham e linhas que não terminam
O separador é ;, e os textos vêm entre aspas. Até aí, CSV comum. O problema é que uma aspa dentro de um valor não é escapada: biscoito tipo "cookies" vem assim mesmo, e um valor que termina em aspa vem como ...brilhante. "". Além disso, há valores com quebra de linha, tanto LF quanto CRLF.
Testamos três leitores. O leitor estrito do DuckDB recusa o arquivo. O leitor tolerante junta dois registros em um. O módulo csv do Python divide um registro em dois.
A regra que funcionou tem duas partes. Uma aspa só fecha um campo quando vem antes de ; ou de uma quebra de linha. E uma quebra de linha só fecha o registro quando o campo é o último, porque o leitor sabe quantos campos o cabeçalho tem. O caso que forçou a segunda parte está em saneantes: um nome de produto contém AMBIENTE "AS MENINAS", depois duas quebras de linha, depois SPRAY. Lido assim, todo registro de todos os arquivos sai com o número de campos do cabeçalho.
Os textos também trazem entidades HTML, como L'ANA MED e ". Em cosméticos, & vem em maiúsculas. Já &KISS; não é uma entidade e fica como está. Alguns valores terminam em espaço duro e quebra de linha, então todo valor é aparado.
Datas: dia ou mês primeiro, conforme o arquivo
Os arquivos de alimentos, o das petições em andamento e o de cosméticos escrevem dd/mm/yyyy HH:MM:SS. Os de saneantes, das petições finalizadas e de produtos irregulares escrevem mm/dd/yyyy HH:MM:SS. Nenhum deles avisa.
Nós fixamos um formato por arquivo, e nunca os dois. Com os dois ativos, 12/08/2026 entraria pelo primeiro que aceitasse, sem erro. Com um só, o formato errado deixa sem leitura toda data com dia acima de 12, e a trava de 20% de valores nulos por coluna derruba a conversão. É o jeito de o erro aparecer no build, e não meses depois.
Mais três detalhes:
- O vencimento do registro em alimentos vem como
MMYYYY, por exemplo122029. Toda notificação traz122029, um valor de preenchimento. Os registros vencem 5, 10, 15 ou 20 anos depois da regularização. - Em saneantes, há validades no ano 3033.
- Os booleanos são
SeNem alimentos, com umXsolto, e1e0em saneantes. Cosméticos tem uma coluna de cada tipo.
Número de processo: 13, 14 ou 17 dígitos
O dicionário dá a entender que o processo tem 17 dígitos. O arquivo de alimentos, medido em 6 de outubro de 2026:
| Dígitos | Linhas | Ativas |
|---|---|---|
| 13 | 29.647 | 90 |
| 14 | 400 | 0 |
| 17 | 36.496 | 17.647 |
| outros | 138 | 0 |
Uma regra de 17 dígitos deixa de fora 44% das linhas, quase todas histórico inativo. E os 400 processos de 14 dígitos têm o tamanho de um CNPJ, então “14 dígitos é CNPJ” é ambíguo. A nossa busca tenta CNPJ primeiro e depois processo.
Em cosméticos, medido em 7 de outubro: 17 dígitos em 1.126.326 linhas, 15 em 69.822, 13 em 8.826 e 14 em 6.
O dicionário avisa que o Excel converte campos com zeros à esquerda em número. O CNPJ vem com zeros à esquerda, como 00000000055417, então processo, CNPJ e registro são texto, nunca número.
Os números de registro têm a própria pegadinha. Nas notificações, o número de registro é o próprio processo. Nos registros, são 9 dígitos. E nu_registro, por apresentação, é o registro mais um sufixo de 4 dígitos: 13 dígitos, o mesmo tamanho de um processo antigo.
Produtos irregulares: o CNPJ que não é da empresa autuada
O arquivo de produtos irregulares tem uma linha por produto, ação e atividade de cada dossiê de fiscalização: suspensão, proibição, recolhimento, apreensão, interdição ou inutilização. Ele tem duas colunas de CNPJ, e a primeira engana.
nu_cnpj é de quem abriu o dossiê. Em 1.698 de 4.916 dossiês, é a própria ANVISA. Em alguns, é um marketplace. Em outros, é a própria empresa. A empresa autuada está em nu_cnpj_empresa_investigada, e essa coluna nem sempre traz um CNPJ: são 14 dígitos em 35.029 linhas, um CPF de 11 dígitos em 2.334, vazio em 31.658 e texto no resto, como DESCONHECIDO, Não se aplica e NA.
Cruzar com os cadastros ajuda menos do que parece. Das 8.693 linhas de alimentos que nomeiam uma empresa, só 1.667 encontram um CNPJ no cadastro de alimentos. E só 8 de 1.580 nomes de produto batem com um produto regularizado. A maior parte das medidas em alimentos é contra produtos e empresas sem registro.
Dois cuidados a mais: produtos_concatenados é cortado em 4.000 caracteres, então use produto, e 38 linhas são iguais a outra depois de aparar um espaço no fim do nome.
Cosméticos: 1,2 milhão de linhas e processos em dobro
O arquivo de cosméticos tem 1.204.980 linhas e 10 campos, sem quebra de linha dentro de valor. É o maior dos sete, e o mais regular na forma.
A pegadinha é outra: 11.144 processos aparecem duas vezes, com o mesmo CNPJ e o mesmo número de registro. Uma linha vem com st_registrado falso e o tipo de petição preenchido, e a outra com st_registrado verdadeiro e o tipo vazio. No arquivo original, as duas podem estar a um milhão de registros de distância, porque as linhas registradas vêm todas depois do registro 1.089.484. Para contar produtos, conte processos distintos.
Convertido, o arquivo vira 24,2 MB de Parquet. O nosso leitor processa o CSV em 31 segundos usando 68 MiB, e a conversão inteira leva de 30 a 40 segundos com pico de cerca de 700 MiB. Os bytes estão no nome do produto, 12,9 MB, e no número do processo, 7,1 MB, então tirar colunas não ajuda. Dividir pelo tipo de petição ajuda: 10,5 MB, 11,9 MB e 2,0 MB.
Petições de alimentos: dois arquivos sem empresa
O ciclo de análise das petições de alimentos vem em dois arquivos: as finalizadas pelo menos uma vez, e as que nunca foram. Os dois não se sobrepõem, e nenhum nomeia a empresa.
Para saber de quem é a petição, é preciso cruzar o número do processo com o cadastro de alimentos. Isso encontra 72% dos processos do arquivo de finalizadas, 163 das 172 petições abertas do tipo Petição, e nenhuma das 164 abertas do tipo Processo. Faz sentido: essas são pedidos de registro novo, e o produto ainda não existe no cadastro.
Como verificamos
Os arquivos de alimentos, saneantes e produtos irregulares são de 5 de outubro de 2026. Os de petições são de 6 de outubro, e o de cosméticos, de 7 de outubro. A conversão roda todo dia às 21:00 UTC, com DuckDB, e publica Parquet com compressão zstd em grupos de 8.192 linhas. Uma coluna que perde mais de 20% dos valores na conversão derruba o build.
Conferimos à mão, em 6 de outubro, a página de produto e a página de apresentação do painel de consultas da ANVISA contra as tabelas de alimentos. Nada que o painel mostra faltava. As outras tabelas não foram comparadas com nada.
Depois das mudanças no leitor, o Parquet dos seis arquivos menores saiu byte a byte igual ao anterior, conferido por SHA-256, com as fontes baixadas de novo em 8 de outubro.
O que não medimos: a variação dos arquivos ao longo do tempo, e se os defeitos de codificação são corrigidos na origem.
Um conversor que já trata isso
O leitor e a conversão estão no cliente anvisa-api, em Python com licença MIT. O resultado é publicado todo dia no GitHub Pages, com um manifest.json que lista cada tabela, as linhas, os bytes, o SHA-256, as colunas e a ordenação.
pip install 'anvisa[dados]'
anvisa dados list
anvisa dados build --out dist
O mesmo Parquet pode ser lido de qualquer lugar com DuckDB:
SELECT no_produto, marcas, situacao_registro
FROM 'https://vasfvitor.github.io/anvisa-api/data/<build_id>/alimentos.parquet'
WHERE nu_cnpj_empresa = '00000000000000';
O projeto não tem vínculo com a ANVISA. A busca no navegador que usa esses arquivos é o contém., e como ela funciona sem servidor está no post sobre a busca com DuckDB-WASM. As filas de análise saem todo dia em feeds Atom.
O post sobre a API da ANVISA mostra o mesmo tipo de levantamento para a parte que tem API. Se a sua empresa depende de um arquivo ou de um sistema que não se comporta como a documentação diz, é esse o trabalho que fazemos em engenharia reversa. Ligar esses dados ao seu sistema, com atualização diária e aviso quando a fonte muda, é o que fazemos em integrações.
Perguntas
- Que codificação os CSVs de dados abertos da ANVISA usam?
- Windows-1252, não Latin-1. Os bytes 0x96, 0x92, 0x93, 0x94 e 0x99 são o travessão, o apóstrofo, as aspas curvas e o símbolo de marca registrada. Lidos como Latin-1, viram caracteres de controle.
- As datas seguem um formato só?
- Não. É dia/mês em alimentos e cosméticos, e mês/dia em saneantes, nas petições finalizadas e em produtos irregulares. Dentro de cada arquivo vale um formato só.
- Quantos dígitos tem o número de processo?
- Varia. No arquivo de alimentos, 36.496 linhas têm 17 dígitos, 29.647 têm 13 e 400 têm 14, o mesmo tamanho de um CNPJ. Compare como texto, sem pontuação.
- Em produtos irregulares, o CNPJ é da empresa autuada?
- Não. A coluna nu_cnpj é de quem abriu o dossiê, e em 1.698 de 4.916 dossiês é a própria ANVISA. A empresa autuada está em nu_cnpj_empresa_investigada, que nem sempre é um CNPJ.
- Qual é o tamanho dos dados?
- O arquivo de cosméticos tem 1.204.980 linhas: 228 MB em CSV e 24,2 MB em Parquet. Os outros seis arquivos somam cerca de 11 MB em Parquet.