Extrair dados (e conhecimento) de ficheiros PDF
Este protótipo demonstra como recolher dados de ficheiros PDF e como construir modelos analíticos sobre esses dados, produzindo conhecimento e automatizando tarefas repetitivas. Vamos recorrer a um script R integrado com o Power Query para conceber uma solução de recolha, transformação e análise de dados.
PDF nativos vs. PDF digitalizados
Antes de começarmos, é importante distinguir estes dois tipos de PDF.
Os PDF nativos são aqueles produzidos por outras aplicações, como o Word ou o Excel, a partir de versões eletrónicas de um documento. No fundo, é como se já “nascessem” no formato digital.
Os PDF digitalizados são convertidos nesse formato a partir de papel, a partir de um scanner. Neste tipo de PDF não é possível pesquisar texto porque, na verdade, estes documentos não são mais do que coleções de imagens.
Extrair dados de PDF nativos é muito mais fácil do que extrair dados de PDF digitalizados.
Extrair dados do extrato bancário
No primeiro exemplo vamos extrair dados de um extrato bancário. Este é um tipo de PDF nativo que vive em praticamente todas as empresas e há muito interesse em analisar os dados para efeitos de reconciliações bancárias, planeamento e orçamentação de tesouraria e controlo interno.
O ficheiro PDF que usaremos tem o seguinte aspeto:
Para extrair a tabela com o extrato bancário, vamos usar o package “tabulizer” do R e correr o seguinte código:
library(tabulizer) library(dplyr) setwd("D:/OneDrive/OneDrive - Portal Gestão/Artigos/PDF") # Ficheiro ficheiro <- 'Bank Account Statement.pdf' # Extrair a tabela
output <- extract_tables(ficheiro, output="data.frame", encoding="UTF-8")
head(output)
dataset <- as.data.frame(output)
Assim que importado para o Power Query, teríamos o código acima acrescido das transformações aos dados. Estas transformações também poderiam ter sido feitas no R, mas é muito mais prático fazê-las com o editor do Power Query:
let
Origem = R.Execute("library(tabulizer)#(lf)library(dplyr)#(lf)#(lf)setwd(""D:/OneDrive/OneDrive - Portal Gestão/Artigos/PDF"")#(lf)#(lf)# Ficheiro#(lf)#(lf)ficheiro <- 'Bank Account Statement.pdf'#(lf)#(lf)# Extract the table#(lf)output <- extract_tables(ficheiro, output = ""data.frame"", encoding = ""UTF-8"")#(lf)head(output)#(lf)#(lf)dataset <- as.data.frame(output)"),
dataset1 = Origem{[Name="dataset"]}[Value],
#"Cabeçalhos Promovidos" = Table.PromoteHeaders(dataset1, [PromoteAllScalars=true]),
#"Tipo Alterado" = Table.TransformColumnTypes(#"Cabeçalhos Promovidos",{{"Date Details", type text}, {"Withdrawals", type text}, {"Deposits", type text}, {"Balance", type text}}),
#"Dividir Coluna por Posições" = Table.SplitColumn(#"Tipo Alterado", "Date Details", Splitter.SplitTextByPositions({0, 6}), {"Date Details.1", "Date Details.2"}),
#"Tipo Alterado1" = Table.TransformColumnTypes(#"Dividir Coluna por Posições",{{"Date Details.1", type date}, {"Date Details.2", type text}}),
#"Colunas com Nome Mudado" = Table.RenameColumns(#"Tipo Alterado1",{{"Date Details.1", "Date"}, {"Date Details.2", "Details"}}),
#"Tipo Alterado com Região" = Table.TransformColumnTypes(#"Colunas com Nome Mudado", {{"Withdrawals", type number}, {"Deposits", type number}}, "en-US"),
#"Valor Substituído" = Table.ReplaceValue(#"Tipo Alterado com Região",",","",Replacer.ReplaceText,{"Balance"}),
#"Valor Substituído1" = Table.ReplaceValue(#"Valor Substituído","7.567.87","7567.87",Replacer.ReplaceValue,{"Balance"}),
#"Tipo Alterado com Região1" = Table.TransformColumnTypes(#"Valor Substituído1", {{"Balance", type number}}, "en-US")
in
#"Tipo Alterado com Região1"
O resultado, com os dados importados e transformados, seria este:
Este resultado poderia ser obtido usando apenas o editor do Power Query, a partir da função “Importar dados de tabelas PDF”. O algoritmo incorporado no Power Query faz um trabalho quase perfeito na importação de tabelas de PDF nativos.
Nesta solução, o código seria:
let let Origem = Pdf.Tables(File.Contents("D:\OneDrive\OneDrive - Portal Gestão\Artigos\PDF\Bank Account Statement.pdf")), Table002 = Origem{[Id="Table002"]}[Data], #"Cabeçalhos Promovidos" = Table.PromoteHeaders(Table002, [PromoteAllScalars=true]), #"Tipo Alterado" = Table.TransformColumnTypes(#"Cabeçalhos Promovidos",{{"Date", type date}, {"Details", type text}, {"Withdrawals", type text}, {"Deposits", type text}, {"Balance", type text}}), #"Valor Substituído" = Table.ReplaceValue(#"Tipo Alterado","7.567.87","7,567.87",Replacer.ReplaceText,{"Balance"}), #"Tipo Alterado com Região" = Table.TransformColumnTypes(#"Valor Substituído", {{"Withdrawals", type number}, {"Deposits", type number}, {"Balance", type number}}, "en-US") in #"Tipo Alterado com Região"
Agregar transferências bancárias guardadas num diretório
O segundo exemplo é mais ambicioso. Vamos guardar documentos PDF com comprovativos de transferências de um banco (BPI) numa pasta comum. Interessa-nos agregar os dados de todas as transferências bancárias deste banco numa única tabela, para que seja possível pesquisar, reconciliar e analisar todas as transferências desta empresa.
O primeiro requisito é, portanto, que todos os documentos estejam numa pasta comum (independentemente de esta poder conter subpastas). Seja, no nosso caso, a pasta “Transferencias”, como se demonstra abaixo:
Outro requisito é que todos os documentos sigam uma estrutura comum. Não é que sejam iguais, em rigor, pois podem conter mais ou menos linhas, consoante os campos utilizados em cada transferência, mas que sigam o mesmo tipo de formato:
Esta empresa realizou milhares de transferências no último ano e é muito prático guardar todos os comprovativos para que sejam agregados no Power Query. As transformações requerem uma função personalizada e alguns ajustes, sendo o código final o seguinte:
let Origem = Folder.Files("D:\OneDrive\OneDrive - Portal Gestão\Artigos\PDF\Transferencias"), #"Outras Colunas Removidas" = Table.SelectColumns(Origem,{"Content"}), #"Ficheiros Ocultos Filtrados1" = Table.SelectRows(#"Outras Colunas Removidas", each [Attributes]?[Hidden]? <> true), #"Invocar Função Personalizada1" = Table.AddColumn(#"Ficheiros Ocultos Filtrados1", "Transformar Ficheiro", each #"Transformar Ficheiro"([Content])), #"Outras Colunas Removidas1" = Table.SelectColumns(#"Invocar Função Personalizada1", {"Transformar Ficheiro"}), #"Coluna de Tabela Expandida1" = Table.ExpandTableColumn(#"Outras Colunas Removidas1", "Transformar Ficheiro", Table.ColumnNames(#"Transformar Ficheiro"(#"Ficheiro de Exemplo"))), #"Tipo Alterado" = Table.TransformColumnTypes(#"Coluna de Tabela Expandida1",{{"Column1", type text}, {"Column2", type text}}), #"Cabeçalhos Promovidos" = Table.PromoteHeaders(#"Tipo Alterado", [PromoteAllScalars=true]), #"Tipo Alterado1" = Table.TransformColumnTypes(#"Cabeçalhos Promovidos",{{"Nº Operação", type text}, {"Data Processamento", type text}, {"Descrição", type text}, {"IBAN", type text}, {"SWIFT/BIC", type text}, {"Montante", type text}, {"Situação", type text}}), #"Linhas Filtradas" = Table.SelectRows(#"Tipo Alterado1", each ([Situação] = "Aceite")), #"Dividir Coluna por Delimitador" = Table.SplitColumn(#"Linhas Filtradas", "Montante", Splitter.SplitTextByDelimiter(" ", QuoteStyle.Csv), {"Montante.1", "Montante.2"}), #"Tipo Alterado2" = Table.TransformColumnTypes(#"Dividir Coluna por Delimitador",{{"Montante.1", type number}, {"Montante.2", type text}}), #"Colunas com Nome Mudado" = Table.RenameColumns(#"Tipo Alterado2",{{"Montante.1", "Montante"}, {"Montante.2", "Divisa"}}), #"Tipo Alterado3" = Table.TransformColumnTypes(#"Colunas com Nome Mudado",{{"Data Processamento", type date}, {"Nº Operação", Int64.Type}}) in #"Tipo Alterado3"
Depois de realizadas as transformações aos dados, é muito fácil pesquisar transferências bancárias por diversos critérios:
- Número de operação
- Data de processamento
- Descrição
- IBAN ou SWIFT do destinatário da transferência
- Montante
- Situação
- Etc.
Veja-se, a título de exemplo, como estão neste momento os dados dos ficheiros PDF referentes a 3 transferências bancárias:
Extrair texto de PDF digitalizados
O último exemplo demonstra como extrair texto de PDF digitalizados. Seja um contrato ou a página de um livro, há muito conhecimento a descobrir no texto.
Imagine uma sociedade de advogados com milhares de contratos e documentação técnica em arquivo. Como extrair a informação relevante para um novo caso de todo o texto digitalizado ao longo dos anos? Como referido anteriormente, não é possível pesquisar texto em PDF digitalizados e se o trabalho tiver de ser feito manualmente (por humanos) será certamente muito moroso.
Com o meu telemóvel fotografei rapidamente as primeiras 7 páginas de um livro e guardei-as num ficheiro PDF. A primeira página do ficheiro é a seguinte:
Como se pode ver, o texto não é mais do que uma imagem, não temos, portanto, a capacidade para selecionar ou pesquisar qualquer informação.
Para nos ajudar, desta vez, vamos recorrer à biblioteca Tesseract, da Google. Esta é uma biblioteca open-source com a capacidade OCR (Optical Carachter Recognition) que pode ser usada para extrair texto de imagens.
Voltando ao editor do R teríamos o seguinte código:
# Ficheiro 2 - extrair dados de PDF digitalizado library(tesseract) library(pdftools) pt <- tesseract("por")
ficheiro2 <- pdf_convert('digital.pdf', dpi="600)"
output2 <- ocr(ficheiro2, engine="pt)
gsub("[\r\n], " ", output2)
A biblioteca Tesseract utilizada dados de treino para o reconhecimento de texto que são específicos para cada idioma. Portanto, o primeiro passo é precisamente carregar em memória esses dados, para que o algoritmo possa ser treinado em Português e testado nas imagens contidas no ficheiro PDF.
O passo seguinte consiste em converter as páginas do PDF em imagens – que o algoritmo espera como input. De forma conveniente, a biblioteca faz o trabalho pesado por nós, gerando um ficheiro .png para cada uma das páginas.
Por fim, corremos o algoritmo e analisamos o resultado. Na primeira página, obtemos:
[1] "Prólogo O leitor poderá não o saber, mas a aprendizagem automática está presente em toda a parte. Quando escrevemos alguma coisa num motor de busca, é a aprendizagem automática que permite a este mesmo motor determinar quais os resultados que nos vai mostrar (e também quais os anúncios). Quando lemos o nosso email, não vemos a maior parte do spam porque a aprendizagem automática o filtrou e excluiu. Vamos à Amazon.com comprar um livro ou à Netflix para ver um vídeo, e o sistema de aprendizagem automática recomenda-nos títulos dos quais poderemos gostar. O Facebook usa a aprendizagem automática para decidir quais as atualizações a mostrar, € O Twitter faz o mesmo com os tweets. Sempre que usamos um computador, o mais provável é que algures a aprendizagem automática esteja envolvida no processo. Tradicionalmente, a única forma de conseguir que um compu- tador fizesse alguma coisa — desde adicionar dois números à pilotar um avião — passava por escrever um algoritmo que lhe explicasse como o fazer, com laborioso pormenor. Mas os algoritmos de aprendizagem, ou algoritmos evolutivos, são diferentes: sabem o que fazer por sua conta, com base em inferências feitas a partir dos dados. E quanto mais dados tiverem, melhores se tornam. Agora, não temos de programar os computadores: eles programam-se à si mesmos. “Esto não acontece apenas no ciberespaço: todo o nosso dia, desde que acordamos até que adormecemos, encontra-se repleto de aprendi- “O nosso rádio-despertador ganha vida às 7h00. Está a tocar uma canção que nunca ouvimos, mas gostamos bastante dela. Graças ao neo & ni ei 13 se E Ê EA ; | "
Há certamente algum trabalho a fazer com este texto, mas tudo aconteceu em segundos com poucas linhas de código, out-of-the-box!
O texto é agora pesquisável, editável e pode ser analisado usando outro tipo de algoritmos de text-mining.
Avançando um pouco mais no tratamento deste texto, seria possível retirar os espaços duplicados, a pontuação, algumas stopwords e converter todas as palavras em minúsculas, entre outras operações, para se chegar ao seguinte:
[1] "prólogo leitor poderá saber aprendizagem automática presente toda parte escrevemos alguma coisa motor busca aprendizagem automática permite motor determinar quais resultados vai mostrar quais anúncios lemos email vemos maior parte spam porque aprendizagem automática filtrou excluiu vamos amazoncom comprar livro netflix ver vídeo sistema aprendizagem automática recomenda-nos títulos quais poderemos gostar facebook usa aprendizagem automática decidir quais atualizações mostrar twitter faz tweets sempre usamos computador provável algures aprendizagem automática envolvida processo tradicionalmente única forma conseguir computador fizesse alguma coisa desde adicionar dois números pilotar avião passava escrever algoritmo explicasse fazer laborioso pormenor…
Este texto pode ser analisado para extrair rapidamente o seu conteúdo. É possível retirar palavras-chave e conjuntos de palavras frequentes. Por exemplo, nestas primeiras 7 páginas do livro, é relativamente fácil descobrir qual o seu assunto principal, se ordenarmos as palavras de forma decrescente em função da sua frequência: