PDF é um formato pensado para impressão e visualização, não para o armazenamento de dados. Justamente por isso é o formato do qual é mais difícil extrair informação de forma confiável entre todos os do nosso panorama do parsing de documentos. Em PDF chegam faturas, contratos, especificações, listas de preços, extratos bancários e documentos de órgãos públicos — e quase sempre é preciso convertê-los em dados estruturados.
A grande bifurcação: texto ou imagem
A primeira coisa que determina toda a estratégia é como o texto está armazenado dentro do arquivo. Em um PDF «digital» (gerado a partir do Word, de uma diagramação ou do navegador) o texto está guardado como caracteres selecionáveis — dá para extraí-lo diretamente. Em um PDF digitalizado, a página é uma imagem: sem reconhecimento óptico (OCR) ela não contém caracteres e a extração devolverá o vazio. Verificar é simples: se no visualizador o texto pode ser selecionado com o mouse, é digital; se não, é preciso OCR. Os dois casos exigem ferramentas completamente diferentes, então convém começar sempre por essa verificação.
Extração de texto de PDFs digitais
Python
Para extrair texto e, sobretudo, tabelas, a melhor opção é o pdfplumber. Ele entende as coordenadas dos caracteres e das linhas e, por isso, sabe reconstruir a estrutura tabular.
import pdfplumber
with pdfplumber.open("invoice.pdf") as pdf:
page = pdf.pages[0]
text = page.extract_text()
print(text)
for table in page.extract_tables():
for row in table:
print(row) # row é a lista de células da linhaQuando só o texto é necessário e a velocidade vem em primeiro lugar, recorre-se ao PyMuPDF (importado como fitz) — é rapidíssimo e respeita bem a disposição do texto.
import fitz # PyMuPDF
doc = fitz.open("contract.pdf")
for page in doc:
print(page.get_text())Para tarefas simples, como extrair o texto página a página ou trabalhar com os metadados, serve o pypdf (o sucessor do obsoleto PyPDF2).
from pypdf import PdfReader
reader = PdfReader("report.pdf")
for page in reader.pages:
print(page.extract_text())JavaScript / Node.js
No Node, o caminho rápido para obter todo o texto do documento é o pdf-parse.
const fs = require("fs");
const pdf = require("pdf-parse");
const buffer = fs.readFileSync("invoice.pdf");
pdf(buffer).then((data) => {
console.log(data.text); // todo o texto
console.log(data.numpages);
});Quando é necessário controle de baixo nível ou trabalhar no navegador, usa-se o pdf.js da Mozilla — ele dá acesso aos fragmentos de texto individuais com suas coordenadas.
Java
Nos projetos corporativos, o Apache PDFBox é amplamente usado. Atenção à mudança de API entre versões: no PDFBox 3.x o documento é aberto com Loader.loadPDF(...), enquanto no 2.x era PDDocument.load(...).
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.text.PDFTextStripper;
import java.io.File;
try (PDDocument doc = Loader.loadPDF(new File("report.pdf"))) {
String text = new PDFTextStripper().getText(doc);
System.out.println(text);
}As tabelas: uma dor à parte
Em PDF não existe o conceito de «tabela»: o que uma pessoa vê como tabela é, na verdade, um conjunto de blocos de texto e linhas com coordenadas. Por isso a extração de tabelas é sempre uma heurística. O pdfplumber as reconstrói a partir das linhas da grade e funciona bem quando a tabela está traçada. Para tabelas complexas sem linhas, em Python empregam-se o Camelot (método baseado na disposição) ou o tabula-py (um invólucro do motor Java Tabula). Se as tabelas são muitas e homogêneas, o habitual é escolher uma ferramenta e calibrá-la para aquele layout específico.
Muitas vezes o mais prático é despejar o resultado diretamente em uma tabela — a partir daí, o processamento continua no Excel ou em CSV, como com qualquer fonte tabular.
Digitalizações e OCR
Se o texto não se deixa selecionar, antes de extraí-lo é preciso reconhecer a página. A pilha clássica em Python: renderizar as páginas como imagens (com o próprio PyMuPDF) e reconhecê-las com o motor Tesseract por meio do invólucro pytesseract.
import fitz
import pytesseract
from PIL import Image
import io
doc = fitz.open("scan.pdf")
for page in doc:
pix = page.get_pixmap(dpi=300) # renderizamos a página como imagem
img = Image.open(io.BytesIO(pix.tobytes()))
text = pytesseract.image_to_string(img, lang="por")
print(text)A qualidade do OCR depende muito da resolução (use 300 DPI no mínimo), da limpeza da digitalização e do idioma. Para documentos em português, indique sempre o pacote de idioma por. Não espere do OCR uma precisão de cem por cento: o resultado quase sempre exige pós-processamento e revisão.
Quando é melhor não parsear o PDF diretamente
Às vezes o documento também existe em outro formato: o mesmo extrato está disponível como exportação CSV ou uma API entrega os dados em JSON. Isso é quase sempre mais confiável do que extrair uma tabela da versão para impressão, então verifique a fonte antes de partir para cima do PDF.
PDF é um formato em que a solução «universal» não existe: uma fatura, um contrato e uma digitalização exigem ferramentas diferentes. Se você recebe um fluxo regular de documentos homogêneos — faturas, romaneios, especificações —, configuramos a extração de dados dos seus PDFs para aquele layout específico, tabelas e OCR de digitalizações incluídos, com a carga do resultado no sistema de que você precisa.