Files
scripts/pdf-extractor/pdfmd.py

272 lines
9.1 KiB
Python
Executable File

"""
pdfmd.py
Author: Descomplicar® Crescimento Digital
Link: https://descomplicar.pt
Copyright: 2025 Descomplicar®
"""
import re
import pypdf
import requests
import time
from pathlib import Path
from datetime import datetime
# Configurações da API
import os
from dotenv import load_dotenv
load_dotenv()
BASE_URL = "https://openrouter.ai/api/v1/chat/completions"
API_KEY = os.environ.get("OPENROUTER_API_KEY", "")
if not API_KEY:
print("ERRO: OPENROUTER_API_KEY não configurada! Criar ficheiro .env com OPENROUTER_API_KEY=...")
exit(1)
def extrair_nome_capitulo(texto):
"""Extrai o nome do capítulo do texto."""
padrao = r'(?:Chapter|Capítulo|Parte|Section)\s*\d*\s*[:\-]?\s*([^\n]+)'
match = re.search(padrao, texto, re.IGNORECASE)
if match:
return match.group(1).strip()
return "Sem título"
def processar_texto(texto, titulo, max_retries=3, retry_delay=5):
# Remover a linha que redefine retry_delay
"""Processa o texto extraído do PDF e formata em Markdown."""
prompt = f"""Analise este texto de '{titulo}' e crie:
1. Um resumo do capítulo (3-4 parágrafos)
2. 10 tags técnicas relevantes para o conteúdo
3. O texto completo, mantendo a estrutura e formatação original
4. 10 perguntas e respostas (FAQs) detalhadas sobre o conteúdo
Mantenha o texto no idioma original do documento.
Texto para processar:
{texto}"""
headers = {
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json"
}
data = {
"model": "mistralai/ministral-3b",
"messages": [{"role": "user", "content": prompt}],
"temperature": 0.3
}
for attempt in range(max_retries):
try:
response = requests.post(
BASE_URL,
json=data,
headers=headers,
timeout=30
)
if response.status_code == 200:
return response.json()['choices'][0]['message']['content'].strip()
elif response.status_code == 429:
wait_time = retry_delay * (attempt + 1)
print(f"Rate limit atingido. A aguardar {wait_time}s...")
time.sleep(wait_time)
else:
print(f"Erro API: {response.status_code}")
print(f"Resposta: {response.text}")
time.sleep(retry_delay)
except requests.exceptions.RequestException as e:
print(f"Erro na requisição: {str(e)}")
time.sleep(retry_delay)
return None
def extrair_capitulos(pdf_path):
"""Extrai capítulos do PDF com seus títulos."""
reader = pypdf.PdfReader(pdf_path)
texto_completo = []
# Primeiro, extrair todo o texto do PDF e formatar em Markdown
print("\nExtraindo texto do PDF...")
for i, page in enumerate(reader.pages):
try:
texto = page.extract_text()
if texto:
# Formatação básica para Markdown
texto = texto.replace('\n', ' \n') # Adiciona quebra de linha em Markdown
texto_completo.append(texto)
print(f" Página {i+1}: {len(texto)} caracteres")
else:
print(f" Página {i+1}: sem texto")
except Exception as e:
print(f" Erro na página {i+1}: {str(e)}")
continue
# Verifica se conseguiu extrair algum texto
if not texto_completo:
print("AVISO: Não foi possível extrair texto do PDF.")
return [{'numero': 1, 'titulo': 'Documento Completo', 'texto': ''}]
texto_documento = '\n'.join(texto_completo)
print(f"\nTotal extraído: {len(texto_documento)} caracteres")
# Padrões para identificar capítulos
padroes_capitulo = [
r'(?:Chapter|Capítulo|Parte|Section)\s*\d+',
r'\d+\.\s+[A-Z]',
r'[A-Z]+\s*\d+\s*[:\-]',
r'(?:Introdução|Introduction)',
r'(?:Conclusão|Conclusion)',
r'(?:Anexo|Appendix)\s*[A-Z]',
r'(?:Bibliografia|References)',
r'(?:Índice|Index)'
]
# Tenta identificar capítulos
print("\nProcurando divisões de capítulos...")
padrao_combinado = '|'.join(f'({p})' for p in padroes_capitulo)
matches = list(re.finditer(padrao_combinado, texto_documento, re.IGNORECASE | re.MULTILINE))
# Se não encontrou divisões, retorna documento completo
if not matches:
print("Nenhuma divisão de capítulo encontrada - tratando como documento único")
return [{'numero': 1, 'titulo': 'Documento Completo', 'texto': texto_documento}]
# Processa cada capítulo encontrado
capitulos = []
print("\nProcessando capítulos...")
# Adiciona o texto inicial antes do primeiro capítulo, se houver
if matches[0].start() > 0:
texto_inicial = texto_documento[:matches[0].start()].strip()
if texto_inicial:
capitulos.append({
'numero': 1,
'titulo': 'Conteúdo Inicial',
'texto': texto_inicial
})
print(f" Adicionado conteúdo inicial: {len(texto_inicial)} caracteres")
# Processa os capítulos identificados
for i, match in enumerate(matches):
inicio = match.start()
if i < len(matches) - 1:
fim = matches[i+1].start()
else:
fim = len(texto_documento)
texto_capitulo = texto_documento[inicio:fim].strip()
# Pega a primeira linha como título
linhas = texto_capitulo.split('\n')
titulo = linhas[0].strip()
numero = len(capitulos) + 1
capitulos.append({
'numero': numero,
'titulo': titulo,
'texto': texto_capitulo
})
print(f" Capítulo {numero}: {titulo} ({len(texto_capitulo)} caracteres)")
# Verifica se todo o texto foi incluído
texto_capitulos = ''.join(cap['texto'] for cap in capitulos)
if len(texto_capitulos) < len(texto_documento) * 0.9: # Margem de 10% para diferenças de formatação
print("\nAVISO: Possível perda de conteúdo detectada!")
print(f" Texto original: {len(texto_documento)} caracteres")
print(f" Texto nos capítulos: {len(texto_capitulos)} caracteres")
print("Retornando documento completo para garantir preservação...")
return [{'numero': 1, 'titulo': 'Documento Completo', 'texto': texto_documento}]
print(f"\nProcessamento concluído: {len(capitulos)} capítulos identificados")
return capitulos
def processar_pdf(arquivo_pdf):
"""Processa um arquivo PDF."""
print(f"\nProcessando: {arquivo_pdf}")
try:
capitulos = extrair_capitulos(arquivo_pdf)
print(f"Encontrados {len(capitulos)} capítulos")
md_content = f"""# {arquivo_pdf.stem}
Data de processamento: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
"""
for capitulo in capitulos:
print(f"Processando capítulo {capitulo['numero']}: {capitulo['titulo']}")
conteudo_processado = processar_texto(
capitulo['texto'],
f"Capítulo {capitulo['numero']} - {capitulo['titulo']}"
)
if conteudo_processado:
md_content += f"\n{conteudo_processado}\n\n---\n"
else:
print(f"Erro ao processar capítulo {capitulo['numero']}")
# Remover o delay entre capítulos para evitar interrupções
# Salvar arquivo
caminho_saida = Path('output') / arquivo_pdf.parent.relative_to('input')
caminho_saida.mkdir(parents=True, exist_ok=True)
arquivo_saida = caminho_saida / f"{arquivo_pdf.stem}.md"
with open(arquivo_saida, 'w', encoding='utf-8') as f:
f.write(md_content)
print(f"Arquivo salvo: {arquivo_saida}")
return True
except Exception as e:
print(f"Erro ao processar {arquivo_pdf}: {str(e)}")
return False
def main():
print("Iniciando o processamento...")
start_time = datetime.now()
Path('input').mkdir(exist_ok=True)
Path('output').mkdir(exist_ok=True)
pdfs = list(Path('input').rglob('*.pdf'))
total = len(pdfs)
print(f"Encontrados {total} PDFs")
falhas = []
sucessos = []
for i, pdf in enumerate(pdfs, 1):
print(f"\nProcessando {i}/{total}: {pdf}")
try:
if processar_pdf(pdf):
sucessos.append(pdf)
else:
falhas.append(pdf)
except Exception as e:
print(f"Erro ao processar {pdf}: {str(e)}")
falhas.append(pdf)
sucessos.append(pdf)
else:
falhas.append(pdf)
time.sleep(5)
print(f"\n=== Relatório Final ===")
print(f"Data de início: {start_time}")
print(f"Data de término: {datetime.now()}")
print(f"Duração: {datetime.now() - start_time}")
print(f"PDFs processados com sucesso: {len(sucessos)}")
print(f"PDFs com falha: {len(falhas)}")
if falhas:
print("\nFalhas:")
for f in falhas:
print(f"- {f}")
if __name__ == "__main__":
main()