247 lines
8.8 KiB
Python
Executable File
247 lines
8.8 KiB
Python
Executable File
"""
|
|
extract_reddit_only.py - Extração EXCLUSIVA Reddit
|
|
|
|
Processa apenas os 2 ficheiros Reddit:
|
|
- reddit_Autoupholstery_1762438195.md
|
|
- reddit_upholstery_1762438227.md
|
|
|
|
Modelo: google/gemini-2.5-flash-lite
|
|
|
|
Author: Descomplicar® Crescimento Digital
|
|
Link: https://descomplicar.pt
|
|
Copyright: 2025 Descomplicar®
|
|
"""
|
|
|
|
import os
|
|
import json
|
|
import requests
|
|
from pathlib import Path
|
|
from typing import Dict, Optional
|
|
from dotenv import load_dotenv
|
|
import time
|
|
from datetime import datetime
|
|
|
|
load_dotenv()
|
|
|
|
# Configurações
|
|
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/knowledge_base_final"
|
|
API_KEY = os.getenv("OPENROUTER_API_KEY")
|
|
|
|
# Ficheiros Reddit específicos
|
|
REDDIT_FILES = [
|
|
"reddit_Autoupholstery_1762438195.md",
|
|
"reddit_upholstery_1762438227.md"
|
|
]
|
|
|
|
class RedditKnowledgeExtractor:
|
|
def __init__(self):
|
|
self.api_key = API_KEY
|
|
self.api_url = "https://openrouter.ai/api/v1/chat/completions"
|
|
self.model = "google/gemini-2.5-flash-lite"
|
|
|
|
self.stats = {'total': 0, 'processados': 0, 'relevantes': 0, 'erros': 0}
|
|
|
|
# Prompt otimizado para Reddit
|
|
self.extraction_prompt = """
|
|
És um especialista em estofamento automotivo, náutico, ferroviário e aeronáutico.
|
|
|
|
⚠️ CRITÉRIO CRÍTICO DE RELEVÂNCIA:
|
|
O conteúdo SÓ É RELEVANTE se contiver o fluxo COMPLETO:
|
|
PROBLEMA → SOLUÇÃO → RESULTADO
|
|
|
|
Se o texto apenas descreve problemas SEM as suas soluções, retorna: {"relevante": false}
|
|
|
|
IGNORAR COMPLETAMENTE:
|
|
- Comentários genéricos ("obrigado", "bom post", "upvote")
|
|
- Conversas off-topic
|
|
- Problemas mencionados sem soluções correspondentes
|
|
- Links sem contexto
|
|
- Discussões sem conclusão técnica
|
|
|
|
EXTRAIR APENAS SE EXISTIR FLUXO COMPLETO:
|
|
1. **Problema técnico específico** identificado claramente
|
|
2. **Solução prática** aplicada ou recomendada para esse problema
|
|
3. **Resultado obtido** ou esperado (se mencionado)
|
|
|
|
FORMATO JSON DE SAÍDA:
|
|
{
|
|
"relevante": true/false,
|
|
"categoria_aplicacao": "automovel|automovel-classico|mobiliario|nautica|ferroviaria|aeronautica|geral",
|
|
"tipo_conteudo": "problema-tecnico|tutorial|caso-pratico|comparacao-materiais",
|
|
"casos_completos": [
|
|
{
|
|
"problema": {
|
|
"descricao": "Problema específico identificado",
|
|
"contexto": "Tipo de veículo/aplicação/situação",
|
|
"severidade": "baixa|media|alta"
|
|
},
|
|
"solucao": {
|
|
"material_usado": "Material específico aplicado",
|
|
"tecnica": "Técnica ou método usado",
|
|
"passos": "Passos principais (se mencionados)"
|
|
},
|
|
"resultado": {
|
|
"obtido": "Resultado concreto alcançado",
|
|
"qualidade": "Avaliação da solução"
|
|
}
|
|
}
|
|
],
|
|
"materiais_discutidos": {
|
|
"principais": ["materiais eficazes mencionados"],
|
|
"nao_recomendados": ["materiais que falharam ou são evitados"]
|
|
},
|
|
"keywords_tecnicas": ["termos", "tecnicos", "relevantes"],
|
|
"aplicabilidade": ["tipos de veículos/situações"],
|
|
"nivel_expertise": "iniciante|intermedio|avancado"
|
|
}
|
|
|
|
⚠️ IMPORTANTE:
|
|
- Se o texto só menciona problemas sem soluções: {"relevante": false}
|
|
- Se menciona soluções sem contexto de problema: {"relevante": false}
|
|
- Apenas CASOS COMPLETOS com problema→solução→resultado devem ser extraídos.
|
|
"""
|
|
|
|
def extract_knowledge(self, content: str) -> Optional[Dict]:
|
|
"""Extrai conhecimento via OpenRouter API."""
|
|
headers = {
|
|
"Authorization": f"Bearer {self.api_key}",
|
|
"Content-Type": "application/json",
|
|
"HTTP-Referer": "https://descomplicar.pt",
|
|
"X-Title": "CTF Reddit Knowledge Extractor"
|
|
}
|
|
|
|
payload = {
|
|
"model": self.model,
|
|
"messages": [
|
|
{
|
|
"role": "system",
|
|
"content": self.extraction_prompt
|
|
},
|
|
{
|
|
"role": "user",
|
|
"content": f"Analisa este conteúdo Reddit e extrai conhecimento técnico:\n\n{content[:15000]}"
|
|
}
|
|
],
|
|
"temperature": 0.3,
|
|
"max_tokens": 2500,
|
|
"response_format": {"type": "json_object"}
|
|
}
|
|
|
|
try:
|
|
response = requests.post(self.api_url, headers=headers, json=payload, timeout=90)
|
|
response.raise_for_status()
|
|
|
|
result = response.json()
|
|
content_text = result['choices'][0]['message']['content']
|
|
|
|
# Parse JSON
|
|
knowledge = json.loads(content_text)
|
|
return knowledge
|
|
|
|
except Exception as e:
|
|
print(f"⚠️ Erro API: {e}")
|
|
return None
|
|
|
|
def process_file(self, filename: str):
|
|
"""Processa um ficheiro Reddit MD."""
|
|
filepath = Path(INPUT_DIR) / filename
|
|
|
|
if not filepath.exists():
|
|
print(f" ❌ Ficheiro não encontrado: {filepath}")
|
|
self.stats['erros'] += 1
|
|
return
|
|
|
|
print(f"\n📄 A processar: {filename}")
|
|
self.stats['total'] += 1
|
|
|
|
try:
|
|
with open(filepath, 'r', encoding='utf-8') as f:
|
|
content = f.read()
|
|
|
|
# Validação básica
|
|
if len(content) < 500:
|
|
print(f" ⏭️ Ficheiro muito pequeno (<500 chars), ignorado")
|
|
return
|
|
|
|
# Extração via API
|
|
print(f" 🔄 A enviar para API... ({len(content)} caracteres)")
|
|
knowledge = self.extract_knowledge(content)
|
|
|
|
if not knowledge:
|
|
print(f" ❌ Erro ao extrair conhecimento")
|
|
self.stats['erros'] += 1
|
|
return
|
|
|
|
# Verificar relevância
|
|
if not knowledge.get('relevante', False):
|
|
print(f" ⏭️ Conteúdo não relevante (sem problema→solução)")
|
|
return
|
|
|
|
# Verificar casos completos
|
|
casos = knowledge.get('casos_completos', [])
|
|
if not casos or len(casos) == 0:
|
|
print(f" ⏭️ Sem casos completos extraídos")
|
|
return
|
|
|
|
# Guardar JSON
|
|
stem = filepath.stem # reddit_Autoupholstery_1762438195
|
|
output_file = Path(OUTPUT_DIR) / f"knowledge_{stem}.json"
|
|
|
|
with open(output_file, 'w', encoding='utf-8') as f:
|
|
json.dump(knowledge, f, ensure_ascii=False, indent=2)
|
|
|
|
print(f" ✅ Extraído: {len(casos)} casos completos → knowledge_{stem}.json")
|
|
self.stats['processados'] += 1
|
|
self.stats['relevantes'] += 1
|
|
|
|
# Rate limiting
|
|
time.sleep(2)
|
|
|
|
except Exception as e:
|
|
print(f" ❌ Erro ao processar: {e}")
|
|
self.stats['erros'] += 1
|
|
|
|
def run(self):
|
|
"""Executa extração Reddit."""
|
|
print("═══════════════════════════════════════════════════════════")
|
|
print(" EXTRAÇÃO REDDIT - Knowledge Base CTF")
|
|
print(" Modelo: google/gemini-2.5-flash-lite")
|
|
print(" Descomplicar® Crescimento Digital")
|
|
print("═══════════════════════════════════════════════════════════")
|
|
print()
|
|
|
|
# Criar output dir se não existir
|
|
Path(OUTPUT_DIR).mkdir(parents=True, exist_ok=True)
|
|
|
|
print(f"📂 Input: {INPUT_DIR}")
|
|
print(f"📂 Output: {OUTPUT_DIR}")
|
|
print(f"📊 Ficheiros a processar: {len(REDDIT_FILES)}\n")
|
|
|
|
for filename in REDDIT_FILES:
|
|
self.process_file(filename)
|
|
|
|
# Resumo final
|
|
print("\n═══════════════════════════════════════════════════════════")
|
|
print(" EXTRAÇÃO REDDIT CONCLUÍDA")
|
|
print("═══════════════════════════════════════════════════════════")
|
|
print()
|
|
print("📊 ESTATÍSTICAS:")
|
|
print(f" Total ficheiros: {self.stats['total']}")
|
|
print(f" Processados com sucesso: {self.stats['processados']}")
|
|
print(f" Relevantes: {self.stats['relevantes']}")
|
|
print(f" Erros: {self.stats['erros']}")
|
|
print()
|
|
|
|
if self.stats['relevantes'] > 0:
|
|
print(f"✅ Extraídos {self.stats['relevantes']} ficheiros com casos completos")
|
|
else:
|
|
print("⚠️ Nenhum caso relevante extraído")
|
|
|
|
print(f"📁 Ficheiros guardados em: {OUTPUT_DIR}")
|
|
print()
|
|
|
|
if __name__ == "__main__":
|
|
extractor = RedditKnowledgeExtractor()
|
|
extractor.run()
|