Mesclando arquivos PDFs: REGEX e PyPDF2 com Python

Imagine que você tem uma série de documentos em pdf, ou mesmo outro arquivo pdf, para mesclar e manter tudo em apenas um arquivo.

Vamos fazer isso com REGEX e PyPDF2 com python

Você, assim como eu, já foi em sites que mesclam PDF. Mas e se você puder fazer isso de graça com Python? Ou ainda criar seu próprio site para hospedar esse “concatenador” de pdf?

É isso que vamos fazer agora! Nesse projeto, vamos concatenar arquivos PDF com base na data contida no nome do arquivo.

Startando o Projeto – Bibiotecas REGEX e PyPDF2

Vamos usar as libs PyPDF2, re e os. Com exceção da PyPDF2, tanto a lib os como a re são módulos padrão do Python. Sendo assim, já vem instalada.

Então, a unica a ser instalada neste caso é a PyPDF2. Execute a seguinte instalação em seu ambiente virtual.

pip install PyPDF2 

Dentro do código, você vai importa-las dessa forma:

Função de extração da data com re – Regular Expression

Aqui vamos usar a função search da lib re para encontrar as ocorrências do padrão na string.

A string em nosso caso, é o nome do arquivo.

Definimos o padrão usando raw string para que o Python não interprete caracteres especiais em nosso padrão.

pattern = r'\d{4}-\d{2}-\d{2}' 

Uma vez definido o padrão, executados a busca e recebemos o resultado. O nosso resultado retornado é composto pela classe Macth da lib re.

Com ela podemos usar diversos métodos que recuperam as infos da busca pelo padrão, tais como: posição, string, início e fim do padrão.

Caso tenhamos um resultado em mãos, iremos executar o método group() que retorna a parte inteira da string que casou com a expressão. Caso contrário, retornamos None.

Da uma olhada em como ficou a função:

def extract_date_from_filename(filename):
    # Utiliza expressão regular para extrair a data do nome do arquivo
    pattern = r'\d{4}-\d{2}-\d{2}'  # Formato de data esperado YYYY-MM-DD
    match = re.search(pattern, filename)
    if match:
        return match.group()
    return None

Ordenando Arquivos baseado em padrão de string – Data

Agora, vamos usar a data extraida do nomes do arquivos para ordernar nossa lista de arquivos.

O primeiro pedaço de código usa um for para colocar dentro de uma lista todos os arquivos de um diretório.

Esse for em uma linha melhora a legibilidade do código e diminui o número de linhas necessárias.

Neste caso, estamos verificando os items dentro do directoy_path e adicionando a lista apenas se o f for um arquivo.

Após a criação da nossa lista (pdf_files_with_date) que receberá as tuplas, vamos chamar os métodos anteriores.

def sorting_files_by_date(directory_path):
    files = [f for f in os.listdir(directory_path)
             if os.path.isfile(os.path.join(directory_path, f))]

    # var lista de tuplas
    pdf_files_with_date = []

    for file in files:
        if file.lower().endswith('.pdf'):
            date = extract_date_from_filename(file)
            if date:
                # tupla: (filename, data_padrao)
                pdf_files_with_date.append((file, date))

    return sorted(pdf_files_with_date, key=lambda x: x[1])

Verificamos se os arquivos são pdf com o método de string endswith(). Caso verdadeiro chamamos a função extract_data_from_file(file), passando o nome do arquivo atual como parâmetro.

Caso haja uma data (correspondente ao padrão) adicionamos a tupla (nome_do_arquivo, data_encontrada) a lista.

Após a varredura a função sorted irá ordernar a lista a parte de uma key (chave).

Ficou confuso com o lambda na história? Então, vamos entender melhor.

Key recebe a função lambda que permite verificar o segundo elemento da tupla.

Neste caso, x é um elemento da lista. Já que a lista é formada por tuplas, seremos capazes de acessar seus items pelos indices 0 e 1.

