Files
webclone/simple-crawler/wget-cloner.js
T

359 lines
12 KiB
JavaScript

const express = require('express');
const { exec, spawn } = require('child_process');
const path = require('path');
const fs = require('fs');
const axios = require('axios');
const app = express();
app.use(express.json());
class WgetCloner {
constructor() {
this.baseOutputDir = path.join(__dirname, '..', 'cloned-sites');
this.wgetPath = path.join(__dirname, 'wget.exe');
this.ensureDirectoryExists(this.baseOutputDir);
}
ensureDirectoryExists(dirPath) {
if (!fs.existsSync(dirPath)) {
fs.mkdirSync(dirPath, { recursive: true });
}
}
sanitizeFilename(filename) {
return filename.replace(/[<>:"/\\|?*]/g, '_').replace(/\s+/g, '_');
}
async checkWget() {
return new Promise((resolve) => {
exec('where wget', (error, stdout) => {
if (error || !stdout) {
resolve(false);
} else {
resolve(true);
}
});
});
}
async downloadWget() {
console.log('📥 Baixando wget para Windows...');
try {
// URL do wget para Windows (versão portável)
const wgetUrl = 'https://eternallybored.org/misc/wget/1.21.4/64/wget.exe';
console.log('🔗 Conectando ao servidor...');
const response = await axios.get(wgetUrl, {
responseType: 'arraybuffer',
timeout: 120000,
maxContentLength: 50 * 1024 * 1024, // 50MB
onDownloadProgress: (progressEvent) => {
const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total);
process.stdout.write(`\r📦 Baixando: ${percentCompleted}%`);
}
});
console.log('\n💾 Salvando arquivo...');
fs.writeFileSync(this.wgetPath, response.data);
console.log('✅ wget baixado com sucesso!');
return true;
} catch (error) {
console.error('\n❌ Erro ao baixar wget:', error.message);
console.log('💡 Você pode baixar manualmente de: https://eternallybored.org/misc/wget/');
return false;
}
}
async cloneWebsite(url, customFolderName = null) {
console.log(`\n${'='.repeat(60)}`);
console.log(`🚀 CLONAGEM PROFISSIONAL COM WGET`);
console.log(`${'='.repeat(60)}`);
console.log(`🌐 URL: ${url}\n`);
// Sempre usar o wget.exe local primeiro
let wgetCommand = '/bin/wget'; // Linux system wget
// Verificar se o wget.exe local existe
if (false) { // bypass wget.exe check
console.log('⚠️ wget.exe local não encontrado');
// Tentar baixar
const downloaded = await this.downloadWget();
if (!downloaded) {
// Tentar usar wget do sistema como fallback
const hasWget = await this.checkWget();
if (hasWget) {
console.log('✅ Usando wget do sistema');
wgetCommand = 'wget';
} else {
throw new Error('Não foi possível baixar o wget. Instale manualmente: https://eternallybored.org/misc/wget/');
}
}
} else {
console.log('✅ Usando wget.exe local');
}
// Criar diretório para o site
const urlObj = new URL(url);
const siteName = this.sanitizeFilename(urlObj.hostname);
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const finalFolderName = customFolderName ? this.sanitizeFilename(customFolderName) : `${siteName}_${timestamp}`;
const siteDir = path.join(this.baseOutputDir, finalFolderName);
this.ensureDirectoryExists(siteDir);
console.log(`📁 Salvando em: ${siteDir}\n`);
// Argumentos do wget - ABORDAGEM SIMPLES E EFICAZ
const wgetArgs = [
'--recursive', // Baixar recursivamente
'--no-clobber', // Não sobrescrever arquivos
'--page-requisites', // Baixar CSS, JS, imagens
'--html-extension', // Adicionar .html
'--convert-links', // Converter links para locais
'--restrict-file-names=windows', // Nomes compatíveis com Windows
'--domains=' + urlObj.hostname, // Apenas este domínio
'--span-hosts', // Permitir redirecionamentos entre subdomínios/CDN
'--user-agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36"',
'--no-parent', // Não subir diretórios
'--no-check-certificate', // Ignorar SSL
'-e', 'robots=off', // Ignorar robots.txt
`-P`, siteDir, // Diretório de saída
url
];
return new Promise((resolve, reject) => {
console.log('🔄 Iniciando clonagem...\n');
const wgetProcess = spawn(wgetCommand, wgetArgs, {
cwd: this.baseOutputDir,
shell: true
});
let output = '';
let errorOutput = '';
wgetProcess.stdout.on('data', (data) => {
const text = data.toString();
output += text;
// Mostrar progresso
const lines = text.split('\n');
lines.forEach(line => {
if (line.includes('%') || line.includes('saved') || line.includes('=>')) {
console.log(` ${line.trim()}`);
}
});
});
wgetProcess.stderr.on('data', (data) => {
const text = data.toString();
errorOutput += text;
// Mostrar erros importantes
if (text.includes('ERROR') || text.includes('failed')) {
console.log(` ⚠️ ${text.trim()}`);
}
});
wgetProcess.on('close', (code) => {
console.log(`\n${'='.repeat(60)}`);
if (code === 0 || code === 8) { // 8 = alguns arquivos falharam, mas ok
console.log(`✅ CLONAGEM CONCLUÍDA!`);
console.log(`${'='.repeat(60)}`);
console.log(`📁 Localização: ${siteDir}`);
const { execSync } = require('child_process');
// Mover arquivos do subdiretório do domínio para a raiz do siteDir
try {
const domainDir = path.join(siteDir, urlObj.hostname);
if (fs.existsSync(domainDir)) {
execSync(`cp -r "${domainDir}"/* "${siteDir}"/ 2>/dev/null && rm -rf "${domainDir}"`);
}
} catch (e) {}
// Baixar imagens faltantes manualmente
console.log(`\n🔍 Verificando imagens faltantes...`);
this.downloadMissingImages(siteDir, url).then(() => {
console.log(`🌐 Abra o arquivo index.html no navegador`);
console.log(`${'='.repeat(60)}\n`);
});
// countFiles inline helper
var countFiles = function(dir) { var ts=0,fc=0; (function rec(d){if(!fs.existsSync(d))return;fs.readdirSync(d).forEach(function(i){var p=path.join(d,i),s=fs.statSync(p);if(s.isDirectory()&&i.indexOf("cloned-sites")==-1&&i.indexOf(".versions")==-1)rec(p);else if(!s.isDirectory()){ts+=s.size;fc++;}});})(dir);return{fileCount:fc,totalSize:ts};};
// Criar arquivo de informações
const stats = countFiles(siteDir);
const info = {
originalUrl: url,
clonedAt: new Date().toISOString(),
directory: siteDir,
method: 'wget',
success: true,
fileCount: stats.fileCount,
totalSize: stats.totalSize
};
fs.writeFileSync(
path.join(siteDir, 'clone-info.json'),
JSON.stringify(info, null, 2)
);
// Criar arquivo .bat para abrir
const indexPath = this.findIndexFile(siteDir);
if (indexPath) {
fs.writeFileSync(
path.join(siteDir, 'abrir-site.bat'),
`@echo off\nstart "" "${indexPath}"`
);
}
resolve({
url: url,
success: true,
directory: siteDir,
method: 'wget',
exitCode: code,
files: stats.fileCount,
size: stats.totalSize,
cloneInfo: info
});
} else {
console.log(`❌ ERRO NA CLONAGEM (código: ${code})`);
console.log(`${'='.repeat(60)}\n`);
reject(new Error(`wget falhou com código ${code}\n${errorOutput}`));
}
});
wgetProcess.on('error', (error) => {
console.error(`❌ Erro ao executar wget: ${error.message}`);
reject(error);
});
});
}
async downloadMissingImages(siteDir, baseUrl) {
try {
const indexPath = path.join(siteDir, 'index.html');
if (!fs.existsSync(indexPath)) return;
const html = fs.readFileSync(indexPath, 'utf8');
// Procurar por todas as referências de imagens
const imagePatterns = [
/background-image="([^"]+)"/g,
/src="([^"]+\.(jpg|jpeg|png|gif|webp|svg))"/gi,
/href="([^"]+\.(jpg|jpeg|png|gif|webp|svg))"/gi,
];
const imagesToDownload = new Set();
for (const pattern of imagePatterns) {
let match;
while ((match = pattern.exec(html)) !== null) {
const imagePath = match[1];
if (imagePath.startsWith('/ws/') || imagePath.startsWith('ws/')) {
imagesToDownload.add(imagePath.replace(/^\//, ''));
}
}
}
console.log(`📦 Encontradas ${imagesToDownload.size} referências de imagens`);
let downloaded = 0;
for (const imagePath of imagesToDownload) {
const localPath = path.join(siteDir, imagePath);
// Se já existe, pular
if (fs.existsSync(localPath)) continue;
// Criar diretório se não existir
const dir = path.dirname(localPath);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
// Baixar imagem
try {
const urlObj = new URL(baseUrl);
const imageUrl = `${urlObj.protocol}//${urlObj.hostname}/${imagePath}`;
console.log(` 📥 Baixando: ${imagePath}`);
const response = await axios.get(imageUrl, {
responseType: 'arraybuffer',
timeout: 30000,
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
}
});
fs.writeFileSync(localPath, response.data);
downloaded++;
} catch (error) {
console.log(` ⚠️ Falha ao baixar: ${imagePath}`);
}
}
console.log(`✅ ${downloaded} imagens adicionais baixadas`);
} catch (error) {
console.log(`⚠️ Erro ao baixar imagens faltantes: ${error.message}`);
}
}
findIndexFile(directory) {
// Procurar por index.html recursivamente
const findIndex = (dir) => {
const files = fs.readdirSync(dir);
for (const file of files) {
const fullPath = path.join(dir, file);
const stat = fs.statSync(fullPath);
if (stat.isDirectory()) {
const found = findIndex(fullPath);
if (found) return found;
} else if (file === 'index.html') {
return fullPath;
}
}
return null;
};
return findIndex(directory);
}
}
// API
app.post('/wget-clone', async (req, res) => {
try {
const { url, folderName } = req.body;
if (!url) {
return res.status(400).json({ error: 'URL obrigatória' });
}
const cloner = new WgetCloner();
const result = await cloner.cloneWebsite(url, folderName);
res.json({ success: true, data: result });
} catch (error) {
console.error('❌ Erro:', error.message);
res.status(500).json({ success: false, error: error.message });
}
});
app.get('/health', (req, res) => {
res.json({ status: 'ok', service: 'wget-cloner' });
});
app.listen(3002, () => {
console.log('\n' + '='.repeat(60));
console.log('🌐 WGET Cloner - Clonagem Profissional de Sites');
console.log('='.repeat(60));
console.log('📡 Rodando em: http://localhost:3002');
console.log('✅ Clonagem de alta fidelidade com wget');
console.log('='.repeat(60) + '\n');
});