Quer fazer parsing de PDF?
(eliot-jones.com)- O parsing de PDF deveria funcionar com base em uma ordem e estrutura bem definidas, mas os arquivos reais frequentemente não seguem essa especificação
- Vários erros e inconsistências ocorrem na busca de ponteiros de cross-reference (xref) e offsets
- Na prática, muitos problemas surgem por causa de dados desnecessários antes do cabeçalho do PDF ou por posições incorretas de ponteiros e offsets
- Também há muitos casos em que a própria tabela xref do PDF é ambígua ou mal formatada
- Por isso, os principais visualizadores implementam lógica adicional para suportar até arquivos PDF fora do padrão
Abordagem ideal para parsing de PDF
- Em teoria, o parsing de PDF segue etapas fixas
- Encontrar o comentário de cabeçalho de versão no início do arquivo
- Encontrar o ponteiro de cross-reference (xref)
- Coletar todos os offsets dos objetos
- Encontrar o dicionário trailer para acessar a estrutura completa do catálogo
Introdução aos objetos PDF
- Objetos PDF são unidades que encapsulam e armazenam vários elementos do PDF, como números, strings e dicionários
- Cada objeto existe entre os marcadores
objeendobj - Os objetos se conectam entre si por meio de referências indiretas (
indirect reference, por exemplo:16 0 R) - A forma de dividir os objetos no arquivo é livre, mas alguns tipos de objeto obrigatoriamente precisam ser referências indiretas
Encontrando offsets de cross-reference
- Estruturalmente, o PDF tem uma tabela de cross-reference (xref), que funciona como um índice das posições dos objetos
- No fim do arquivo, a expressão
startxrefindica uma posição específica em bytes como ponteiro - Esse ponteiro indica a posição da xref, mas há diferenças entre a especificação e os arquivos reais. Por exemplo, o marcador
%EOFdeveria estar na última linha, mas em PDFs reais ele pode estar em qualquer lugar dentro dos últimos 1.024 bytes - Nos arquivos reais, aparecem várias variações, como erros de formato no ponteiro (
startrefetc.) e ausência de quebra de linha
Encontrando offsets de objetos
- A tabela xref segue a ordem
xref, número inicial do objeto e quantidade de objetos, e o offset/número de geração/estado de cada objeto (nouf) é registrado em uma linha - Pode haver várias tabelas xref, ou elas podem estar ligadas entre si por meio da entrada
/Prev
Localizando o dicionário trailer
- Acima do marcador
startxref, existe o dicionário trailer, que contém os metadados essenciais para encontrar o objeto raiz - A partir do objeto raiz, é possível iniciar a interpretação de toda a estrutura
Ambiente real: problemas inesperados
-
Há muitos arquivos que não seguem a especificação do PDF, então parsers comuns têm dificuldade para processá-los
-
Casos em que a busca pelo ponteiro de cross-reference costuma falhar
- O ponteiro não está no fim do arquivo nem nos últimos 1.024 bytes
- Erros de digitação (
startrefetc.) - Formatos excepcionais
-
Em uma análise de 3.977 amostras reais de PDF, cerca de 0,5% tinham erro na declaração da xref
O conteúdo do PDF começa em um offset diferente de zero
- Se houver dados inúteis (
junk) antes do cabeçalho, todos os offsets em bytes são deslocados, e a posição destartxreffica incorreta - É preciso recalcular os offsets com base na posição do cabeçalho e verificar as duas posições
- Isso representa cerca de 50% de todos os erros
O ponteiro xref aponta para o meio da tabela xref
- O offset especificado pode acabar levando para o meio do conteúdo da tabela xref
- Isso foi encontrado em cerca de 5 dos 3.977 exemplos
O ponteiro está perto da xref
- Muitas vezes, o ponteiro não está exato, mas só deslocado pela diferença de espaços em branco ou quebras de linha logo antes ou depois da xref
O ponteiro está certo, mas os offsets da xref estão errados
- Os próprios offsets registrados na tabela xref podem estar errados
- Alguns objetos podem estar corretos, enquanto outros têm erro de offset
O primeiro ponteiro está normal, mas o offset anterior (/Prev) está estranho
- Há muitos casos em que, ao modificar um PDF, o ponteiro
/Prevrecebe um valor incorreto (por exemplo,0)
O formato da tabela xref é anormal
- Aparecem casos variados, como
xrefcolado com números sem quebra de linha, mais entradas do que objetos declarados, ou dados lixo no meio da tabela - Muitos desses casos foram relatados como issues em projetos como o PdfPig
Conclusão
- Segundo a especificação, o parsing de PDF deveria seguir uma sequência padronizada, mas muitos arquivos reais não são assim, o que causa diversos problemas de parsing
- Visualizadores de PDF usados na prática já incluem por padrão recursos para ampliar o suporte a PDFs fora da especificação
- Este resumo tratou apenas de uma pequena parte do parsing coberta pela especificação do PDF (22 páginas de um total de 1300)
5 comentários
Este resumo trata apenas da análise de uma pequena parte da especificação do PDF (22 páginas de um total de 1300) <-... 1300 páginas é algo realmente assustador...
Uau..
Para ser bem sincero, PDF é um formato amigável para leitura humana, feito para preservar ao máximo a formatação criada por pessoas, e péssimo para lidar com máquinas.
Concordo. Na verdade, nem sei se é bom de ler... é pesado demais e inconveniente.
Comentários do Hacker News
A resposta é clara
Na prática, meu nome é Geoff, e metade dos parsers de currículo reconhece meu nome separadamente como "Geo" e "ff"
Isso acontece por causa da forma como o texto entra no PDF, e é um problema que continua ocorrendo em vários aplicativos de origem
Fazer parsing de PDF e fazer parsing do conteúdo de um PDF são coisas completamente diferentes
Fazer parsing do arquivo PDF já é uma dor de cabeça, mas como o próprio PDF é baseado em "desenhar alguma coisa em uma posição especificada", e não em texto bem definido dentro de caixas delimitadoras, para extrair palavras você acaba tendo que adivinhar quais caracteres pertencem juntos
Se quiser ajudar parsers de currículo, talvez valha a pena olhar para a accessibility tree
Nem todo renderizador de PDF exporta PDF acessível, mas um PDF acessível pode ao menos ajudar a ler corretamente coisas como nomes
O problema com "ff" provavelmente é um caso em que o analisador de currículos não consegue lidar com caracteres não ASCII, como a ligadura ff
Dá para configurar o renderizador de PDF para não gerar ligaduras, mas isso pode deixar o texto feio
Parece que estão depositando expectativa demais na palavra "deveria"
Quando o uso de PDF é, na prática, bem hostil, as pessoas não parecem pensar tanto assim
O próprio motivo de enviar um currículo em PDF é impedir que intermediários mexam nele, e também existe "edição" por vários motivos, como ocultar algo desenhando uma caixa em cima da imagem, ou gerar uma tabela em PDF em vez de CSV para dificultar a análise
Na verdade esse método funciona bem em alguns casos, e alguns apps usam essa abordagem
Só que continua existindo o problema de as duas representações (corpo/metadados) não necessariamente coincidirem
Fica a dúvida sobre como lidar com digitalizações de manuscritos ou outros documentos escaneados se o scanner e um computador doméstico comum não tiverem suporte perfeito a OCR
Provavelmente o problema é que ff está sendo renderizado como uma ligadura
Sou fundador da Tensorlake
Criamos uma API de parsing de documentos para desenvolvedores
Esse é o motivo de abordagens de Computer Vision funcionarem tão bem em parsing de PDF no mundo real
Depender só dos metadados do arquivo não escala para a variedade de PDFs de origem
Então a abordagem é converter o PDF em imagem, aplicar primeiro um modelo de reconhecimento de layout, depois rodar modelos especializados para reconhecimento de texto e tabelas, entre outros, e então juntar as partes, obtendo resultados utilizáveis até em áreas onde a precisão é essencial
À primeira vista esse jeito parece ridículo, mas na verdade parece ser a solução mais realista
PDF é, em essência, um formato criado para expressar um layout legível por humanos, não algo projetado para ser lido por computadores; o foco é exibição bonita
Por isso, imitar a forma como humanos leem parece uma abordagem lógica
Ainda assim, é uma pena que em mais de 30 anos o PDF não tenha conseguido adicionar legibilidade para máquinas
Fico me perguntando que incentivo faltou para tornar isso possível
Se alguém tiver alguma percepção sobre isso, eu gostaria de ouvir
Tem algo meio engraçado nisso
Tirar sarro de quem imprime um PDF, escaneia e manda por e-mail parece natural, mas em parsing de PDF estamos basicamente fazendo a mesma coisa
É frustrante que essa abordagem seja necessária
Ninguém faz parsing de HTML desse jeito
Sou cofundador da Nutrient.io e trabalho com PDF há mais de 10 anos
Assim como navegadores web, visualizadores de PDF precisam aceitar uma variedade enorme de PDFs
Como o PDF é muito antigo, quem gera os arquivos muitas vezes faz alterações arbitrárias desde que fique bom no visualizador que usa
Por isso nossa empresa criou um SDK de processamento de documentos com IA (REST API, você envia um PDF e recebe dados estruturados em JSON)
Além dos métodos visuais, nossa experiência com pré-processamento e pós-processamento estrutural permite entregar resultados melhores tanto em desempenho quanto em custo em comparação com uma abordagem puramente baseada em visão
Se você não quiser se preocupar em lidar diretamente com PDFs e preferir focar no seu trabalho principal, isso pode ajudar
https://www.nutrient.io/sdk/ai-document-processing
Já que há alguém aqui especialista na estrutura interna de PDFs, tenho uma pergunta
Gostaria de entender por que o mupdf-gl é muito mais rápido que todos os outros programas (no Linux desktop padrão)
A velocidade de busca em PDFs grandes é visivelmente superior, e sempre me perguntei por que outros visualizadores não conseguem ser tão rápidos
Se alguém tiver alguma percepção a respeito, gostaria de ouvir
No fim das contas, vocês terceirizaram o trabalho de parsing para o software usado para renderizar o PDF como imagem
Há muito tempo acho que precisamos sair da comunicação documental centrada em layout
Ou seja, o próprio layout profissionalmente diagramado me parece, na verdade, algo mais próximo de um costume antigo, com pouca relação com a compreensão real do conteúdo
Por exemplo, documentos enviados a órgãos reguladores costumam ser enormes, e seguir as regras de layout faz você passar muito tempo trabalhando no Microsoft Word
Para garantir esse layout, enviam tudo em DOCX ou PDF, mas esses formatos são muito inadequados para programas extraírem ou processarem o conteúdo automaticamente
Um LLM até consegue ler esses arquivos, mas o custo computacional é muito maior do que em arquivos simples e amigáveis para máquinas, como texto, markdown, XML ou JSON
Como alternativa, penso na possibilidade de padronizar formatos simples realmente "machine-first" e "content-first", baseados em JSON, XML ou HTML
Eles teriam apenas estrutura mínima e informações de incorporação de imagens, e quando humanos precisassem ler, um aplicativo visualizador reorganizaria tudo de forma agradável
O processamento por máquinas seria muito mais fácil
Mesmo já existindo formatos parecidos, como HTML/navegadores e EPUB, acho que chegou a hora de substituir o jeito antigo
Espero que a revolução dos LLMs nos empurre nessa direção, e que no futuro o parsing caro de PDF fique apenas como parte de pipelines legados
Nunca cheguei a criar um parser de DOCX, mas como DOCX é baseado em XML e nem tudo vira coordenadas absolutas a menos que você especifique explicitamente o layout, se JPEG vale 0, PDF vale 15 e markdown vale 100, minha impressão é que DOCX deve estar mais ou menos em 80 em facilidade
Achei um ótimo resumo, e houve um ponto adicional que me chamou atenção
Cadeia de incremental save: o primeiro offset de startxref até pode estar certo, mas quando o Acrobat vai anexando repetidamente links /Prev a cada nova modificação, muitas vezes eles apontam alguns bytes antes do próximo xref
A maioria dos visualizadores (PDF.js, MuPDF, até Adobe Reader) simplesmente sai procurando por tokens obj no arquivo inteiro e reconstrói uma nova tabela, enquanto parsers mais fiéis à especificação explodem
Se você quiser lidar no mundo real com documentos que foram modificados repetidamente por vários aplicativos, esse caminho de recuperação é indispensável
Muitas vezes a referência anterior, ou algum item da cadeia, aponta para um offset fora do arquivo, offset 0 ou outro valor inválido
O que me levou a escrever esse texto foi uma reformulação da lógica inicial de parsing no meu projeto PdfPig
No começo eu tinha portado o código do Java PDFBox, mas queria deixá-lo mais rápido e simples
A nova lógica escaneia o arquivo inteiro se perder qualquer xref table/stream, e no caminho de recuperação confia apenas naquele offset
Mas ela certamente ficou mais lenta do que antes, e estou em dúvida se a mudança realmente vale a pena
Estou explorando vários casos esquisitos com um conjunto de testes de 10 mil arquivos
https://github.com/UglyToad/PdfPig/pull/1102
Parece que seria fácil se houvesse suposições que funcionassem e um parser adequado de objetos PDF, mas acho que a realidade está longe disso
Essa situação é um inferno de PDF
PDF não é uma especificação, é um acordo social, um nível de "vibe"
Quanto mais você se debate, mais afunda, e agora parece que todos nós vivemos num atoleiro afastado da visão de Deus
Essa fala me fez rir
Sobre a pergunta "Você quer fazer parsing de PDF?", posso afirmar categoricamente que não
O motivo está muito bem explicado no texto original
Eu queria que meu banco fornecesse esses materiais em um formato mais fácil de ler, mas até lá não tem jeito
Já cometi esse erro antes e não pretendo repetir
Como alguém que já escreveu um parser de PDF, acho PDF um formato realmente estranho
Acho que esse lado esquisito vem da origem do formato como uma mistura de binário e texto
Também suponho que o problema dos offsets de xref levemente imprecisos venha de bugs no tratamento da conversão de quebras de linha LF/CR
Uma coisa que o texto não mencionou é que PDFs mais novos (v1.5+) muitas vezes vêm com "xref stream" em vez de uma xref table em texto simples
No v1.6 ou superior, os próprios objetos também podem ficar dentro de object streams
Parece que está tudo bem até você descobrir que o objeto que quer está dentro de um stream, que esse stream usa uma variante da compressão PNG, ou que o offset está dentro de um xref stream comprimido com flate
Além disso, há várias versões do documento misturadas, então também é complicado determinar onde começa e termina a versão mais recente
É fácil conseguir a documentação do PDF 1.7, mas até apenas 2 anos atrás a especificação do PDF 2.0 ainda estava atrás de paywall
PDF é um formato que não foi pensado para streaming
Por causa do trailer dictionary no final, fica difícil fazer parsing antes de carregar o arquivo inteiro
Ainda assim, também existem PDFs "streamable", então se as informações necessárias estiverem no começo, dá para renderizar a primeira página imediatamente, mesmo que o resto não
Faz algum tempo que estou mais distante da área de PDF, então vale considerar isso
Mesmo com um rodapé no fim, se o site suportar Range Request e usar corretamente o cabeçalho Content-Length, PDF pode ser transmitido em streaming
Um leitor com streaming pode fazer uma requisição HEAD, pedir as últimas centenas de bytes do arquivo para obter os ponteiros e a tabela, e depois continuar baixando o resto
Isso não serve para PDFs gerados em tempo real, mas para um servidor web razoavelmente antigo, 1 ou 2 RTTs extras já bastam
Infelizmente é raro ver casos em que alguém se preocupa com parsers baseados em Range por arquivo, mas tecnicamente eu não diria que é impossível
Isso mesmo, existe um formato chamado Linearized PDF, projetado para mostrar rapidamente a primeira página sem precisar baixar o arquivo inteiro
No resumo, eu tinha deixado isso de fora porque exigiria muita explicação adicional
Um dos primeiros projetos que tentei quando estava aprendendo Python foi um parser de PDF
Eu queria automatizar a extração de mapas para uma campanha de DnD, mas fracassei no fim das contas haha
Já escrevi um leitor de TIFF
TIFF também tem fama de ser fácil de escrever e difícil de ler
PDF me parece entrar exatamente na mesma categoria