ProxynetProxynet

Como extrair dados de PDF: tabelas, texto e OCR

Publicado:

15 min de leitura

Acar Diveroli
Autor: Acar Diveroli
Uma pilha isométrica de cubos escuros com partes de um PDF, um cubo CSV azul no topo e um encaixe tracejado marcado needs OCR

Um fornecedor envia a lista de preços dele todo mês em um PDF de 40 páginas. A equipe financeira recebe os extratos bancários do mesmo jeito, e o relatório de mercado que você comprou na semana passada tem a melhor tabela na página 17. Você precisa dessas linhas em uma planilha, e copiá-las à mão quebra as colunas, perde os sinais de negativo e toma uma hora que você vai gastar de novo no mês que vem.

Este artigo começa pela pergunta que define o método, se o arquivo tem camada de texto, e depois passa pelo conector de PDF do Excel, pdfplumber, Camelot e tabula-py, OCR com Tesseract para documentos digitalizados e um script em lote que transforma uma pasta de PDFs em um único CSV. Todos os exemplos foram executados em PDFs de teste que geramos com o ReportLab.

O que significa extrair dados de um PDF?

Extrair dados de um PDF é transformar um conteúdo diagramado para impressão em linhas e campos que você pode ordenar e carregar em outro lugar. Um PDF não guarda "uma tabela com quatro colunas". Ele guarda instruções como "desenhe 1,007.50 nesta posição com esta fonte" e "trace uma linha daqui até ali". Toda ferramenta de extração de dados reconstrói a tabela a partir dessas coordenadas, e é por isso que o mesmo arquivo sai limpo em uma ferramenta e embaralhado em outra.

É o mesmo trabalho de tirar campos de uma página web (Como extrair dados de um site), só que com caracteres posicionados no lugar de tags HTML. Coletar PDFs de sites em grande escala faz parte de um fluxo mais amplo de extração de dados.

PDF de texto ou PDF digitalizado: como saber?

  • PDF de texto (digital). Exportado do Word, de um sistema contábil ou de uma ferramenta de relatórios. Os caracteres ficam armazenados como texto, então a extração lê os números exatos do arquivo.
  • PDF digitalizado (imagem). Cada página é uma foto de um papel. Não existe texto até o OCR (reconhecimento óptico de caracteres) deduzir os caracteres a partir dos pixels.

O teste rápido: pressione Ctrl+F e procure uma palavra que você está vendo. Se ela for encontrada, existe camada de texto. No código, uma lista page.chars vazia no pdfplumber indica que aquela página é uma digitalização. Alguns arquivos misturam os dois (um relatório digital com uma página de assinaturas digitalizada), então verifique página por página, não arquivo por arquivo.

Como funciona a extração de tabelas de PDF?

Seja no Excel, no pdfplumber ou no Camelot, os mesmos passos rodam por trás:

  1. A página é analisada. Cada caractere é lido com sua posição, fonte e tamanho, além das linhas e retângulos desenhados.
  2. Os caracteres viram palavras e linhas. Caracteres próximos na mesma linha de base formam palavras; palavras na mesma altura formam uma linha.
  3. As áreas de tabela são encontradas. A partir de linhas de grade que formam uma malha, ou de colunas de palavras alinhadas com espaço em branco entre elas.
  4. As células são montadas. Cruzamentos de linhas ou vãos entre colunas definem os limites das células, e cada palavra vai para a célula que a contém.
  5. As linhas são devolvidas. Uma lista de linhas (ou um DataFrame) que você limpa e salva.

O passo 3 causa a maior parte dos problemas. Uma grade completa é fácil; uma tabela sem bordas obriga a ferramenta a adivinhar as colunas pelo espaço em branco, e um nome de produto comprido pode deslocar todas as colunas.

Métodos de extração de PDF lado a lado

