UNPKG

wic-web

Version:

Advanced web scraper for downloading complete website themes with WordPress support and folder hierarchy

497 lines (413 loc) 19.6 kB
const axios = require('axios'); const cheerio = require('cheerio'); const fs = require('fs-extra'); const path = require('path'); const url = require('url'); // İndirmek istediğiniz template URL'leri const templateUrls = [ 'https://seyfooksck.dev/', // Ana sayfa ]; // Konfigürasyon const config = { // WordPress benzeri yapıları destekle (uzantısız URL'ler) supportWordPressStructure: true, // İndirme derinliği (kaç seviye link takip edilecek) maxDepth: 2, // Timeout süreleri requestTimeout: 30000, // Hangi dosya türlerinin indirileceği fileExtensions: { images: ['.jpg', '.jpeg', '.png', '.gif', '.svg', '.webp', '.ico', '.bmp'], styles: ['.css'], scripts: ['.js'], fonts: ['.woff', '.woff2', '.ttf', '.eot', '.otf'] } }; // Dosya indirme fonksiyonu async function downloadFile(fileUrl, filePath, cookieHeader = null) { try { const options = { method: 'get', url: fileUrl, responseType: 'stream', timeout: config.requestTimeout }; // Cookie varsa header'a ekle if (cookieHeader) { options.headers = { 'Cookie': cookieHeader }; } const response = await axios(options); await fs.ensureDir(path.dirname(filePath)); const writer = fs.createWriteStream(filePath); response.data.pipe(writer); return new Promise((resolve, reject) => { writer.on('finish', resolve); writer.on('error', reject); }); } catch (error) { console.log(`⚠️ ${fileUrl} indirilemedi: ${error.message}`); } } // Dosya yolunu orijinal yapısını koruyarak düzenle function getLocalFilePath(originalPath, outputDir) { // ./ ile başlayan yolları temizle let cleanPath = originalPath.replace(/^\.\//, ''); // Dosya yolunu output directory ile birleştir return path.join(outputDir, cleanPath); } // Relatif yolu orijinal yapıda koru function getRelativePathForHtml(originalPath) { // ./ ile başlayan yolları koru if (originalPath.startsWith('./')) { return originalPath; } // Başka bir şekilde relative ise ./ ekle if (!originalPath.startsWith('/') && !originalPath.startsWith('http')) { return './' + originalPath; } return originalPath; } // CSS dosyalarındaki URL'leri işleme async function processCssUrls(cssContent, cssUrl, outputDir, cookieHeader = null, downloadedFiles = new Set()) { const baseUrl = cssUrl.substring(0, cssUrl.lastIndexOf('/')); const urlRegex = /url\(['"]?([^'")\s]+)['"]?\)/gi; let match; while ((match = urlRegex.exec(cssContent)) !== null) { const resourceUrl = match[1]; if (!resourceUrl.startsWith('http') && !resourceUrl.startsWith('data:')) { const fullUrl = url.resolve(baseUrl + '/', resourceUrl); // Dosya daha önce indirildi mi kontrol et if (!downloadedFiles.has(fullUrl)) { downloadedFiles.add(fullUrl); const localPath = getLocalFilePath(resourceUrl, outputDir); await downloadFile(fullUrl, localPath, cookieHeader); console.log(`📄 CSS resource: ${resourceUrl}`); } } } return cssContent; } // HTML sayfalarındaki linkleri bulma function findHtmlLinks($, baseUrl) { const htmlLinks = new Set(); // Menü linklerini bul $('a[href]').each((i, elem) => { const href = $(elem).attr('href'); if (href && !href.startsWith('http') && !href.startsWith('//') && !href.startsWith('#') && !href.startsWith('mailto:') && !href.startsWith('tel:')) { // WordPress yapısını destekle if (config.supportWordPressStructure) { // .html ile biten veya uzantısız linkler if (href.endsWith('.html') || (!href.includes('.') && href !== '/' && href !== '')) { const fullUrl = url.resolve(baseUrl + '/', href); htmlLinks.add(fullUrl); } } else { // Sadece .html ile biten linkler if (href.endsWith('.html')) { const fullUrl = url.resolve(baseUrl + '/', href); htmlLinks.add(fullUrl); } } } }); // Navigation linklerini bul $('nav a[href], .navbar a[href], .menu a[href], .navigation a[href]').each((i, elem) => { const href = $(elem).attr('href'); if (href && !href.startsWith('http') && !href.startsWith('//') && !href.startsWith('#') && !href.startsWith('mailto:') && !href.startsWith('tel:')) { if (config.supportWordPressStructure) { if (href.endsWith('.html') || (!href.includes('.') && href !== '/' && href !== '')) { const fullUrl = url.resolve(baseUrl + '/', href); htmlLinks.add(fullUrl); } } else { if (href.endsWith('.html')) { const fullUrl = url.resolve(baseUrl + '/', href); htmlLinks.add(fullUrl); } } } }); return Array.from(htmlLinks); } // URL'nin dosya mı sayfa mı olduğunu kontrol et function isPageUrl(urlString) { const parsedUrl = url.parse(urlString); const pathname = parsedUrl.pathname; // .html ile bitiyorsa kesinlikle sayfa if (pathname.endsWith('.html')) { return true; } // WordPress yapısı destekleniyorsa ve uzantı yoksa sayfa olarak kabul et if (config.supportWordPressStructure) { const hasExtension = path.extname(pathname) !== ''; return !hasExtension && pathname !== '/' && pathname !== ''; } return false; } // Sayfa dosya adını ve klasör yapısını belirleme function getPageFileName(urlString) { const parsedUrl = url.parse(urlString); let pathname = parsedUrl.pathname; // Son / karakterini kaldır if (pathname.endsWith('/')) { pathname = pathname.slice(0, -1); } // .html ile bitiyorsa if (pathname.endsWith('.html')) { // Klasör yapısını koru: admin/user/page.html -> admin/user/page.html const result = pathname.startsWith('/') ? pathname.substring(1) : pathname; return result; } // WordPress yapısı için if (config.supportWordPressStructure && pathname) { const segments = pathname.split('/').filter(s => s); if (segments.length === 0) { return 'index.html'; } // Klasör yapısını koru: /admin/user -> admin/user.html if (segments.length > 1) { const folderPath = segments.slice(0, -1).join('/'); const fileName = segments[segments.length - 1]; const result = `${folderPath}/${fileName}.html`; return result; } else { // Tek segment: /user -> user.html const result = `${segments[0]}.html`; return result; } } return 'index.html'; } // Cookie string'ini oluştur function createCookieHeader(cookie) { if (!cookie) return null; if (typeof cookie === 'string') { return cookie; } if (typeof cookie === 'object' && cookie.name && cookie.value) { return `${cookie.name}=${cookie.value}`; } return null; } async function downloadTemplate(templateUrl, index, options = {}, cookie = null) { const templateName = `template-${index + 1}`; // Output directory'yi belirle (path artık zorunlu) const outputDir = path.resolve(options.path, templateName); console.log(`📦 ${templateName} indiriliyor...`); // Cookie header'ını hazırla const cookieHeader = createCookieHeader(cookie); if (cookieHeader) { console.log(`🍪 Cookie kullanılıyor: ${cookieHeader.substring(0, 50)}...`); } try { // Output klasörünü temizle ve oluştur await fs.remove(outputDir); await fs.ensureDir(outputDir); // İndirilen sayfaları ve dosyaları takip et const downloadedPages = new Set(); const downloadedFiles = new Set(); // Dosya tekrarını önlemek için const pagesToDownload = [templateUrl]; // Base URL'i al const baseUrl = templateUrl.substring(0, templateUrl.lastIndexOf('/')); while (pagesToDownload.length > 0) { const currentUrl = pagesToDownload.shift(); if (downloadedPages.has(currentUrl)) { continue; } downloadedPages.add(currentUrl); console.log(`📄 Sayfa indiriliyor: ${path.basename(currentUrl)}`); // HTML dosyasını indir let response; try { const requestOptions = { timeout: config.requestTimeout }; // Cookie varsa header'a ekle if (cookieHeader) { requestOptions.headers = { 'Cookie': cookieHeader }; } response = await axios.get(currentUrl, requestOptions); } catch (error) { console.log(`⚠️ ${currentUrl} indirilemedi: ${error.message}`); continue; } const html = response.data; const $ = cheerio.load(html); // Bu sayfadaki diğer HTML linklerini bul const htmlLinks = findHtmlLinks($, baseUrl); htmlLinks.forEach(link => { if (!downloadedPages.has(link) && !pagesToDownload.includes(link)) { pagesToDownload.push(link); } }); // CSS dosyalarını indir const cssPromises = []; $('link[rel="stylesheet"]').each((i, elem) => { const href = $(elem).attr('href'); if (href && !href.startsWith('http') && !href.startsWith('//')) { const fullUrl = url.resolve(baseUrl + '/', href); // Dosya daha önce indirildi mi kontrol et if (!downloadedFiles.has(fullUrl)) { downloadedFiles.add(fullUrl); const localPath = getLocalFilePath(href, outputDir); cssPromises.push( downloadFile(fullUrl, localPath, cookieHeader).then(async () => { console.log(`📄 CSS: ${href}`); // CSS içindeki kaynakları da indir try { const cssRequestOptions = { timeout: config.requestTimeout }; if (cookieHeader) { cssRequestOptions.headers = { 'Cookie': cookieHeader }; } const cssResponse = await axios.get(fullUrl, cssRequestOptions); await processCssUrls(cssResponse.data, fullUrl, outputDir, cookieHeader, downloadedFiles); } catch (error) { console.log(`⚠️ CSS içeriği işlenemedi: ${href}`); } }) ); } // HTML'deki href'i orijinal yapıda bırak $(elem).attr('href', getRelativePathForHtml(href)); } }); // JavaScript dosyalarını indir const jsPromises = []; $('script[src]').each((i, elem) => { const src = $(elem).attr('src'); if (src && !src.startsWith('http') && !src.startsWith('//')) { const fullUrl = url.resolve(baseUrl + '/', src); // Dosya daha önce indirildi mi kontrol et if (!downloadedFiles.has(fullUrl)) { downloadedFiles.add(fullUrl); const localPath = getLocalFilePath(src, outputDir); jsPromises.push( downloadFile(fullUrl, localPath, cookieHeader).then(() => { console.log(`📄 JS: ${src}`); }) ); } // HTML'deki src'yi orijinal yapıda bırak $(elem).attr('src', getRelativePathForHtml(src)); } }); // Resimleri indir const imgPromises = []; $('img').each((i, elem) => { const src = $(elem).attr('src'); if (src && !src.startsWith('http') && !src.startsWith('//') && !src.startsWith('data:')) { const fullUrl = url.resolve(baseUrl + '/', src); // Dosya daha önce indirildi mi kontrol et if (!downloadedFiles.has(fullUrl)) { downloadedFiles.add(fullUrl); const localPath = getLocalFilePath(src, outputDir); imgPromises.push( downloadFile(fullUrl, localPath, cookieHeader).then(() => { console.log(`📄 IMG: ${src}`); }) ); } // HTML'deki src'yi orijinal yapıda bırak $(elem).attr('src', getRelativePathForHtml(src)); } }); // Background image'ları indir $('[style*="background-image"]').each((i, elem) => { const style = $(elem).attr('style'); if (style) { const bgMatch = style.match(/background-image:\s*url\(['"]?([^'")\s]+)['"]?\)/); if (bgMatch && bgMatch[1] && !bgMatch[1].startsWith('http') && !bgMatch[1].startsWith('data:')) { const bgUrl = bgMatch[1]; const fullUrl = url.resolve(baseUrl + '/', bgUrl); // Dosya daha önce indirildi mi kontrol et if (!downloadedFiles.has(fullUrl)) { downloadedFiles.add(fullUrl); const localPath = getLocalFilePath(bgUrl, outputDir); imgPromises.push( downloadFile(fullUrl, localPath, cookieHeader).then(() => { console.log(`📄 BG: ${bgUrl}`); }) ); } // Style'daki URL'yi orijinal yapıda bırak const newStyle = style.replace(bgMatch[1], getRelativePathForHtml(bgUrl)); $(elem).attr('style', newStyle); } } }); // HTML linklerini yerel yollara dönüştür $('a[href]').each((i, elem) => { const href = $(elem).attr('href'); if (href && !href.startsWith('http') && !href.startsWith('//') && !href.startsWith('#') && !href.startsWith('mailto:') && !href.startsWith('tel:')) { if (isPageUrl(url.resolve(baseUrl + '/', href))) { const targetUrl = url.resolve(baseUrl + '/', href); const fileName = getPageFileName(targetUrl); // Klasör yapısını koruyarak relatif yol oluştur $(elem).attr('href', fileName); } } }); // Tüm indirmeleri bekle await Promise.all([...cssPromises, ...jsPromises, ...imgPromises]); // Güncellenmiş HTML'i kaydet const updatedHtml = $.html(); const pageFileName = getPageFileName(currentUrl); const fullPagePath = path.join(outputDir, pageFileName); // Klasör yapısını oluştur await fs.ensureDir(path.dirname(fullPagePath)); await fs.writeFile(fullPagePath, updatedHtml); } console.log(`✅ ${templateName} başarıyla indirildi:`); console.log(` 📄 ${downloadedPages.size} sayfa indirildi`); console.log(` 📁 ${downloadedFiles.size} dosya indirildi`); console.log(` 📂 Konum: ${outputDir}`); } catch (error) { console.error(`❌ ${templateName} indirme hatası:`, error.message); } } async function downloadAllTemplates(urls = templateUrls, options = {}, cookie = null) { // Path zorunlu kontrol if (!options.path) { throw new Error('options.path zorunludur! Örnek: { path: "./theme" }'); } // Konfigürasyonu güncelle const finalConfig = { ...config, ...options }; Object.assign(config, finalConfig); console.log('🚀 Tema indirme işlemi başlatılıyor...\n'); console.log(`📋 Konfigürasyon:`); console.log(` WordPress yapısı: ${config.supportWordPressStructure ? 'Aktif' : 'Pasif'}`); console.log(` Maksimum derinlik: ${config.maxDepth}`); console.log(` Request timeout: ${config.requestTimeout}ms`); console.log(` Output klasörü: ${options.path}`); if (cookie) { console.log(` Cookie kullanımı: Aktif`); } console.log(''); // Ana klasörü oluştur const mainDir = path.resolve(options.path); await fs.ensureDir(mainDir); // Her template'i sırayla indir for (let i = 0; i < urls.length; i++) { await downloadTemplate(urls[i], i, options, cookie); console.log(''); // Boş satır ekle } console.log('🎉 Tüm temalar başarıyla indirildi!'); console.log(`📂 İndirilen temalar: ${path.resolve(options.path)}`); } // NPM modülü olarak export et module.exports = { downloadAllTemplates, downloadTemplate, config }; // Eğer dosya direkt çalıştırılıyorsa otomatik başlat if (require.main === module) { downloadAllTemplates(templateUrls, { path: './downloaded-themes', supportWordPressStructure: true }).catch(console.error); }