← Back to list

Como automatizei a extração de 10 anos de dados do IBGE com Python

Extraindo tabelas de PDFs complexos da PNAD usando pdfplumber, pandas e um pouco de paciência.

SIS DATA · 2026-05-21 17:10 · 0 claps · 6.0 min read
#python #beautifulsoup #data-extraction
Open on Medium ↗

Como automatizei a extração de 10 anos de dados do IBGE com Python

Extraindo tabelas de PDFs complexos da PNAD usando pdfplumber, pandas e um pouco de paciência.

Tudo começou com uma mensagem simples: "Amor, você pode me ajudar a pegar uns dados do IBGE para um trabalho?"

Minha esposa estava na faculdade e precisava dos microdados da PNAD — Pesquisa Nacional por Amostra de Domicílios para analisar a evolução da distribuição de renda no Brasil ao longo de uma década. O problema? Ela precisava especificamente das tabelas 4.6 (pessoas por classes de rendimento) e 4.22 (a mesma segmentação por sexo), para cada ano de 2002 a 2013.

Se você já tentou extrair dados de PDFs do IBGE, sabe que não é simplesmente "copiar e colar". As tabelas são extensas, o layout é preservado, e fazer isso manualmente para 10 anos significaria dias de trabalho tedioso — sem garantia de que os números sairiam corretos.

Respirei fundo, olhei para o terminal e pensei: "Tem que ter um jeito melhor."

O desafio: PDFs que não colaboram

Os PDFs da PNAD são documentos digitalizados com layout publishing — ou seja, as tabelas foram montadas visualmente, não como estruturas de dados. Cada célula, cada linha, cada número está posicionado em coordenadas absolutas na página. Não existe uma tag <table> escondida ali.

Sem OCR, sem metadados. Apenas palavras flutuando em posições precisas (X, Y) no espaço da página.

Além disso, cada PDF tem centenas de páginas. As tabelas 4.6 e 4.22 estão enterradas no meio do documento, entre outras dezenas de tabelas. Era preciso:

  • Baixar automaticamente cada PDF do catálogo online do IBGE.

  • Localizar as páginas certas dentro de cada arquivo.

  • Extrair os dados de tabelas cujo único "formato" é a posição espacial das palavras.

  • Organizar tudo em colunas padronizadas: ano, situação, classes de rendimento, pessoas (total/homens/mulheres) e rendimento médio.

  • Exportar para algo utilizável — no caso, planilhas Excel.

Fazer isso manualmente? Improvável. Automatizar? Desafio aceito.

A solução: um pipeline em 3 atos

O resultado foi um extrator escrito em Python 3.12, organizado em torno de três etapas bem definidas, cada uma com seu próprio componente:

1. Coleta — IBGEClient + PnadScraper

O primeiro passo era obter os PDFs. O IBGEClient acessa o catálogo online do IBGE via requests + BeautifulSoup, localiza os links de todos os PDFs da PNAD no período desejado e baixa apenas aqueles ainda não presentes no disco. O PnadScraper orquestra essa etapa, garantindo que o download seja feito de forma inteligente (pulando arquivos já existentes).

2. Processamento — PnadProcessor (o coração do projeto)

Aqui mora a mágica — e a complexidade. O PnadProcessor abre cada PDF com a biblioteca pdfplumber e faz o seguinte:

  1. Localiza as tabelas: Varre página por página procurando pelo texto "Tabela 4.6" e "Tabela 4.22" usando regex. Uma vez encontrada a primeira página da tabela, continua capturando páginas seguintes até encontrar outra tabela — isso porque cada tabela pode ocupar várias páginas.

  2. Extrai por coordenadas espaciais: Em vez de tentar ler a tabela como um bloco (o que pdfplumber tenta fazer com extract_table()), a abordagem aqui é mais artesanal: extrai todas as palavras das páginas da tabela com suas coordenadas (x0, x1, y0) e depois as reagrupa em linhas.

  3. Identifica o cabeçalho: Procura a linha que contém "Total", "Homens" e "Mulheres" — o cabeçalho clássico dessas tabelas.

  4. Determina limites de colunas: Usa a posição X das palavras do cabeçalho para inferir onde cada coluna começa e termina, expandindo os limites para cobrir os intervalos entre palavras.

  5. Atribui valores às colunas: Cada palavra na linha de dados é atribuída à coluna correta com base em sua coordenada X. Palavras que caem fora dos limites conhecidos são tratadas como parte do rótulo da linha.

# Trecho ilustrativo da lógica de atribuição às colunas
for w in sorted(line_words, key=lambda x: x["x0"]):
x0 = w["x0"]
text = w["text"]
assigned = False
for i, (left, right, _) in enumerate(col_boundaries):
if left <= x0 <= right:
# Palavra pertence a esta coluna
values[i] = (values[i] + " " + text).strip()
assigned = True
break
if not assigned:
label_parts.append(text)  # Fora das colunas: é rótulo de linha

O resultado é um pandas.DataFrame perfeitamente estruturado, com as colunas padronizadas do projeto.

Exportação — ExcelWriter

Por fim, o ExcelWriter recebe os dicionários de DataFrames e os persiste em arquivos .xlsx usando pandas + openpyxl. São gerados:

  • Um arquivo consolidado (pnad_tabelas_4_6_e_4_22.xlsx) com abas separadas por ano e tabela.

  • Arquivos individuais (pnad{ano}{tabela}.xlsx) para cada ano/tabela — ao todo, 20 arquivos.

