214 lines
7.3 KiB
Python
Executable File
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)
|