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)
One comment