att base de dados
This commit is contained in:
1 parent
ceb7908da4
commit
73693e5da9
14 files changed
+3010
-1
No files matched your search
@@ -0,0 +1,339 @@
|
||||
# benchmark.py
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import multiprocessing
|
||||
import logging
|
||||
import time
|
||||
import platform
|
||||
import sys
|
||||
import re
|
||||
import unicodedata
|
||||
import csv
|
||||
import psutil
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
# Importando apenas o necessário (sem sobrescrever as pastas do utils nativo no processamento global)
|
||||
from file_parser import parse_mmp_file
|
||||
from file_saver import save_to_json, save_to_yaml
|
||||
from utils import SRC_MMPSEARCH, MMP_FOLDER
|
||||
|
||||
# === ISOLAMENTO DE DIRETÓRIOS PARA O BENCHMARK ===
|
||||
# Tudo será gerado dentro de "benchmark_output" para não afetar o Jekyll
|
||||
BENCHMARK_OUT = os.path.join(SRC_MMPSEARCH, "benchmark_output")
|
||||
DATA_FOLDER = os.path.join(BENCHMARK_OUT, "data")
|
||||
METADATA_FOLDER = os.path.join(BENCHMARK_OUT, "metadata")
|
||||
WAV_FOLDER = os.path.join(BENCHMARK_OUT, "wav")
|
||||
MMPZ_FOLDER = os.path.join(BENCHMARK_OUT, "mmpz")
|
||||
LOG_FOLDER = os.path.join(BENCHMARK_OUT, "logs")
|
||||
SAIDA_ANALISES = os.path.join(BENCHMARK_OUT, "analises")
|
||||
LOTES_FOLDER = os.path.join(BENCHMARK_OUT, "lotes_yaml")
|
||||
|
||||
# === CONFIGURAÇÕES GERAIS ===
|
||||
TIMEOUT_RENDER_SECONDS = 300
|
||||
MIN_RAM_FREE_MB = 500
|
||||
|
||||
global_counter = None
|
||||
|
||||
|
||||
def slugify(value):
|
||||
value = str(value)
|
||||
value = (
|
||||
unicodedata.normalize("NFKD", value).encode("ascii", "ignore").decode("ascii")
|
||||
)
|
||||
value = value.lower()
|
||||
value = re.sub(r"[^\w\s-]", "", value)
|
||||
value = re.sub(r"[-\s_]+", "-", value)
|
||||
return value.strip("-_")
|
||||
|
||||
|
||||
def get_cpu_safe_count():
|
||||
try:
|
||||
count = multiprocessing.cpu_count()
|
||||
return max(1, count - 1)
|
||||
except:
|
||||
return 1
|
||||
|
||||
|
||||
def init_worker(counter):
|
||||
global global_counter
|
||||
global_counter = counter
|
||||
|
||||
|
||||
# === FUNÇÃO ORIGINAL DO SEU CÓDIGO (Com leve ajuste de segurança de arquivos) ===
|
||||
def process_single_file(args):
|
||||
file_name, clean_slug, total_files = args
|
||||
file_path = os.path.join(MMP_FOLDER, file_name)
|
||||
pid = os.getpid()
|
||||
|
||||
global global_counter
|
||||
current_idx = 0
|
||||
if global_counter:
|
||||
with global_counter.get_lock():
|
||||
global_counter.value += 1
|
||||
current_idx = global_counter.value
|
||||
|
||||
start_time = time.time()
|
||||
result = {
|
||||
"success": False,
|
||||
"file": file_name,
|
||||
"slug": clean_slug,
|
||||
"data": None,
|
||||
"error": None,
|
||||
"duration_s": 0.0,
|
||||
"ram_mb": 0.0,
|
||||
"file_size_mb": 0.0,
|
||||
}
|
||||
|
||||
try:
|
||||
if os.path.exists(file_path):
|
||||
result["file_size_mb"] = os.path.getsize(file_path) / (1024 * 1024)
|
||||
|
||||
mem = psutil.virtual_memory()
|
||||
if (mem.available / (1024 * 1024)) < MIN_RAM_FREE_MB:
|
||||
result["error"] = "RAM Insuficiente"
|
||||
return result
|
||||
|
||||
original_base_name = os.path.splitext(file_name)[0]
|
||||
ogg_name = clean_slug + ".ogg"
|
||||
json_name = clean_slug + ".json"
|
||||
yml_name = clean_slug + ".yml"
|
||||
target_mmp_path = ""
|
||||
|
||||
if file_name.endswith(".mmpz"):
|
||||
destination_path = os.path.join(MMPZ_FOLDER, file_name)
|
||||
# ALERTA DE BENCHMARK: Trocado move por copy2 para não destruir o dataset de testes!
|
||||
if not os.path.exists(destination_path):
|
||||
shutil.copy2(file_path, destination_path)
|
||||
|
||||
mmp_temp_name = clean_slug + ".mmp"
|
||||
output_mmp_path = os.path.join(MMP_FOLDER, mmp_temp_name)
|
||||
abs_dest = os.path.abspath(destination_path)
|
||||
abs_mmp_out = os.path.abspath(output_mmp_path)
|
||||
|
||||
with open(abs_mmp_out, "w") as outfile:
|
||||
subprocess.run(
|
||||
["lmms", "--dump", abs_dest],
|
||||
stdout=outfile,
|
||||
stderr=subprocess.PIPE,
|
||||
check=True,
|
||||
timeout=60,
|
||||
env={"QT_QPA_PLATFORM": "offscreen", **os.environ},
|
||||
)
|
||||
target_mmp_path = output_mmp_path
|
||||
elif file_name.endswith(".mmp"):
|
||||
target_mmp_path = file_path
|
||||
|
||||
# Renderização do Áudio
|
||||
if os.path.exists(target_mmp_path):
|
||||
abs_target_mmp = os.path.abspath(target_mmp_path)
|
||||
abs_ogg_out = os.path.abspath(os.path.join(WAV_FOLDER, ogg_name))
|
||||
|
||||
# Aqui estava o errinho do "wav_cmd" que arrumamos na revisão!
|
||||
ogg_cmd = ["lmms", "-r", abs_target_mmp, "-o", abs_ogg_out, "-f", "ogg"]
|
||||
render_process = subprocess.run(
|
||||
ogg_cmd,
|
||||
check=False,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=TIMEOUT_RENDER_SECONDS,
|
||||
env={"QT_QPA_PLATFORM": "offscreen", **os.environ},
|
||||
)
|
||||
if render_process.returncode != 0:
|
||||
raise subprocess.CalledProcessError(
|
||||
render_process.returncode, "lmms render", stderr="Erro LMMS"
|
||||
)
|
||||
|
||||
# Parsing do XML
|
||||
if os.path.exists(target_mmp_path):
|
||||
mmp_data = parse_mmp_file(target_mmp_path)
|
||||
if mmp_data:
|
||||
mmp_data["file"] = clean_slug
|
||||
mmp_data["original_title"] = original_base_name
|
||||
save_to_json(mmp_data, os.path.join(METADATA_FOLDER, json_name))
|
||||
save_to_yaml(mmp_data, os.path.join(DATA_FOLDER, yml_name))
|
||||
result["success"] = True
|
||||
result["data"] = mmp_data
|
||||
|
||||
try:
|
||||
process = psutil.Process(pid)
|
||||
# rss = Resident Set Size (RAM real usada pelo processo)
|
||||
result["ram_mb"] = process.memory_info().rss / (1024 * 1024)
|
||||
except:
|
||||
result["ram_mb"] = 0.0
|
||||
|
||||
except Exception as e:
|
||||
result["error"] = str(e)
|
||||
|
||||
result["duration_s"] = time.time() - start_time
|
||||
return result
|
||||
|
||||
|
||||
# === MOTOR DE BENCHMARK ===
|
||||
def execute_batch(limit_files, mode):
|
||||
"""
|
||||
Executa um lote de arquivos.
|
||||
Se mode == 'sequencial', usa apenas 1 núcleo.
|
||||
Se mode == 'paralelo', usa o safe count de núcleos.
|
||||
"""
|
||||
start_time_global = time.time()
|
||||
|
||||
# Prepara pastas limpas para o teste
|
||||
for folder in [
|
||||
MMPZ_FOLDER,
|
||||
WAV_FOLDER,
|
||||
METADATA_FOLDER,
|
||||
DATA_FOLDER,
|
||||
LOTES_FOLDER,
|
||||
SAIDA_ANALISES,
|
||||
]:
|
||||
os.makedirs(folder, exist_ok=True)
|
||||
|
||||
all_files_raw = [f for f in os.listdir(MMP_FOLDER) if f.endswith((".mmp", ".mmpz"))]
|
||||
if limit_files > 0:
|
||||
all_files_raw = all_files_raw[:limit_files]
|
||||
|
||||
total_files = len(all_files_raw)
|
||||
if total_files == 0:
|
||||
return 0, 0, 0, 0, 0
|
||||
|
||||
shared_counter = multiprocessing.Value("i", 0)
|
||||
tasks = []
|
||||
|
||||
for idx, file_name in enumerate(all_files_raw):
|
||||
slug = slugify(os.path.splitext(file_name)[0])
|
||||
if not slug:
|
||||
slug = f"proj-{idx}"
|
||||
tasks.append((file_name, slug, total_files))
|
||||
|
||||
num_cores = 1 if mode == "sequencial" else get_cpu_safe_count()
|
||||
|
||||
print(
|
||||
f"\n[{mode.upper()}] Processando {total_files} arquivos com {num_cores} núcleo(s)..."
|
||||
)
|
||||
|
||||
# Execução
|
||||
with multiprocessing.Pool(
|
||||
processes=num_cores, initializer=init_worker, initargs=(shared_counter,)
|
||||
) as pool:
|
||||
results = pool.map(process_single_file, tasks)
|
||||
|
||||
# --- CÁLCULO DE MÉDIAS E SUCESSOS ---
|
||||
sucessos = [r for r in results if r["success"]]
|
||||
qtd_sucessos = len(sucessos)
|
||||
|
||||
avg_time = (
|
||||
sum(r["duration_s"] for r in sucessos) / qtd_sucessos if qtd_sucessos > 0 else 0
|
||||
)
|
||||
avg_size = (
|
||||
sum(r["file_size_mb"] for r in sucessos) / qtd_sucessos
|
||||
if qtd_sucessos > 0
|
||||
else 0
|
||||
)
|
||||
avg_ram = (
|
||||
sum(r["ram_mb"] for r in sucessos) / qtd_sucessos if qtd_sucessos > 0 else 0
|
||||
)
|
||||
|
||||
duration_total = time.time() - start_time_global
|
||||
|
||||
# --- SALVAR RELATÓRIO DETALHADO POR LOTE ---
|
||||
detalhado_csv = os.path.join(
|
||||
BENCHMARK_OUT, f"auditoria_detalhada_{mode}_{limit_files}.csv"
|
||||
)
|
||||
with open(detalhado_csv, "w", newline="", encoding="utf-8") as f:
|
||||
writer = csv.writer(f)
|
||||
writer.writerow(
|
||||
["Arquivo", "Tamanho_MB", "Tempo_s", "RAM_MB", "Status", "Erro"]
|
||||
)
|
||||
for r in results:
|
||||
status_str = "SUCESSO" if r["success"] else "FALHA"
|
||||
writer.writerow(
|
||||
[
|
||||
r["file"],
|
||||
f"{r['file_size_mb']:.2f}",
|
||||
f"{r['duration_s']:.2f}",
|
||||
f"{r['ram_mb']:.2f}",
|
||||
status_str,
|
||||
r["error"] or "",
|
||||
]
|
||||
)
|
||||
|
||||
print(
|
||||
f"[{mode.upper()}] Concluído em {duration_total:.2f}s (Sucessos: {qtd_sucessos}/{total_files})"
|
||||
)
|
||||
print(
|
||||
f"[{mode.upper()}] Médias -> Tempo: {avg_time:.2f}s | Tamanho: {avg_size:.2f}MB | RAM: {avg_ram:.2f}MB"
|
||||
)
|
||||
|
||||
return duration_total, qtd_sucessos, avg_time, avg_size, avg_ram
|
||||
|
||||
|
||||
def run_benchmark():
|
||||
print("==================================================")
|
||||
print(" INICIANDO TESTE DE SPEEDUP (SEQUENCIAL VS PARALELO)")
|
||||
print("==================================================")
|
||||
|
||||
test_sizes = [1, 10, 100, 1000]
|
||||
modes = ["sequencial", "paralelo"]
|
||||
|
||||
results_table = []
|
||||
|
||||
for size in test_sizes:
|
||||
print(f"\n>>> INICIANDO BATERIA PARA {size} ARQUIVO(S) <<<")
|
||||
row = {"Tamanho": size}
|
||||
|
||||
for mode in modes:
|
||||
duration, success_count, avg_time, avg_size, avg_ram = execute_batch(
|
||||
size, mode
|
||||
)
|
||||
row[f"Tempo_Total_{mode}_(s)"] = round(duration, 2)
|
||||
row[f"Tempo_Medio_Proj_{mode}_(s)"] = round(avg_time, 2)
|
||||
row[f"Tamanho_Medio_Proj_(MB)"] = round(
|
||||
avg_size, 2
|
||||
) # O tamanho é igual para ambos, mas guardamos
|
||||
row[f"RAM_Media_{mode}_(MB)"] = round(avg_ram, 2)
|
||||
row[f"Sucesso_{mode}"] = success_count
|
||||
|
||||
# Calcula o Speedup (Tempo Total Sequencial / Tempo Total Paralelo)
|
||||
speedup = (
|
||||
row["Tempo_Total_sequencial_(s)"] / row["Tempo_Total_paralelo_(s)"]
|
||||
if row["Tempo_Total_paralelo_(s)"] > 0
|
||||
else 0
|
||||
)
|
||||
row["Speedup"] = round(speedup, 2)
|
||||
results_table.append(row)
|
||||
|
||||
print(f"--- Fim da bateria de {size}. Speedup Alcançado: {speedup:.2f}x ---")
|
||||
|
||||
# Salva o resultado final num CSV consolidado
|
||||
os.makedirs(BENCHMARK_OUT, exist_ok=True)
|
||||
csv_file = os.path.join(BENCHMARK_OUT, "resultado_grafico_speedup.csv")
|
||||
|
||||
# Lista de colunas para o CSV final
|
||||
colunas = [
|
||||
"Tamanho",
|
||||
"Speedup",
|
||||
"Tempo_Total_sequencial_(s)",
|
||||
"Tempo_Total_paralelo_(s)",
|
||||
"Tempo_Medio_Proj_sequencial_(s)",
|
||||
"Tempo_Medio_Proj_paralelo_(s)",
|
||||
"RAM_Media_sequencial_(MB)",
|
||||
"RAM_Media_paralelo_(MB)",
|
||||
"Tamanho_Medio_Proj_(MB)",
|
||||
"Sucesso_sequencial",
|
||||
"Sucesso_paralelo",
|
||||
]
|
||||
|
||||
with open(csv_file, "w", newline="", encoding="utf-8") as f:
|
||||
writer = csv.DictWriter(f, fieldnames=colunas)
|
||||
writer.writeheader()
|
||||
writer.writerows(results_table)
|
||||
|
||||
print("\n==================================================")
|
||||
print(f"BENCHMARK FINALIZADO! Resultados salvos em:\n{BENCHMARK_OUT}")
|
||||
print("==================================================")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
run_benchmark()
|
||||
@@ -0,0 +1,53 @@
|
||||
nohup: ignoring input
|
||||
==================================================
|
||||
INICIANDO TESTE DE SPEEDUP (SEQUENCIAL VS PARALELO)
|
||||
==================================================
|
||||
|
||||
>>> INICIANDO BATERIA PARA 1 ARQUIVO(S) <<<
|
||||
|
||||
[SEQUENCIAL] Processando 1 arquivos com 1 núcleo(s)...
|
||||
[SEQUENCIAL] Concluído em 43.78s (Sucessos: 1/1)
|
||||
[SEQUENCIAL] Médias -> Tempo: 43.69s | Tamanho: 0.54MB | RAM: 27.22MB
|
||||
|
||||
[PARALELO] Processando 1 arquivos com 11 núcleo(s)...
|
||||
[PARALELO] Concluído em 46.51s (Sucessos: 1/1)
|
||||
[PARALELO] Médias -> Tempo: 46.48s | Tamanho: 0.54MB | RAM: 27.58MB
|
||||
--- Fim da bateria de 1. Speedup Alcançado: 0.94x ---
|
||||
|
||||
>>> INICIANDO BATERIA PARA 10 ARQUIVO(S) <<<
|
||||
|
||||
[SEQUENCIAL] Processando 10 arquivos com 1 núcleo(s)...
|
||||
Process ForkPoolWorker-13:
|
||||
Traceback (most recent call last):
|
||||
File "/usr/lib/python3.12/multiprocessing/pool.py", line 131, in worker
|
||||
put((job, i, result))
|
||||
File "/usr/lib/python3.12/multiprocessing/queues.py", line 399, in put
|
||||
self._writer.send_bytes(obj)
|
||||
File "/usr/lib/python3.12/multiprocessing/connection.py", line 200, in send_bytes
|
||||
self._send_bytes(m[offset:offset + size])
|
||||
File "/usr/lib/python3.12/multiprocessing/connection.py", line 420, in _send_bytes
|
||||
self._send(header)
|
||||
File "/usr/lib/python3.12/multiprocessing/connection.py", line 384, in _send
|
||||
n = write(self._handle, buf)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
BrokenPipeError: [Errno 32] Broken pipe
|
||||
|
||||
During handling of the above exception, another exception occurred:
|
||||
|
||||
Traceback (most recent call last):
|
||||
File "/usr/lib/python3.12/multiprocessing/process.py", line 314, in _bootstrap
|
||||
self.run()
|
||||
File "/usr/lib/python3.12/multiprocessing/process.py", line 108, in run
|
||||
self._target(*self._args, **self._kwargs)
|
||||
File "/usr/lib/python3.12/multiprocessing/pool.py", line 136, in worker
|
||||
put((job, i, (False, wrapped)))
|
||||
File "/usr/lib/python3.12/multiprocessing/queues.py", line 399, in put
|
||||
self._writer.send_bytes(obj)
|
||||
File "/usr/lib/python3.12/multiprocessing/connection.py", line 200, in send_bytes
|
||||
self._send_bytes(m[offset:offset + size])
|
||||
File "/usr/lib/python3.12/multiprocessing/connection.py", line 420, in _send_bytes
|
||||
self._send(header)
|
||||
File "/usr/lib/python3.12/multiprocessing/connection.py", line 384, in _send
|
||||
n = write(self._handle, buf)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
BrokenPipeError: [Errno 32] Broken pipe
|
||||
@@ -0,0 +1,230 @@
|
||||
import os
|
||||
import time
|
||||
import threading
|
||||
import requests
|
||||
import csv
|
||||
import psutil
|
||||
import re
|
||||
import unicodedata
|
||||
from datetime import datetime, timedelta
|
||||
from bs4 import BeautifulSoup
|
||||
from urllib.parse import urljoin, urlparse, parse_qs
|
||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||
|
||||
from utils import (
|
||||
SRC_MMPSEARCH,
|
||||
MMP_FOLDER,
|
||||
)
|
||||
|
||||
# ================= CONFIGURAÇÕES PADRONIZADAS =================
|
||||
BASE_DIR = SRC_MMPSEARCH
|
||||
PASTA_DESTINO = MMP_FOLDER # Pasta de entrada do pipeline
|
||||
LOG_DIR = os.path.join(BASE_DIR, "logs/crawler")
|
||||
ARQUIVO_AUDITORIA = os.path.join(LOG_DIR, "audit_crawler.csv")
|
||||
|
||||
# Segurança de Hardware
|
||||
MIN_RAM_FREE_MB = 500 # Pausa se tiver menos que 500MB livre
|
||||
MAX_WORKERS_SAFE = 4 # I/O Bound não precisa de tantos workers quanto CPU, mas 4 é seguro
|
||||
|
||||
HEADERS = {
|
||||
'User-Agent': 'Mozilla/5.0 (Compatible; MMPResearchBot/1.0; +http://seu-site.com)'
|
||||
}
|
||||
|
||||
# Cria pastas
|
||||
for p in [PASTA_DESTINO, LOG_DIR]:
|
||||
os.makedirs(p, exist_ok=True)
|
||||
|
||||
# ================= FUNÇÕES AUXILIARES =================
|
||||
|
||||
def slugify(value):
|
||||
"""Sanitização robusta de nomes de arquivo (Padrão do Pipeline)"""
|
||||
value = str(value)
|
||||
value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore').decode('ascii')
|
||||
value = value.lower()
|
||||
value = re.sub(r'[^\w\s-]', '', value)
|
||||
value = re.sub(r'[-\s_]+', '-', value)
|
||||
return value.strip('-_')
|
||||
|
||||
def verificar_memoria_segura():
|
||||
"""Retorna True se é seguro continuar"""
|
||||
try:
|
||||
mem = psutil.virtual_memory()
|
||||
return (mem.available / (1024 * 1024)) > MIN_RAM_FREE_MB
|
||||
except:
|
||||
return True
|
||||
|
||||
# ================= CLASSE DE AUDITORIA =================
|
||||
class AuditoriaCrawler:
|
||||
def __init__(self, arquivo_csv):
|
||||
self.arquivo_csv = arquivo_csv
|
||||
self.inicio_global = time.time()
|
||||
self.lock = threading.Lock()
|
||||
|
||||
# Cria CSV se não existir
|
||||
if not os.path.exists(self.arquivo_csv):
|
||||
with open(self.arquivo_csv, 'w', newline='', encoding='utf-8') as f:
|
||||
writer = csv.writer(f)
|
||||
writer.writerow([
|
||||
"Timestamp", "Nome_Original", "Slug_Final",
|
||||
"Tamanho_MB", "Tempo_DL_s", "Velocidade_KBps",
|
||||
"Status", "URL_Origem"
|
||||
])
|
||||
|
||||
def registrar_download(self, nome_orig, slug, tamanho_bytes, tempo_s, url, status="SUCESSO"):
|
||||
with self.lock:
|
||||
try:
|
||||
tamanho_mb = tamanho_bytes / (1024 * 1024)
|
||||
velocidade = (tamanho_bytes / 1024) / tempo_s if tempo_s > 0 else 0
|
||||
ts = datetime.now().strftime("%H:%M:%S")
|
||||
|
||||
with open(self.arquivo_csv, 'a', newline='', encoding='utf-8') as f:
|
||||
writer = csv.writer(f)
|
||||
writer.writerow([
|
||||
ts, nome_orig, slug,
|
||||
f"{tamanho_mb:.2f}", f"{tempo_s:.2f}",
|
||||
f"{velocidade:.2f}", status, url
|
||||
])
|
||||
|
||||
# Log Console Simplificado
|
||||
print(f"[{ts}] {status}: {slug} ({tamanho_mb:.2f}MB @ {velocidade:.0f}KB/s)")
|
||||
except Exception as e:
|
||||
print(f"Erro ao auditar: {e}")
|
||||
|
||||
# ================= WORKER =================
|
||||
|
||||
def baixar_projeto_worker(args):
|
||||
url_detalhes, auditor = args
|
||||
|
||||
# Check de Segurança (Throttle)
|
||||
if not verificar_memoria_segura():
|
||||
time.sleep(5) # Espera RAM liberar
|
||||
if not verificar_memoria_segura():
|
||||
return (False, "RAM Crítica - Download Abortado")
|
||||
|
||||
try:
|
||||
# 1. Pega página de detalhes
|
||||
resp = requests.get(url_detalhes, headers=HEADERS, timeout=15)
|
||||
if resp.status_code != 200:
|
||||
auditor.registrar_download("N/A", "N/A", 0, 0, url_detalhes, f"HTTP_{resp.status_code}")
|
||||
return (False, f"HTTP {resp.status_code}")
|
||||
|
||||
soup = BeautifulSoup(resp.text, 'html.parser')
|
||||
|
||||
# Lógica de encontrar link de download (Manteve a sua, que é boa)
|
||||
link_tag = soup.find('a', href=lambda h: h and 'download_file.php' in h)
|
||||
if not link_tag:
|
||||
link_tag = soup.find('a', string='Download')
|
||||
|
||||
if link_tag:
|
||||
url_download = urljoin(url_detalhes, link_tag['href'])
|
||||
|
||||
inicio_dl = time.time()
|
||||
|
||||
# Request do Arquivo
|
||||
with requests.get(url_download, headers=HEADERS, stream=True, timeout=60) as r_file:
|
||||
r_file.raise_for_status()
|
||||
|
||||
# --- DESCIFRAR NOME (COM SANITIZAÇÃO) ---
|
||||
nome_bruto = ""
|
||||
if "Content-Disposition" in r_file.headers:
|
||||
cd = r_file.headers["Content-Disposition"]
|
||||
if "filename=" in cd:
|
||||
nome_bruto = cd.split("filename=")[1].strip('"')
|
||||
|
||||
if not nome_bruto:
|
||||
query = parse_qs(urlparse(url_download).query)
|
||||
nome_bruto = query['name'][0] if 'name' in query else f"proj_{int(time.time())}.mmpz"
|
||||
|
||||
# Aplica Slugify (Padrão do Pipeline)
|
||||
nome_base, ext = os.path.splitext(nome_bruto)
|
||||
if not ext: ext = ".mmpz" # Default seguro
|
||||
slug = slugify(nome_base)
|
||||
nome_final = f"{slug}{ext}"
|
||||
|
||||
caminho_final = os.path.join(PASTA_DESTINO, nome_final)
|
||||
|
||||
# Skip se já existe
|
||||
if os.path.exists(caminho_final):
|
||||
auditor.registrar_download(nome_bruto, slug, os.path.getsize(caminho_final), 0, url_detalhes, "SKIPPED_EXISTS")
|
||||
return (True, "Já existe")
|
||||
|
||||
# Gravação em chunks (Seguro para RAM)
|
||||
bytes_downloaded = 0
|
||||
with open(caminho_final, 'wb') as f:
|
||||
for chunk in r_file.iter_content(chunk_size=8192):
|
||||
if chunk:
|
||||
f.write(chunk)
|
||||
bytes_downloaded += len(chunk)
|
||||
|
||||
tempo_total = time.time() - inicio_dl
|
||||
|
||||
# Auditoria de Sucesso
|
||||
auditor.registrar_download(nome_bruto, slug, bytes_downloaded, tempo_total, url_detalhes, "SUCESSO")
|
||||
return (True, "OK")
|
||||
|
||||
else:
|
||||
auditor.registrar_download("N/A", "N/A", 0, 0, url_detalhes, "LINK_NOT_FOUND")
|
||||
return (False, "Link não encontrado")
|
||||
|
||||
except Exception as e:
|
||||
auditor.registrar_download("Erro", "Erro", 0, 0, url_detalhes, f"EXCEPT_{str(e)[:20]}")
|
||||
return (False, str(e))
|
||||
|
||||
# ================= MAIN =================
|
||||
|
||||
def crawler_manager(url_base, max_paginas=2):
|
||||
print(f"\n=== INICIANDO CRAWLER PADRONIZADO ===")
|
||||
print(f"Destino: {PASTA_DESTINO}")
|
||||
print(f"Auditoria: {ARQUIVO_AUDITORIA}")
|
||||
|
||||
auditor = AuditoriaCrawler(ARQUIVO_AUDITORIA)
|
||||
pagina_atual = 1
|
||||
total_sucesso = 0
|
||||
|
||||
# Executor seguro
|
||||
with ThreadPoolExecutor(max_workers=MAX_WORKERS_SAFE) as executor:
|
||||
while pagina_atual <= max_paginas:
|
||||
print(f"\n--- Varrendo Página {pagina_atual} ---")
|
||||
|
||||
operador = '&' if '?' in url_base else '?'
|
||||
url_pag = f"{url_base}{operador}page={pagina_atual}"
|
||||
|
||||
try:
|
||||
# Pega a lista de projetos da página
|
||||
r = requests.get(url_pag, headers=HEADERS)
|
||||
soup = BeautifulSoup(r.text, 'html.parser')
|
||||
# Filtro específico do LMMS Sharing Platform
|
||||
links = soup.find_all('a', href=lambda h: h and '?action=show&file=' in h)
|
||||
urls_unicas = sorted(list(set([l['href'] for l in links])))
|
||||
|
||||
if not urls_unicas:
|
||||
print("Nenhum projeto encontrado nesta página. Encerrando.")
|
||||
break
|
||||
|
||||
# Submete tarefas
|
||||
futures = []
|
||||
for u in urls_unicas:
|
||||
url_completa = urljoin(url_base, u)
|
||||
futures.append(executor.submit(baixar_projeto_worker, (url_completa, auditor)))
|
||||
|
||||
# Processa resultados conforme chegam
|
||||
for future in as_completed(futures):
|
||||
ok, msg = future.result()
|
||||
if ok: total_sucesso += 1
|
||||
|
||||
except Exception as e:
|
||||
print(f"Erro na paginação: {e}")
|
||||
|
||||
pagina_atual += 1
|
||||
time.sleep(1) # Respeito ao servidor
|
||||
|
||||
tempo_total = time.time() - auditor.inicio_global
|
||||
print(f"\n=== CRAWLER FINALIZADO ===")
|
||||
print(f"Tempo: {timedelta(seconds=int(tempo_total))}")
|
||||
print(f"Downloads com Sucesso: {total_sucesso}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
# URL de exemplo (Projetos ordenados por data)
|
||||
URL_ALVO = 'https://lmms.io/lsp/?action=browse&category=Projects&sort=date'
|
||||
|
||||
crawler_manager(URL_ALVO, max_paginas=499)
|
||||
Reference in new issue
Block a user