Arquitetura do sistema

Visualmente, o fluxo é simples e linear:

main()
├── PnadScraper.collect_and_download()
│     └── IBGEClient (catálogo → links → download)
├── PnadProcessor.process_all()
│     └── PnadProcessor.process_pdf() (para cada PDF)
│           ├── _find_table_pages()       # Localiza por regex
│           └── _extract_table_from_words() # Extrai por coordenadas
└── ExcelWriter.save_all()
└── pd.ExcelWriter() + df.to_excel()

Cada componente tem uma responsabilidade única e testável — uma arquitetura que facilitou muito a depuração e a evolução do código.

Resultados

O pipeline foi executado e processou com sucesso 10 dos 11 anos do período:

| Anos | Tabela 4.6 | Tabela 4.22 |
|------|:----------:|:-----------:|
| 2002 | ✅ | ✅ |
| 2004 | ✅ | ✅ |
| 2005 | ✅ | ✅ |
| 2006 | ✅ | ✅ |
| 2007 | ✅ | ✅ |
| 2008 | ✅ | ✅ |
| 2009 | ✅ | ✅ |
| 2011 | ✅ | ✅ |
| 2012 | ✅ | ✅ |
| 2013 | ✅ | ✅ |

Nota: O ano de 2010 não é contemplado pois a PNAD não foi a campo naquele ano (Censo Demográfico).

Cada planilha gerada segue uma estrutura padronizada:

| Coluna | Descrição |
|--------|-----------|
| Ano | Ano da pesquisa |
| Situação / Classes de rendimento | Descrição da linha (ex.: "Total", "Urbana", "Até 1/2 salário mínimo") |
| Pessoas - Total / Homens / Mulheres | Quantidade de pessoas |
| Rendimento (R$) - Total / Homens / Mulheres | Rendimento médio em reais |

Total gerado: 1 arquivo consolidado + 20 arquivos individuais (2 tabelas × 10 anos).

A limitação honesta: o ano 2003

Nem tudo saiu perfeito. O PDF de 2003 utiliza uma codificação proprietária de fonte (CJK / fonte embedada não padrão) que impede a extração de texto por bibliotecas como pdfplumber. As páginas retornam texto vazio, e sem texto não é possível localizar nem extrair as tabelas.

É o tipo de problema que foge do escopo de uma abordagem puramente baseada em layout. Uma solução possível seria utilizar OCR — com pytesseract + pdf2image, por exemplo — para processar esse PDF específico. Fica como melhoria futura.

O que aprendi

Este projeto, que começou como um favor para a minha esposa, acabou se tornando um estudo fascinante sobre:

  • Extração de dados de PDFs com layout complexo — pdfplumber é uma ferramenta poderosa quando você entende o modelo de dados subjacente (palavras com coordenadas).

  • Parsing por coordenadas espaciais — uma técnica útil sempre que você precisa extrair informações de documentos cujo layout é consistente mas não há marcação semântica.

  • Engenharia de pipeline — separar responsabilidades em download, processamento e exportação tornou o código muito mais fácil de manter e evoluir.

  • Resiliência com falhas conhecidas — documentar a limitação do ano 2003 foi tão importante quanto celebrar os 10 anos processados.

E, acima de tudo, aprendi que um problema real e urgente (o prazo do trabalho da faculdade) é o melhor combustível para um projeto de código.

O trabalho da minha esposa? Foi entregue no prazo, com dados limpos e organizados. E o código que nasceu dessa necessidade está disponível como referência para quem mais precisar extrair dados dos PDFs da PNAD.

Para saber mais

O código completo do projeto está aberto no GitHub. Se você precisa trabalhar com dados da PNAD, enfrenta PDFs complexos no seu dia a dia, ou simplesmente quer ver a implementação detalhada da lógica de extração por coordenadas, fique à vontade para explorar, usar e contribuir.

Link do projeto: https://github.com/nandodevs/extracao_pnad_ibge

Tecnologias utilizadas:

  • Python 3.12

  • pdfplumber — extração tabular por coordenadas

  • pandas + openpyxl — manipulação e exportação de dados

  • requests + BeautifulSoup — scraping do catálogo do IBGE

  • lxml + html5lib — parsing HTML

Contribuições são bem-vindas — especialmente se alguém quiser encarar o desafio do OCR para 2003!

*Este artigo foi baseado no projeto real "Extrator de Dados PNAD — Tabelas 4.6 e 4.22", desenvolvido para automatizar a extração de dados socioeconômicos dos PDFs públicos do IBGE.*


메타데이터
post_id
7cd01e251d2a
slug
como-automatizei-a-extração-de-10-anos-de-dados-do-ibge-com-python-7cd01e251d2a
url
https://medium.com/@sis_data/como-automatizei-a-extra%C3%A7%C3%A3o-de-10-anos-de-dados-do-ibge-com-python-7cd01e251d2a
canonical_url
https://medium.com/@sis_data/como-automatizei-a-extra%C3%A7%C3%A3o-de-10-anos-de-dados-do-ibge-com-python-7cd01e251d2a
author_url
https://medium.com/@sis_data
status
ok
fetched_at
2026-06-09 15:37:30