MétodoPrecisa de código?Lê digitalizações?Ideal paraOnde trava
Copiar e colarNãoNãoUma tabela pequena, uma vezColunas se juntam em uma célula, tabelas de várias páginas
Excel, Obter Dados > Do PDFNãoNãoPoucos arquivos por mês, análise no ExcelTabelas sem bordas, muitos arquivos com layouts diferentes
pdfplumber (Python)SimNãoTexto e tabelas, trabalhos em lote, controle finoExige ajuste para tabelas sem bordas
Camelot (Python)SimExtra opcionalTabelas com grade, relatório de precisão por tabelaTexto livre em volta das tabelas
tabula-py (Python)SimNãoDataFrames rápidos de arquivos cheios de tabelasExige Java instalado na máquina
Tesseract via pytesseractSimSimPáginas digitalizadas, fotos de documentosA precisão depende da qualidade da digitalização; as tabelas perdem a estrutura

Opções sem código: copiar e Excel

Copiar. Para uma tabela pequena, selecione-a no visualizador e cole. Se cada linha cair em uma única célula, use Data > Text to Columns (Dados > Texto para Colunas). Isso deixa de funcionar assim que um nome de produto tem espaços.

O conector de PDF do Excel. O Excel para Microsoft 365 no Windows consegue ler arquivos PDF pelo Power Query:

  1. Abra uma pasta de trabalho em branco e vá em Dados > Obter Dados > Do Arquivo > Do PDF.
  2. Selecione o PDF e clique em Abrir.
  3. O Navegador lista o que o Power Query encontrou: cada tabela detectada (Table001, Table002…) e cada página inteira (Page001…). Clique em um item para ver a prévia.
  4. Escolha Carregar para levar a tabela a uma planilha, ou Transformar Dados para limpá-la antes no Editor do Power Query (remover linhas de cabeçalho repetidas, mudar o tipo da coluna de preço para número).
  5. No mês seguinte, substitua o arquivo e clique em Data > Refresh All (Dados > Atualizar Tudo); os mesmos passos rodam de novo.

A documentação do conector de PDF da Microsoft lista as opções por trás dele: Pdf.Tables aceita StartPage e EndPage para arquivos grandes e uma opção MultiPageTables que junta uma tabela que continua em várias páginas. O conector não faz OCR, e o Power Query no Excel para Mac não oferece PDF como fonte. O conector de pasta combina PDFs com o mesmo layout; quando os layouts são diferentes, um script é mais fácil de manter.

Extraindo texto e tabelas com pdfplumber

O pdfplumber é uma biblioteca Python construída sobre o pdfminer.six, com seu próprio localizador de tabelas. A versão atual é a 0.11.10 (junho de 2026); o README diz que ela funciona melhor com PDFs gerados por máquina e que não faz OCR. Instale-a em um ambiente virtual:

bash
python -m venv .venv
.venv\Scripts\activate        # macOS/Linux: source .venv/bin/activate
pip install pdfplumber pandas

O básico cabe em poucas linhas:

python
import pdfplumber

with pdfplumber.open("pdfs/price-list-2026-08.pdf") as pdf:
    page = pdf.pages[0]
    print(page.extract_text())          # todo o texto, linha por linha
    table = page.extract_table()        # a maior tabela da página
    print(table[:2])
# [['SKU', 'Product', 'Unit', 'Price'], ['B-001', 'Cable set 1', 'box', '13.40']]

extract_tables() devolve todas as tabelas da página como listas de strings de célula. Por padrão, o pdfplumber procura linhas desenhadas; para uma tabela sem bordas, mude para a estratégia de texto:

python
settings = {"vertical_strategy": "text", "horizontal_strategy": "text"}
rows = page.extract_tables(table_settings=settings)

Escolha a estratégia conforme a tabela. Na nossa lista de preços com grade, a estratégia de texto dividiu "Cable set 1" em duas colunas e juntou "Unit" e "Price", porque tentou adivinhar pelos vãos entre as palavras o que as linhas já mostravam. Quando só uma parte da página é tabela, recorte primeiro com page.crop((x0, top, x1, bottom)).

Um script completo: uma pasta de PDFs para um único CSV

