Files
scripts/scraper/structure_content_test.py

389 lines
14 KiB
Python
Executable File

"""
structure_content_test.py - VERSÃO TESTE - Processa apenas 3 ficheiros
Author: Descomplicar® Crescimento Digital
Link: https://descomplicar.pt
Copyright: 2025 Descomplicar®
"""
import os
import json
import logging
import requests
import time
from pathlib import Path
from typing import Dict, Optional, List
from dotenv import load_dotenv
# Carregar variáveis de ambiente
load_dotenv()
# Configurações TESTE
INPUT_DIR = "/media/ealmeida/Dados/GDrive/Cloud/Clientes_360/CTF_Carstuff/KB/Scrapper/sites/output_md"
OUTPUT_DIR = "/media/ealmeida/Dados/GDrive/Cloud/Clientes_360/CTF_Carstuff/KB/Scrapper/sites/formatted_test"
API_KEY = os.getenv("OPENROUTER_API_KEY")
# Ficheiros de teste
TEST_FILES = [
"thehogring.com_100.md",
"thehogring.com_101.md",
"thehogring.com_102.md"
]
# Configurar logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
class ContentStructurer:
def __init__(self):
self.api_key = API_KEY
self.api_url = "https://openrouter.ai/api/v1/chat/completions"
self.model = "anthropic/claude-3.5-sonnet"
self.structure_prompt = """
És um especialista em estofamento automotivo e documentação técnica.
Analisa o texto fornecido e extrai informação estruturada no seguinte formato JSON:
{
"metadata": {
"titulo": "Título principal do conteúdo",
"categoria": "tipo de conteúdo (tutorial, problema-tecnico, showcase, dica, recurso)",
"topicos": ["lista", "de", "topicos", "principais"],
"fonte": "nome do site original"
},
"conteudo": [
{
"tipo": "problema|solucao|resultado|info",
"titulo": "Título desta secção",
"descricao": "Descrição detalhada em português PT-PT",
"detalhes": [
"Lista de pontos-chave",
"Passos específicos",
"Materiais ou ferramentas mencionados"
],
"relevancia": "alta|media|baixa"
}
],
"keywords": ["palavras-chave", "tecnicas", "relevantes"],
"aplicabilidade": ["tipos de veículos ou situações onde se aplica"]
}
REGRAS IMPORTANTES:
1. Se o conteúdo for um artigo sobre problema técnico:
- Identifica claramente: problema → causa → solução → resultado
2. Se for showcase/galeria:
- Extrai: projeto → técnicas usadas → materiais → resultado visual
3. Se for tutorial:
- Extrai: objetivo → passos → dicas → precauções
4. Se for recurso/ferramenta:
- Extrai: propósito → características → vantagens → aplicação
5. IGNORA completamente:
- Navegação do site
- Comentários genéricos
- Publicidade
- Links de rodapé sem contexto
6. FOCA em:
- Técnicas de estofamento
- Materiais e ferramentas
- Processos e métodos
- Problemas comuns e soluções
- Casos práticos
7. Usa PORTUGUÊS DE PORTUGAL (PT-PT):
- "estofamento" (não "estofado")
- "automóvel" (não "carro")
- "técnico" (não "técnico")
Responde APENAS com o JSON, sem texto adicional.
"""
def structure_content(self, content: str, source_file: str, retries: int = 3) -> Optional[Dict]:
"""Estrutura conteúdo usando AI."""
for attempt in range(retries):
try:
headers = {
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json",
"HTTP-Referer": "https://descomplicar.pt",
"X-Title": "CTF Carstuff Knowledge Base - TEST"
}
data = {
"model": self.model,
"messages": [
{"role": "system", "content": self.structure_prompt},
{"role": "user", "content": f"Ficheiro: {source_file}\n\n{content}"}
],
"temperature": 0.3,
"max_tokens": 4000
}
logger.info(f"🔄 A processar com AI: {source_file}")
response = requests.post(
self.api_url,
headers=headers,
json=data,
timeout=90
)
if response.status_code == 200:
result = response.json()
if 'choices' in result and len(result['choices']) > 0:
content_text = result['choices'][0]['message']['content']
# Tentar extrair JSON do texto
try:
# Remover markdown code blocks se existir
if '```json' in content_text:
content_text = content_text.split('```json')[1].split('```')[0]
elif '```' in content_text:
content_text = content_text.split('```')[1].split('```')[0]
structured_data = json.loads(content_text.strip())
logger.info(f"✅ JSON extraído com sucesso")
return structured_data
except json.JSONDecodeError as e:
logger.error(f"❌ Erro ao parsear JSON: {e}")
logger.debug(f"Conteúdo recebido: {content_text[:500]}")
if attempt < retries - 1:
time.sleep(2 ** attempt)
continue
return None
else:
logger.error(f"❌ Resposta sem choices")
elif response.status_code == 429:
wait_time = int(response.headers.get('Retry-After', 20))
logger.warning(f"⏳ Rate limit. Aguardando {wait_time}s...")
time.sleep(wait_time)
continue
else:
logger.error(f"❌ Erro API: {response.status_code} - {response.text}")
if attempt < retries - 1:
time.sleep(2 ** attempt)
continue
return None
except requests.exceptions.Timeout:
logger.warning(f"⏱️ Timeout (tentativa {attempt + 1}/{retries})")
if attempt < retries - 1:
time.sleep(2 ** attempt)
continue
return None
except Exception as e:
logger.error(f"❌ Erro ao estruturar conteúdo: {str(e)}")
if attempt < retries - 1:
time.sleep(2 ** attempt)
continue
return None
return None
def format_structured_md(self, structured_data: Dict, original_file: str) -> str:
"""Converte dados estruturados em Markdown formatado."""
md_lines = []
# Metadata
meta = structured_data.get('metadata', {})
md_lines.append(f"# {meta.get('titulo', 'Sem Título')}")
md_lines.append("")
md_lines.append(f"**Categoria**: {meta.get('categoria', 'Geral')}")
md_lines.append(f"**Fonte**: {meta.get('fonte', original_file)}")
if meta.get('topicos'):
md_lines.append(f"**Tópicos**: {', '.join(meta['topicos'])}")
md_lines.append("")
md_lines.append("---")
md_lines.append("")
# Conteúdo estruturado
conteudo = structured_data.get('conteudo', [])
# Agrupar por tipo
problemas = [c for c in conteudo if c.get('tipo') == 'problema']
solucoes = [c for c in conteudo if c.get('tipo') == 'solucao']
resultados = [c for c in conteudo if c.get('tipo') == 'resultado']
info = [c for c in conteudo if c.get('tipo') == 'info']
# Problemas
if problemas:
md_lines.append("## 🔍 Problemas Identificados")
md_lines.append("")
for p in problemas:
md_lines.append(f"### {p.get('titulo', 'Problema')}")
md_lines.append("")
md_lines.append(p.get('descricao', ''))
md_lines.append("")
if p.get('detalhes'):
for detalhe in p['detalhes']:
md_lines.append(f"- {detalhe}")
md_lines.append("")
# Soluções
if solucoes:
md_lines.append("## 💡 Soluções")
md_lines.append("")
for s in solucoes:
md_lines.append(f"### {s.get('titulo', 'Solução')}")
md_lines.append("")
md_lines.append(s.get('descricao', ''))
md_lines.append("")
if s.get('detalhes'):
for i, detalhe in enumerate(s['detalhes'], 1):
md_lines.append(f"{i}. {detalhe}")
md_lines.append("")
# Resultados
if resultados:
md_lines.append("## ✅ Resultados")
md_lines.append("")
for r in resultados:
md_lines.append(f"### {r.get('titulo', 'Resultado')}")
md_lines.append("")
md_lines.append(r.get('descricao', ''))
md_lines.append("")
if r.get('detalhes'):
for detalhe in r['detalhes']:
md_lines.append(f"- {detalhe}")
md_lines.append("")
# Informação adicional
if info:
md_lines.append("## 📋 Informação Adicional")
md_lines.append("")
for inf in info:
md_lines.append(f"### {inf.get('titulo', 'Info')}")
md_lines.append("")
md_lines.append(inf.get('descricao', ''))
md_lines.append("")
if inf.get('detalhes'):
for detalhe in inf['detalhes']:
md_lines.append(f"- {detalhe}")
md_lines.append("")
# Keywords e aplicabilidade
md_lines.append("---")
md_lines.append("")
if structured_data.get('keywords'):
md_lines.append(f"**Palavras-chave**: {', '.join(structured_data['keywords'])}")
md_lines.append("")
if structured_data.get('aplicabilidade'):
md_lines.append("**Aplicabilidade**:")
for app in structured_data['aplicabilidade']:
md_lines.append(f"- {app}")
md_lines.append("")
return '\n'.join(md_lines)
def process_file(self, input_file: Path, output_dir: Path) -> bool:
"""Processa um ficheiro."""
try:
output_file = output_dir / f"structured_{input_file.name}"
# Verificar tamanho
file_size = input_file.stat().st_size
if file_size < 500:
logger.warning(f"⚠️ Ficheiro muito pequeno ({file_size}B): {input_file.name}")
return False
# Ler conteúdo
with open(input_file, 'r', encoding='utf-8') as f:
content = f.read()
logger.info(f"📄 Processando: {input_file.name} ({file_size/1024:.1f}KB)")
# Estruturar com AI
structured_data = self.structure_content(content, input_file.name)
if not structured_data:
logger.error(f"❌ Falha ao estruturar: {input_file.name}")
return False
# Converter para MD formatado
formatted_md = self.format_structured_md(structured_data, input_file.name)
# Guardar
with open(output_file, 'w', encoding='utf-8') as f:
f.write(formatted_md)
# Guardar JSON também
json_file = output_dir / f"structured_{input_file.stem}.json"
with open(json_file, 'w', encoding='utf-8') as f:
json.dump(structured_data, f, indent=2, ensure_ascii=False)
logger.info(f"✅ Guardado: {output_file.name}")
# Pausa para evitar rate limits
time.sleep(3)
return True
except Exception as e:
logger.error(f"❌ Erro ao processar {input_file.name}: {e}")
return False
def main():
"""Função principal TESTE."""
if not API_KEY:
logger.error("❌ OPENROUTER_API_KEY não configurada no .env")
return
input_path = Path(INPUT_DIR)
output_path = Path(OUTPUT_DIR)
if not input_path.exists():
logger.error(f"❌ Diretório não existe: {INPUT_DIR}")
return
output_path.mkdir(parents=True, exist_ok=True)
# Processar APENAS ficheiros de teste
logger.info(f"🧪 MODO TESTE: Processando apenas {len(TEST_FILES)} ficheiros")
test_files = []
for filename in TEST_FILES:
file_path = input_path / filename
if file_path.exists():
test_files.append(file_path)
else:
logger.warning(f"⚠️ Ficheiro não encontrado: {filename}")
logger.info(f"📊 Encontrados {len(test_files)} ficheiros de teste")
# Processar
structurer = ContentStructurer()
successful = 0
for md_file in test_files:
if structurer.process_file(md_file, output_path):
successful += 1
logger.info("")
logger.info("="*60)
logger.info(f"✅ Concluído: {successful}/{len(test_files)} ficheiros processados")
logger.info(f"📁 Output: {output_path}")
logger.info("="*60)
if __name__ == '__main__':
print("═══════════════════════════════════════════════════════════")
print(" 🧪 CTF CARSTUFF - TESTE ESTRUTURAÇÃO INTELIGENTE")
print(" Formato: Problema → Solução → Resultado")
print(" Ficheiros: 3 amostras The Hog Ring")
print("═══════════════════════════════════════════════════════════")
print("")
main()
print("")
print("✅ Teste concluído!")