Como extrair dados de arquivo PDF

O PDF é um formato de arquivo muito utilizado para representar documentos de maneira independente do aplicativo, do hardware e do sistema operacional usados para criá-los – veja no link algumas formas de criar arquivos PDF. No entanto, quem pretende utilizar parte da informação contida no documento acaba tendo de suar um pouco, já que o texto copiado fica com a formatação muito “bagunçada” quando é colado.

Existem programas que extraem o texto puro sem formatação (ASCII), que poderá ser ser utilizado posteriormente por outros programas. O ps2ascii usa o gs1 para extrair o texto ASCII de arquivos PostScriptTM (PS) ou PDF e geralmente já vem instalado junto com o Debian. Veja um exemplo de seu uso:

ps2ascii file_in.pdf file_out.txt

Existem outros programas, cada um com um resultado diferente. Teste diferentes opções até encontrar o que gere um resultado melhor aproveitável para você. Outro opção é o PDFtoText – instalado usando “sudo apt-get install xpdf”:

pdftotext file.pdf file.txt # extrai texto puro para arquivo ASCII

Caso necessite manipular o arquivo PDF antes, existe o PDF Toolkit, ou PDFtk – instalado através do comando “sudo apt-get install pdftk”. Veja alguns exemplos de sua utilização:

pdftk largepdfile.pdf burst # cria um série de PDFs individuais: pg_01.pdf, pg_02.pdf, etc
pdftk 1.pdf 2.pdf 3.pdf cat output 123.pdf # junta arquivos PDFs em um só
pdftk in.pdf cat 1east 2-end output out.pdf # gira o documento 90º no sentido horário
pdftk secured.pdf input_pw foopass output unsecured.pdf # salva novo arquivo sem senha a partir de arquivo encriptado
pdftk 1.pdf output 1.128.pdf owner_pw foopass # encripta usando senha 128-bits

Com o texto puro, chega a hora de tratar a informação. Nesse ponto, cada caso é um caso. Veja como ficou uma tabela, feita no word/excel e cujo arquivo foi convertido para PDF usando o “pdftotext” (usando o ps2ascii e o mesmo arquivo PDF, o resultado ficou bem diferente, com o mês, os pontinhos e o valor todos na mesma linha, inclusive da tabela que estava ao lado):

Título

JANEIRO
FEVEREIRO
MARÇO
ABRIL
......
......
......
......
56.409
59.038
55.130
41.803

Note que a primeira linha de texto corresponde à primeira linha de números, e assim por diante. Existiam várias tabelas dentro do mesmo arquivo dispostas da mesma forma. Para fazer a correspondência, criei uma variável para receber o número da primeira linha de valores numéricos. Dentro de um laço, para cada mês, deve-se gravar os valores em “colunas” (ou seja, os primeiros valores das tabelas todos na mesma linha e separados por vírgula), apagando os pontos (os valores estavam com pontos para separar casa do milhar, o que não é utilizado em programação).

Para complicar, também quis inverter a posição de duas colunas. Todo o resultado foi gravado com um cabeçalho em um arquivo CSV. Veja como ficou o script:

# Número da linha de início dos valores
na=33
nb=147
nc=90
nd=204

# Gravar valores para cada mês em uma mesma linha e separados por vírgula, apagando os pontos
for (( i=1; i<=12; i++ )); do sed -n "`echo $na`p;`echo $nb`p;`echo $nc`p;`echo $nd`p;" file.txt | sed ':a;N;$!ba;s/\n/,/g' | tr -d '.' >> values_temp
	# avançar para a linha de baixo
	na=$((na+1))
	nb=$((nb+1))
	nc=$((nc+1))
	nd=$((nd+1))
done

echo "LocalA,LocalB,LocalC,LocalD" > values.csv
# trocar coluna 2 com 3 de posição
cat values_temp | awk -F "," ' { t = $2; $2 = $3; $3 = t; print; } ' | tr ' ' ',' > values_temp2
cat values_temp2 >> values.csv
rm values_temp*

O comando sed é executado primeiramente para selecionar as linhas cuja numeração é informada pela variável. Sua saída é encaminhada para outro sed, que substitui a quebra de linha (\n) por uma vírgula. Por último, são apagados os pontos do arquivo.

Para trocar duas colunas de posição, é utilizado o comando AWK, sendo definida a vírgula como parâmetro que separa os campos – a variável t recebe provisoriamente a coluna dois, já que ela recebe a coluna 3, e depois a coluna 3 recebe a coluna que está na variável provisória. Por fim, os espaços em branco criados entre as colunas são substituídos por vírgulas.

Para baixar e extrair dados de arquivo de planilha (XLS), clique no link para ver como fazer via shell script.

Extrair informações de Nota Fiscal em PDF