O script abaixo lê todos os PDFs de uma pasta, pega o número do documento no texto da primeira página, tira as linhas de tabela de todas as páginas, pula os cabeçalhos repetidos, converte os preços em números e grava um único CSV. Páginas sem camada de texto são registradas para OCR, e um arquivo corrompido não interrompe o lote.

python
import csv
import logging
import re
from pathlib import Path

import pdfplumber

logging.basicConfig(level=logging.INFO, format="%(levelname)s %(message)s")

IN_DIR = Path("pdfs")
OUT_CSV = Path("price_lists.csv")
HEADER = ["SKU", "Product", "Unit", "Price"]
DOC_NO = re.compile(r"price list (\S+)", re.IGNORECASE)


def to_number(text):
    """'1,007.50' -> 1007.5; returns None for empty or broken cells."""
    if not text:
        return None
    try:
        return float(text.replace(",", "").strip())
    except ValueError:
        return None


def rows_from_pdf(path):
    with pdfplumber.open(path) as pdf:
        first_text = pdf.pages[0].extract_text() or ""
        match = DOC_NO.search(first_text)
        doc_no = match.group(1) if match else path.stem

        for page in pdf.pages:
            if not page.chars:  # sem camada de texto: provavelmente uma digitalização
                logging.warning("%s p.%d has no text layer, needs OCR", path.name, page.page_number)
                continue
            for table in page.extract_tables():
                for row in table:
                    cells = [(c or "").strip() for c in row]
                    if cells == HEADER:  # cabeçalho repetido em toda página
                        continue
                    if len(cells) != len(HEADER):
                        logging.warning("%s p.%d skipped row %r", path.name, page.page_number, cells)
                        continue
                    sku, product, unit, price = cells
                    yield {
                        "file": path.name,
                        "doc_no": doc_no,
                        "page": page.page_number,
                        "sku": sku,
                        "product": product,
                        "unit": unit,
                        "price": to_number(price),
                    }


def main():
    fields = ["file", "doc_no", "page", "sku", "product", "unit", "price"]
    count = 0
    with OUT_CSV.open("w", newline="", encoding="utf-8-sig") as f:
        writer = csv.DictWriter(f, fieldnames=fields)
        writer.writeheader()
        for path in sorted(IN_DIR.glob("*.pdf")):
            try:
                for row in rows_from_pdf(path):
                    writer.writerow(row)
                    count += 1
            except Exception as exc:  # um arquivo corrompido não deve interromper o lote
                logging.error("%s failed: %s", path.name, exc)
    logging.info("wrote %d rows to %s", count, OUT_CSV)


if __name__ == "__main__":
    main()

Na nossa pasta de teste (uma lista de preços de 70 linhas em duas páginas, outra de 8 linhas e uma cópia digitalizada), a saída foi:

bash
WARNING price-list-scan.pdf p.1 has no text layer, needs OCR
INFO wrote 78 rows to price_lists.csv

utf-8-sig faz o Excel mostrar corretamente nomes acentuados como "Café", e file, doc_no e page em cada linha levam cada número de volta à sua página. Onde guardar as linhas depois está em Como salvar dados de scraping em CSV, JSON e SQLite; a limpeza, em Como limpar dados de scraping com pandas em Python.

Camelot e tabula-py: quando usar

Duas outras bibliotecas se concentram só em tabelas.

O Camelot chegou à versão 2.0.0 em junho de 2026. read_pdf() usa por padrão o modo lattice para tabelas com grade; stream e network cuidam das tabelas sem bordas, e auto escolhe por página. Cada tabela tem um parsing_report com uma pontuação de precisão para sinalizar páginas ruins. Veja a documentação do Camelot.

O tabula-py é um wrapper Python do tabula-java, por isso precisa de Java instalado. tabula.read_pdf() devolve diretamente uma lista de DataFrames do pandas.

python
import camelot
import tabula

PDF = "pdfs/price-list-2026-09.pdf"

tables = camelot.read_pdf(PDF, pages="1-end", flavor="lattice")
for t in tables:
    print(t.page, t.shape, t.parsing_report["accuracy"])
