Files
scripts/kb-processor/src/processors/web_processor.py

214 lines
7.3 KiB
Python
Executable File

"""
Web Processor - Módulo para processamento de conteúdo web
Descomplicar - Agência de Aceleração Digital
https://www.descomplicar.pt
"""
import requests
from bs4 import BeautifulSoup
from urllib.parse import urljoin, urlparse
from typing import List, Dict
from datetime import datetime
from openai import OpenAI
from .base_processor import BaseProcessor
import time
class WebProcessor(BaseProcessor):
"""Processador específico para conteúdo web."""
def __init__(self, url: str):
"""
Inicializa o processador web.
Args:
url (str): URL do conteúdo web
"""
super().__init__(url)
self.url = url
self.base_domain = urlparse(url).netloc
self.soup = None
self.links = []
self.images = []
# Configurar cliente OpenAI para traduções
self.translator = OpenAI(
api_key="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"
)
# Atualizar metadata
self.metadata.update({
"tipo_documento": "web",
"fonte": url,
"data_original": datetime.now().strftime("%d-%m-%Y")
})
def translate_batch(self, texts: List[str]) -> List[str]:
"""
Traduz um lote de textos de uma vez.
Args:
texts (List[str]): Lista de textos para traduzir
Returns:
List[str]: Lista de textos traduzidos
"""
if not texts:
return []
# Juntar textos com marcadores
combined_text = "\n---SPLIT---\n".join(texts)
try:
response = self.translator.chat.completions.create(
model="gpt-4",
messages=[
{"role": "system", "content": "Traduza o seguinte texto para português de Portugal. Mantenha termos técnicos em inglês quando apropriado. Mantenha os marcadores ---SPLIT--- para separar os textos:"},
{"role": "user", "content": combined_text}
]
)
# Separar textos traduzidos
translated = response.choices[0].message.content.split("\n---SPLIT---\n")
return [t.strip() for t in translated]
except Exception as e:
print(f"Erro ao traduzir textos: {str(e)}")
return texts
def read_content(self) -> str:
"""
Lê o conteúdo da URL.
Returns:
str: Conteúdo da página web
"""
try:
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
}
response = requests.get(self.url, headers=headers, timeout=30)
response.raise_for_status()
return response.text
except Exception as e:
print(f"Erro ao ler URL {self.url}: {str(e)}")
return ""
def process_content(self):
"""Processa o conteúdo web."""
start_time = time.time()
# Definir timeout de 5 minutos
timeout = 300
html = self.read_content()
if not html:
return
self.soup = BeautifulSoup(html, 'html.parser')
# Extrair título
title = self.soup.title.string if self.soup.title else "Sem título"
self.metadata["título"] = title.strip()
# Extrair texto principal
self.content = self._extract_main_content()
# Extrair links importantes
self._extract_links()
# Dividir em secções
self._split_into_sections()
# Verificar timeout
if time.time() - start_time > timeout:
print("Tempo limite excedido. Interrompendo processamento.")
return
def _extract_main_content(self) -> str:
"""
Extrai o conteúdo principal da página.
Returns:
str: Texto principal da página
"""
# Remover elementos indesejados
for elem in self.soup.select('script, style, nav, footer, header, .sidebar, .menu, .ads'):
elem.decompose()
# Tentar encontrar o conteúdo principal
main_content = None
for selector in ['article', 'main', '.content', '.main-content', '#content', '#main']:
main_content = self.soup.select_one(selector)
if main_content:
break
# Se não encontrar conteúdo principal, usar body
if not main_content:
main_content = self.soup.body
# Extrair texto
if main_content:
# Extrair apenas parágrafos e cabeçalhos relevantes
elements = main_content.find_all(['p', 'h1', 'h2', 'h3', 'li'])
texts = [elem.get_text(strip=True) for elem in elements]
texts = [t for t in texts if len(t) > 20] # Filtrar textos muito curtos
# Traduzir em lotes de 5 textos
batch_size = 5
translated_texts = []
for i in range(0, len(texts), batch_size):
batch = texts[i:i + batch_size]
translated_batch = self.translate_batch(batch)
translated_texts.extend(translated_batch)
return '\n\n'.join(translated_texts)
return ""
def _extract_links(self):
"""Extrai links importantes da página."""
main_content = self.soup.select_one('article, main, .content, #content')
if not main_content:
return
for link in main_content.find_all('a', href=True):
href = link.get('href')
text = link.get_text(strip=True)
if href and text and len(text) > 5: # Ignorar links muito curtos
absolute_url = urljoin(self.url, href)
if urlparse(absolute_url).netloc == self.base_domain:
self.links.append({
'text': text,
'url': absolute_url
})
def _split_into_sections(self):
"""Divide o conteúdo em secções baseado em cabeçalhos."""
if not self.content:
return
# Dividir por linhas vazias para encontrar parágrafos
paragraphs = [p.strip() for p in self.content.split('\n\n') if p.strip()]
current_section = {
'title': self.metadata['título'],
'content': '',
'faqs': []
}
for p in paragraphs:
# Se o parágrafo parece um título (curto e termina sem pontuação)
if len(p) < 100 and not p[-1] in '.!?':
if current_section['content']:
self.chapters.append(current_section)
current_section = {
'title': p,
'content': '',
'faqs': []
}
else:
current_section['content'] += p + '\n\n'
if current_section['content']:
self.chapters.append(current_section)