Sendo assim, x[0] seria o primeiro item, o nome do arquivo. Já x[1] consiste no segundo elemento, a data associada ao arquivo.

Ordenando pela data seremos capazes de mesclar os arquivos desse diretório da maneira correta.

Perceba que a lista realocada toda vez que chamamos a função para um novo subdiretório. Assim, não teremos sobreposição de listas.

Função de concatenação dos PDFs

Vamos então mesclar os arquivos que recebemos para realizar o trabalho de freela.

Neste caso, precisamos criar uma nova estrutura igual a original para que os arquivos pdf estão corretamente associados a cada diretório.

Mas, antes disso vamos ordernar nossos arquivos de cada subdiretório.

Você vai perceber que a var pdf_files_sorted_by_date está recebendo justamente esses arquivos já ordernados.

O próximo passo está em alocar a classe PdfMerger(). Para que possamos adicionar os arquivos a serem escritos em um único arquivo destino.

 pdf_files_sorted_by_date = sorting_files_by_date(directory_path)
 merger = PyPDF2.PdfMerger()

Então, cada arquivo pdf da nossa lista será adicionado ao merger. Lembre-se de adicionar o caminho completo.

def merge_pdfs_sorted_by_date(directory_path, output_file='mesclados.pdf'):

    pdf_files_sorted_by_date = sorting_files_by_date(directory_path)
    merger = PyPDF2.PdfMerger()

    for file, _ in pdf_files_sorted_by_date:
        file_path = os.path.join(directory_path, file)
        merger.append(file_path)

    new_path = '/'.join(['arquivos_mesclados', directory_path.split('/')[1]])
    if not os.path.exists(new_path):
        os.makedirs(new_path)

    output_path = os.path.join(new_path, output_file)
    with open(output_path, 'wb') as output:
        merger.write(output)

    print(f'Arquivos PDF foram mesclados e salvo como {output_file} em {new_path}')

Se você não precisa manter a mesma estrutura da original, após o merge bastaria escrever o merger.write().

Mas no meu caso, o cliente queria dessa forma. Então….

    #definimos o novo caminho e criamos caso não exista
    new_path = '/'.join(['arquivos_mesclados', directory_path.split('/')[1]])
    if not os.path.exists(new_path):
        os.makedirs(new_path)

Definimos o nome do nosso arquivo destino (único pdf).

 output_path = os.path.join(new_path, output_file)

Depois, escrevemos o arquivo unico usando o método write() da classe PdfMerger(). Lembre-se de abrir o arquivo destino como escrita binária (wb).

A função finalizada fica como na imagem a seguir.

Programa principal

Ok, após criarmos todas as nossas funções iremos testar nosso código.

O nome do diretório esta associado a estrutura que tenho no meu projeto com Pycharm. De uma olhada na próxima imagem que você vai entender melhor.

from function import *

# Caminho para o diretório contendo os arquivos PDF
for diretorio in os.listdir('arquivos'):
    # Chama a função para mesclar os arquivos PDF no diretório fornecido
    merge_pdfs_sorted_by_date('arquivos/'+diretorio)

Após a execução do código a minha estrutura ficou assim:

Que tal aprender mais?

Veja minha seleção de Livros que Impulsionaram minha carreira.

Para mais projetos com Python

Artigos e tutoriais SR: Projetos com Python em nosso site

Criando CRUD MySQL com Python

Conectando ao MySQL com Python

Instalar Python Windows

Projeto para Portifólio: Sua primeira API com Python e MySQL

Principais Bibliotecas Python para Data Science: Manipulação e Visualização

Python: minhas portas TCP e UDP abertas

Playlist do projeto (em construção)

Para quem prefere vídeos, segue nossa playlista do mini projeto.

Juliana Mascarenhas

Data Scientist and Master in Computer Modeling by LNCC.
Computer Engineer

O que é CGNAT ?

O CGNAT na maioria das vezes utiliza o intervalo de endereços IP reservado para uso…

Ler mais

https://www.python.org/