tables[0].df.to_csv("camelot_page1.csv", index=False)

dfs = tabula.read_pdf(PDF, pages="all", lattice=True)
print(len(dfs), [df.shape for df in dfs])

Na nossa lista de preços de duas páginas, as duas encontraram uma tabela por página. O Camelot informou precisão de 100.0 e manteve o cabeçalho como primeira linha; o tabula o usou como nomes de coluna. As três bibliotecas acertaram a tabela com grade, então escolha pelo resto do trabalho: pdfplumber se você também precisa de texto livre, Camelot para ter uma pontuação por tabela, tabula-py para DataFrames com o mínimo de código se o Java estiver instalado.

PDFs digitalizados: OCR com Tesseract

Quando uma página não tem camada de texto, é preciso reconhecer os caracteres a partir da imagem. O Tesseract é o motor de OCR de código aberto mais comum, e o pytesseract é o wrapper dele para Python. O wrapper não inclui o motor: instale primeiro o próprio Tesseract (se depois ele não estiver no PATH, defina tesseract_cmd como mostrado abaixo) e então:

bash
pip install pytesseract pypdfium2

O script renderiza cada página a 300 DPI em tons de cinza, aplica OCR e imprime a confiança média por palavra, que mostra quais páginas precisam de revisão humana:

python
import sys

import pypdfium2 as pdfium
import pytesseract

# No Windows, aponte o pytesseract para o executável se ele não estiver no PATH:
# pytesseract.pytesseract.tesseract_cmd = r"C:\Program Files\Tesseract-OCR\tesseract.exe"


def ocr_pdf(path, lang="eng", dpi=300):
    pdf = pdfium.PdfDocument(path)
    pages = []
    for i, page in enumerate(pdf, start=1):
        image = page.render(scale=dpi / 72).to_pil().convert("L")  # tons de cinza ajudam
        text = pytesseract.image_to_string(image, lang=lang, config="--psm 6")
        pages.append(text)
        # Confiança por palavra: -1 significa "não é uma palavra"; caso contrário, 0-100.
        data = pytesseract.image_to_data(image, lang=lang, output_type=pytesseract.Output.DICT)
        confs = [float(c) for c in data["conf"] if float(c) >= 0]
        if confs:
            print(f"page {i}: mean confidence {sum(confs) / len(confs):.1f}")
    return pages


if __name__ == "__main__":
    try:
        for text in ocr_pdf(sys.argv[1] if len(sys.argv) > 1 else "scans/price-list-scan.pdf"):
            print(text)
    except pytesseract.TesseractNotFoundError:
        sys.exit("Tesseract is not installed or not on PATH")

O valor de 300 DPI vem do guia de melhoria de qualidade do Tesseract. --psm 6 trata a página como um único bloco uniforme de texto. O mesmo guia observa que tabelas exigem segmentação própria e que as bordas da digitalização podem ser lidas como caracteres.

O OCR confunde 0 e O, 1 e l, e a vírgula e o ponto de um preço; digitalizações apagadas ou tortas pioram o resultado. Para números que importam, confira se os itens somam o total e peça para uma pessoa revisar as páginas com baixa confiança. O OCR devolve texto, não a estrutura da tabela: reconstrua as colunas com uma expressão regular por linha ou teste o extra opcional ocr do Camelot.

Onde a extração de dados de PDF é usada

  • Listas de preços de fornecedores. PDFs mensais transformados em um histórico de preços que você compara ao longo do tempo, a base do monitoramento de preços.
  • Relatórios de mercado e do setor. Tabelas de relatórios públicos alimentando um modelo de pesquisa de mercado em vez de serem redigitadas.
  • Faturas e extratos. Totais, datas e números de referência levados para a contabilidade ou a conciliação, um trabalho comum nas equipes de finanças.
  • Documentos públicos e estatísticas. Muitos órgãos ainda publicam tabelas só em PDF; um script transforma anos delas em um único conjunto de dados, um primeiro passo típico antes da mineração de dados.
  • Pipelines de ETL. Ler PDFs é o "extract" de um fluxo maior; O que é ETL explica como as etapas de transformação e carga se encaixam em volta.