Boa parte dos arquivos PDF de notas fiscais na verdade guardam informações como imagem. Assim, as técnicas acima não funcionam e deve-se usar um método de reconhecimento de caracteres em imagens. O tesseract é um software de reconhecimento ótico de caracteres de código aberto, originalmente desenvolvido pela Hewlett-Packard e foi por um tempo mantido pelo Google. Existe a opção de especificar o idioma português para um melhor resultado (encontrar caracteres latinos, por exemplo). O comando para realizar essa ação é:

pytesseract.image_to_string('nome_do_arquivo.ext', lang='por')

Para extrair informações de uma nota fiscal nessa condição, você pode criar um ambiente virtual em python usando conda para instalar os pacotes necessários, conforme os comandos a seguir:

conda create -n pdf
conda activate pdf
conda install -c anaconda pillow
conda install -c conda-forge pdf2image
conda install -c conda-forge pytesseract tesseract

No código a seguir, o usuário deve informar o caminho onde estão os arquivos PDF das notas fiscais alterando o conteúdo entre aspas simples da variável “diretorio_pdf”. O script carrega as bibliotecas instaladas, lista os arquivos, e, para cada arquivo, converte o PDF para uma imagem, extrai todo o texto e extrai as informações. As funções definidas como busca_padrao e busca_padrao2 recebem o texto extraído e o padrão a ser encontrado, sendo que a diferença entre as duas é que a primeira extrai o texto da linha seguinte ao padrão buscado, enquanto que a outra extrai a mesma linha onde o padrão foi encontrado.

Os padrões pesquisados no texto extraído referem-se a: Número da Nota, Data e Hora de Emissão, Nome/Razão social do tomador de serviços, Valor total e Código do Serviço. Esses valores são agrupados em uma lista, compondo uma linha a ser adicionada em uma tabela a ser salva em um arquivo CSV, onde essas informações são dispostas em colunas, e cada linha refere-se a uma nota diferente.

#!/usr/bin/env python3.11.4
# -*- Coding: UTF-8 -*-

import re
from glob import glob
from pdf2image import convert_from_path
import pytesseract
import pandas as pd

# Caminho para o diretório contendo os arquivos PDF
diretorio_pdf = 'CAMINHO_DOS_PDFs'

def busca_padrao(linhas, padrao):
    linha_desejada = 'NA'
    # Iterando pelas linhas para encontrar o padrão
    for i, linha in enumerate(linhas):
        if padrao in linha:
            # Verifica se há uma próxima linha e a guarda na variável
            if i + 1 < len(linhas):
                linha_desejada = linhas[i + 1]
                break
    return linha_desejada

def busca_padrao2(linhas, padrao):
    linha_desejada = 'NA'
    # Iterando pelas linhas para encontrar o padrão
    for i, linha in enumerate(linhas):
        if padrao in linha:
            # Guarda na variável
            linha_desejada = linhas[i]
            break
    return linha_desejada

# Iterar sobre os arquivos no diretório
lst_arquivos = sorted([f for f in glob(diretorio_pdf + "*.pdf", recursive=True)])
# Iniciar dataframe
df = pd.DataFrame(columns=['nota', 'data', 'nome', 'valor', 'codigo'])
for pdf_path in lst_arquivos:
    print(pdf_path)
    # Convertendo páginas do PDF em imagem
    images = convert_from_path(pdf_path)
    
    # Inicializando uma variável para armazenar o texto extraído
    texto_extraido = ''

    # Processando cada imagem e executando OCR
    for page_num, img in enumerate(images, start=1):
        # Convertendo a imagem para texto usando pytesseract
        texto_pagina = pytesseract.image_to_string(img, lang='por')
        
        # Acrescentando o texto extraído à variável
        texto_extraido += f'\n\nPágina {page_num}:\n{texto_pagina}'

    #print(texto_extraido)
    # Dividindo o texto em linhas
    linhas = texto_extraido.split('\n')

    # Extrair informação buscando por padrão
    linha = busca_padrao(linhas, 'Número da Nota')
    nota = linha

    # Extrair informação buscando por padrão
    linha = busca_padrao(linhas, 'Data e Hora de Emissão')
    padrao_data = r'\d{2}/\d{2}/\d{4}'
    match = re.search(padrao_data, linha)
    data = match.group()

    # Extrair informação buscando por padrão
    linha = busca_padrao(linhas, 'TOMADOR DE SERVIÇOS')
    indice = linha.find(':')
    nome = linha[indice + 2:]
    
    # Extrair informação buscando por padrão
    linha = busca_padrao2(linhas, 'VALOR TOTAL')
    indice = linha.find('$')
    valor = linha[indice + 2:]

    # Extrair informação buscando por padrão
    linha = busca_padrao(linhas, 'Código do Serviço')
    codigo = linha[:6]

    # Montar linha e adicionar na dataframe
    nova_linha = [nota, data, nome, valor, codigo]
    df.loc[len(df)] = nova_linha
    #exit()

# Salvar dataframe em CSV
df.to_csv('dados.csv', index=False)
Compartilhe :)

One comment

Leave a Reply

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

Esse site utiliza o Akismet para reduzir spam. Aprenda como seus dados de comentários são processados.