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

https://www.python.org/