Baixando muitos PDFs de sites

Os PDFs costumam estar em um portal de fornecedores ou na página de publicações de um órgão regulador. Baixar centenas deles é um trabalho de web scraping: use o download oficial ou a API, se existir, leia o robots.txt e os termos de uso e espace as requisições. O downloader abaixo pula os arquivos que já tem, não tenta de novo em erros 4xx, respeita Retry-After no HTTP 429 e confere se a resposta é mesmo um PDF:

python
import os
import time
from pathlib import Path
from urllib.parse import urlparse

import requests

PROXY = os.environ.get("PROXY_URL", "http://user:pass@pr.proxynet.io:8000")
URLS = os.environ.get("PDF_URLS", "https://example.com/reports/price-list.pdf").split()
OUT = Path("pdfs")
OUT.mkdir(exist_ok=True)

session = requests.Session()
session.proxies = {"http": PROXY, "https": PROXY}
session.headers["User-Agent"] = "price-list-archiver/1.0 (contact: data@example.com)"


def download(url, retries=3):
    target = OUT / Path(urlparse(url).path).name
    if target.exists():  # já baixado em uma execução anterior
        return target
    for attempt in range(1, retries + 1):
        try:
            r = session.get(url, timeout=30)
            if r.status_code == 429:
                time.sleep(int(r.headers.get("Retry-After", 30)))
                continue
            if 400 <= r.status_code < 500:  # 403, 404...: tentar de novo não adianta
                print(f"{url} returned {r.status_code}, skipped")
                return None
            r.raise_for_status()
            if not r.content.startswith(b"%PDF"):  # uma página de erro HTML, não um PDF
                print(f"{url} did not return a PDF, skipped")
                return None
            target.write_bytes(r.content)
            return target
        except requests.RequestException as exc:
            print(f"attempt {attempt} failed: {exc}")
            time.sleep(2 ** attempt)
    return None


for url in URLS:
    print(url, "->", download(url))
    time.sleep(2)  # seja gentil com o servidor

Por meio de um proxy de teste local com user:pass, um PDF válido foi salvo, um .pdf falso e um 404 foram pulados, e uma segunda execução não baixou nada de novo. Um proxy ajuda quando uma fonte entrega documentos diferentes conforme o país (um Proxies residenciais com país de destino) ou quando o download longo de um arquivo histórico esbarra em limites por IP (um Proxies rotativos). Mesmo assim, desacelerar vem primeiro, como explica HTTP 429 Too Many Requests.

Problemas comuns e como resolver

  • Células mescladas voltam como None. O pdfplumber preenche só a primeira linha de uma célula mesclada; ffill() do pandas copia o valor para baixo (exemplo mais abaixo).
  • A tabela continua na página seguinte. Extraia página por página, descarte as linhas de cabeçalho repetidas (como o script acima faz) e concatene. No Excel, a opção MultiPageTables faz isso por você.
  • Os números chegam como texto. "1,007.50", "(250.00)" para negativos, "1.007,50" em um arquivo alemão. Normalize conforme o formato; nunca adivinhe o separador decimal a partir de um único valor.
  • Letras acentuadas aparecem quebradas. Normalmente o problema está no visualizador, não no texto: um console do Windows, ou o Excel abrindo UTF-8 sem BOM (veja erros de codificação Unicode no Python). Se o próprio extract_text() devolve símbolos estranhos, a fonte não tem mapeamento Unicode e o OCR é o caminho confiável.
  • As colunas se deslocam em tabelas sem bordas. Mude para a estratégia de texto, recorte a área da tabela ou passe posições de coluna explícitas com "vertical_strategy": "explicit" e "explicit_vertical_lines".
  • Zero linhas e nenhum erro. A página é uma digitalização, a tabela é uma imagem ou o arquivo está protegido por senha (pdfplumber.open(path, password="...") para arquivos que você tem direito de abrir).

Esta é a correção completa para células mescladas, testada em uma tabela em que "North" ocupa duas linhas:

python
import pandas as pd
import pdfplumber

with pdfplumber.open("merged.pdf") as pdf:
    rows = pdf.pages[0].extract_table()

df = pd.DataFrame(rows[1:], columns=rows[0])
df["Region"] = df["Region"].ffill()  # uma célula mesclada volta como None abaixo da primeira linha
df["Price"] = pd.to_numeric(df["Price"].str.replace(",", ""), errors="coerce")
print(df)

Qual método escolher?

NecessidadeRecomendação
Uma tabela de um arquivo, uma vezCopiar e colar, depois Texto para Colunas
Poucos PDFs de texto por mês, análise no ExcelExcel, Dados > Obter Dados > Do Arquivo > Do PDF
Texto e tabelas de uma pasta, todo mêsScript de pdfplumber gravando CSV
Tabelas com grade e pontuação de qualidade por tabelaCamelot, modo lattice
DataFrames com o mínimo de código, com Java disponíveltabula-py
Páginas digitalizadas ou fotosRenderizar com pypdfium2 a 300 DPI + Tesseract, com revisão humana dos números
Centenas de PDFs de um sitePrimeiro o download oficial ou a API; senão, um downloader com pausas, com proxy se a fonte for regional

Perguntas frequentes

Dá para extrair dados de PDF de graça?

Sim. pdfplumber, Camelot, tabula-py e Tesseract são de código aberto, e o conector de PDF faz parte do Excel para Microsoft 365 no Windows. As ferramentas pagas acrescentam principalmente editores de modelos e um OCR melhor para digitalizações ruins.

Como passar uma tabela de PDF para o Excel?

No Excel para Microsoft 365 no Windows, use Dados > Obter Dados > Do Arquivo > Do PDF, escolha a tabela no Navegador e clique em Carregar. Em Python, extraia as linhas com o pdfplumber e grave-as com to_excel() do pandas ou como um CSV que o Excel abre.

Por que a extração do meu PDF devolve texto vazio?

O mais provável é que a página não tenha camada de texto. Confira page.chars no pdfplumber ou tente Ctrl+F em um visualizador; se nada for encontrado, aplique OCR.

O pdfplumber é melhor que o PyPDF para tabelas?

Para tabelas, sim. O pypdf é bom para dividir, juntar e ler texto simples, mas não tem localizador de tabelas. O pdfplumber guarda a posição de cada caractere e reconstrói as células a partir de linhas ou alinhamento, que é o que a extração de tabelas exige.

Qual é a precisão do OCR em PDFs digitalizados?

Depende da digitalização. Páginas impressas limpas a 300 DPI costumam sair bem; digitalizações tortas, apagadas ou de baixa resolução, letra de mão e fontes pequenas, não. Confira as pontuações de confiança e verifique os totais.

Extrair dados de arquivos que você tem direito de usar (suas próprias faturas, relatórios públicos) normalmente não é problema, mas o conteúdo ainda pode ter direitos autorais e os dados pessoais nele continuam sujeitos a leis como a GDPR e a LGPD. Leia os termos da fonte antes de baixar em massa; nosso artigo sobre a legalidade do web scraping cobre o quadro geral. Isto não é aconselhamento jurídico.

Em resumo

Todo trabalho com PDF começa com uma verificação: existe camada de texto? Se existir, o conector de PDF do Excel resolve as tabelas ocasionais e um script de pdfplumber transforma uma pasta em um único CSV, com Camelot ou tabula-py para o trabalho só com tabelas. Se não existir, renderize a 300 DPI, rode o Tesseract e verifique os números. Guarde o arquivo de origem e a página em cada linha, e trate downloads de PDF em massa como qualquer outro trabalho de web scraping. Quando esses downloads vêm de fontes regionais ou com limite de requisições, nossos planos de proxy dão ao fluxo os endereços de que ele precisa.